1014 заметок с тегом

программирование

99 бутылок: Psql (переработанная версия)

Я уже публиковал сегодня одну попытку написать «песню про пиво» на языке утилиты psql, но, к сожалению, результат получился не совсем честный — для математики и сравнения мне пришлось использовать вставку на эскуэеле.

Чисто интуитивно чувствовалось, что какое-то подобие арифметики можно сделать безо всякого эскуэля и эта мысль не давала мне покоя. Сегодня ездил в больницу — кашель никак не проходит и, пока ждал врача, в голове появилась конкретная идея.

Реализовать задуманное получилось через таблицу замен (об этом ниже) и кодогенерацию — psql позволяет писать в файлы произвольное содержимое, а потом эти же файлы подключать. К сожалению, после этого остаётся немного мусора во временной папке, но тут уж ничего не попишешь.

Таблицу замен я реализовал через девять переменных с числовыми именами, значением которых является число на единицу меньше имени. Например: \set 9 8 — тут переменная с именем 9 получает значение 8.

Чтобы реализовать вычитание единицы, пришлось разделить счётчик цикла на две переменные — beer_h (старший разряд) и beer_l (младший разряд), вычитание производится для каждой цифры отдельно. На диск записывается код, в который помещается имя переменной и её новое значение, используя текущее значение, как имя переменной, из которой надо взять следующее. Понимаю, что запутанно, надеюсь на примере понять будет проще.

Вот этот кусок: \qecho '\\set beer_h :':beer_h. Если текущее значение переменной beer_h равно 9, то на диск будет помещен скрипт с содержимым \set beer_h :9, после его подключения код выполнится, а так как значение переменной 9 — 8, то beer_h получит значение 8.

Одновременно я таким же способом создаю логические переменные beer_разряд_value_значение со значением 1, которые я использую потом как флаги, чтобы остановить цикл или отключить использование множественного числа, когда число «бутылок» доходит до единицы.

С кодогенерацией, кстати, работать веселее — сгенерированный код потом можно использовать как некий аналог подпрограмм, экономя размер кода.

В общем, в результате получилось реализовать «песню про пиво» на чистом языке утилиты psql, не привлекая другие языки программирования.

\if 0
    99.psql
    Psql beer song v.2
    Written by Evgeny Stepanischev https://bolknote.ru
\endif

\if :{?beer_l} \else
    \set 9 8 \set 8 7 \set 7 6 \set 6 5 \set 5 4 \set 4 3 \set 3 2 \set 2 1 \set 1 0
    \set print /tmp/beer_print.sql
    \set minus /tmp/beer_minus.psql

    \out :print
    \qecho '\\if :{?beer_h_value_0} \\echo -n :beer_l'
    \qecho '\\else \\echo -n :beer_h:beer_l \\endif'

    \set beer_h 9
    \set beer_l 9
    \set s s
\endif

\include :print
\echo -n ' bottle':s' of beer on the wall, '
\include :print
\echo ' bottle':s' of beer.'

\if :{?beer_l_value_0}
    \unset beer_l_value_0
    \set beer_l 9
    \out :minus
    \qecho '\\set beer_h :':beer_h '\\set beer_h_value_:':beer_h 1
    \include :minus
\else
    \out :minus
    \qecho '\\set beer_l :':beer_l '\\set beer_l_value_:':beer_l 1
    \include :minus
\endif

\if :{?beer_h_value_0}
    \if :{?beer_l_value_1}
        \set s
    \endif
    \if :{?beer_l_value_0}
        \echo Take one down and pass it around, no bottles of beer.'\n'
        \echo No more bottles of beer on the wall, no more bottles of beer.
        \echo Go to the store and buy some more, 99 bottles of beer on the wall.
        \q
    \endif
\else
    \unset beer_l_value_1
\endif

\echo -n 'Take one down and pass it around, '
\include :print
\echo ' bottle':s' of beer.\n'

\include_relative 99.psql
 1 комментарий    368   6 дн   99   postgres   программирование

99 бутылок: Psql

Давно чесались руки написать очередную «песню о пиве» на каком-нибудь языке программирования, но что-то никак не мог определиться с выбором. В конечном счёте выбрал psql.

75. Psql — не язык программирования, а клиентская программа, распространяемая вместе с СУБД «Постгрес». У неё есть довольно скромный встроенный язык, в который относительно недавно добавили условный блок.

Тем не менее на этом языке можно требуемое, если привлечь для математики и сравнения эскуэль — сам psql такого не умеет. В качестве цикла пришлось применить включение файла в самого себя, повезло, что цикл относительно короткий, а то бы я упёрся в лимит на количество открытых файлов в системе.

Добавлено позднее: мне всё же удалось реализовать «песню о пиве» без использования эскуэля.

\if 0
    99.psql
    Psql beer song
    Written by Evgeny Stepanischev https://bolknote.ru
\endif 

\if :{?beer} \else \set beer 99 \endif
\echo :beer bottles of beer on the wall, :beer bottles of beer.

select :beer-1 beer, :beer > 2 notend \g /dev/null
\gset

\if :notend \set s s \else \set s \endif

\echo Take one down and pass it around, :beer bottle:s of beer.'\n'

\if :notend
    \include_relative 99.psql
\else
    \echo 1 bottle of beer on the wall, 1 bottle of beer.
    \echo Take one down and pass it around, no bottle:s of beer.'\n'
    \echo No more bottles of beer on the wall, no more bottles of beer.
    \echo Go to the store and buy some more, 99 bottles of beer on the wall.
\endif

Вроде код понятен почти без объяснений, но несколько моментов без знакомства с утилитой вряд ли возможно расшифровать самостоятельно, поэтому я их вкратце хочу объяснить.

:{?beer} — синтаксис проверки существования переменной, это нужно чтобы понять запускаем ли мы программу в первый раз или включили её в себя же, чтобы имитировать цикл.

select :beer-1 beer, :beer > 2 notend — эскуэль-запрос, которым делается математика и сравнение — вычитается единица из переменной и проверяется не пора ли выходить из цикла.

\g /dev/null — запуск запроса в выводом результата в /dev/null (то есть в никуда), поскольку его результат на экране нам не нужен.

\gset — записываем результат запроса в переменные, имена которых равны именам столбцов запроса.

У меня есть одна идея, как сделать вычитание, не привлекая эскуэль, надо будет попробовать. А вот с остановом хуже — там никаких идей.

 1 комментарий    195   7 дн   99   postgres   программирование

«ВАМПИР»

Я не очень-то интересуюсь гештальт-терапией, поэтому всё, что я про неё знаю, мне рассказал больше десяти лет назад один мой старый приятель. С его слов, гештальт — это незавершённые поступки, которые хочется завершить. А терапия каким-то образом помогает это сделать.

Одному из моих гештальтов в этом определении почти 30 лет — в начале 90-х у нас в семье не было своего компьютера и мы ходили в гости к нашему родственнику, у которого был самосборный «Радио-86РК». Там у меня был первый опыт отладки чужой программы — игры «ВАМПИР», написанной на «Бейсике», которая никак не хотела запускаться.

Игру удалось починить и возможно поэтому она мне так запомнилась, что через много лет мне захотелось её найти и сыграть. Тем более, что эмуляторов «Радио-86РК» в наше время, как грязи.

Но не тут-то было, никаких следов игры «Вампир» для этого компьютера в интернете не нашлось. Сюжет я худо-бедно помнил — в этой текстовой игрушке нужно было бродить по комнатам и убивать вампира, кроме того, в «дневниках жизни» нашлась фраза из неё: «американская народная игра убей вампира». Правда это никак не помогало найти саму игру.

Шли годы, я без особой надежды регулярно гуглил эту игрушку, но она никак не находилась.

«HUNT THE WUMPUS» By Gregory Yob, рисунок из статьи в «The Best of Creative Computing Volume 1», 1976

Но тут недавно, когда я подбирал чего бы мне такого портировать на «Фортран», в списке старых игр на «Бейсике» увидел игру «Hunt the Wumpus» 1972 года и по листингу понял, что передо мной — исходный вариант игры «Вампир», следы которой я до сих пор никак не мог разыскать!

Хоть какая-то реальная зацепка!

Все программы для этого компьютера в те времена набирались вручную из печатных источников — из журналов, возможно из каких-то книг по программированию. Осталось узнать где в те годы напечатали переведённую и адаптированную игру «Hunt the Wumpus» для «Радио-86РК», набрать её в эмулятор и наконец сыграть в неё ещё раз!

 2 комментария    391   28 дн   айрхеология   программирование

Игра «Ровно в полдень»

Как я писал вчера, по не совсем ясной причине мне захотелось посмотреть что из себя представляет «Фортран» — язык программирования, придуманный ещё в 50-е. Как по мне, нет лучше способа познакомиться с языком, чем попробовать на нём что-то написать.

Крупное писать не хотелось, из мелочи ничего в голову не пришло, поэтому пошёл по лёгкому пути — портировал небольшую текстовую игру «HIGH NOON» («High noon» — очень известный в своё время фильм-вестерн, вышедший в 1952 году, в русском переводе называется «Ровно полдень»).

Игра написана неким Крисом Гейло, учеником средней школы Сайассет на Лонг-Айленде в начале 1970 года на каком-то раннем диалекте «Бейсика». Её уже набранные кем-то исходники вместе со сканами лежат на «Гитхабе».

Часть оригинального листинга игры «Ровно в полдень»

Игре уже около полувека и сейчас она, разумеется, кажется крайне незамысловатой. Дело происходит на Диком Западе, вы стоите на ста шагах от Чёрного Барта, который вызвал вас на перестрелку. На каждом шаге игрок может продвинуться вперёд, отступить, прыгнуть в укрытие, сдаться или выстретить. Чем ближе удасться подобраться к оппоненту, тем выше точность выстрела, причём и игрока, и его виртуального противника.

На портирование ушло около часа (на результат можно полюбоваться в моём репозитории), больше всего времени пришлось потратить на написание функции, которая переводит числа в строки (если ею не пользоваться, а просто выводить числа как есть, «Фортран» очень неуместно форматирует их пробелами).

Ещё никак не мог понять в чём смысл части оригинального листинга с 460 по 520 строки, сначала мне показалось, что он просто не дописан:

460 PRINT "WHAT IS YOUR LUCKY NUMBER FOR TODAY";
470 INPUT A
480 A = 1E4 * ABS(SIN(A))
490 A = (A - INT(A)) * 1000
500 FOR I = 1 TO A
510 B = RND(0)
520 NEXT I

У игрока спрашивается какое сегодня его счастливое число, потом делается некое вычисление и по результату прокручивается цикл с RND(0) внутри. В дальнейшем полученные в этом куске кода значения ни на что как будто бы не влияют.

Это место из своей портированной версии я просто выкинул, но позже мне пришло в голову объяснение. По всей видимости, это попытка инициализировать генератор псевдослучайных чисел. Видимо в «Бейсике», на котором писал автор, не было другого хорошего способа это сделать.

 2 комментария    338   1 мес   программирование   фортран

Счётчик цикла и Фортран

Что-то захотелось на Фортран поближе взглянуть, не знаю уж чем он меня заинтересовал, возможно тем, что во времена учёбы в Университете постоянно про него слышал (всё-таки язык для научных вычислений, а я на математика учился), но использовать не довелось.

Пока только-только начинаю знакомиться, для практики переписал с Бейсика на Фортран одну из версий игры 1970 года «Highnoon», наверное выложу её попозже на «Гитхаб».

У языка много особенностей, но больше всего интересна следующая. Фортран — язык со статической типизацией, то есть для каждой переменной требуется указать тип, но в некоторых случаях компилятор может вывести тип самостоятельно. Правда, думаю, правило вывода этого типа сильно вас удивит.

В этой простой программе тип переменной I не указан, но компилятор знает, что её тип — integer.

DO I = 99, 1, -1
    PRINT *, I
END DO

END

Дело в том, что по-умолчанию (это поведение можно отключить) Фортран считает все переменные, имена которые начинаются с букв I, J, K, L, M или N целыми (integer), а все остальные — типом real.

Есть мнение, что отсюда и пошла привычка называть переменные цикла буквами из «целого» ряда.

 6 комментариев    326   1 мес   программирование   фортран

Говорящие часы

По причинам, далёким от рациональных, захотелось мне вчера сколхозить для себя говорилку времени. Простой вариант проговаривания цифр показался очень скучным, сразу вспомнились лебедевские часы «Вербариус», где одно и то же время каждый раз пишется по-разному.

На сайте «Студии Лебедева», в разделе часов «Вербариус» есть языковые файлы, три из которых — для русского, английского и французского выгружены в виде обычных экселевских таблиц. Их легко преобразовать в формат CSV при помощи приложения «Намберс» или всем знакомого «Экселя».

В получившемся файле много пустых полей и лишних пробельных символов, которые будут мешать, их легко удалить утилитой командной строки sed:

sed -i '' -E "s/; +| +;/;/g; s/;+/;/g; s/;[ "$'\r'"]*$//;" ~/Downloads/russian.csv

У получившегося файла очень простой формат — сначала идёт время без ведущего нуля, потом — варианты для произношения, разделённые точкой с запятой. На мой взгляд, для поиска нужной строки и выбора случайного вариант для произношения лучше всего подойдёт утилита awk.

Если направить её вывод маковской утилите say, то говорящие часы готовы:

awk -F\; "BEGIN{srand}/^$(date +%k:%M|xargs)/{print \$(int(2+rand*(NF-1))); exit;}" \
~/Downloads/russian.csv | xargs say

Я их пока вызываю из командной строки коротким алиасом t, но если понравится, настрою как службу, с вызовом через горячую клавишу.

 2 комментария    303   1 мес   bash   программирование

Особенности использования индексов в PostgreSQL

Вчера под ночь боролся с «Постгресом» (это такая СУБД), в итоге поборол, но не так, как хотелось бы. Бизнес-задачу мне не хотелось бы сейчас рассматривать, поэтому для иллюстрации проблемы создам тестовую таблицу безо всякой смысловой нагрузки:

SELECT id, CASE WHEN RANDOM()>.5 THEN value END AS value
INTO test
FROM generate_series(1, 1000000) WITH ORDINALITY AS test(id, value)

В тестовой таблице два поля — идентификатор (id) и значение (value), примерно половина значений в поле value — NULL. И теперь нам надо создать индекс для запросов, которые выглядит примерно так:

SELECT id FROM test WHERE value IS NULL ORDER BY id;
SELECT id FROM test WHERE value IS NOT NULL ORDER BY id;

План для них выглядит одинаково печально, что логично — на таблице ни одного индекса:

Sort  (cost=61739.93..62977.68 rows=495100 width=4) (actual time=47289.775..47761.235 rows=498778 loops=1)
   Sort Key: id
   Sort Method: external merge  Disk: 6848kB
   ->  Seq Scan on test  (cost=0.00..14910.00 rows=495100 width=4) (actual time=12.039..40854.731 rows=498778 loops=1)
         Filter: (value IS NULL)
         Rows Removed by Filter: 501222
 Planning Time: 0.174 ms
 Execution Time: 48134.553 ms

Чтобы сэкономить место, мне очень хотелось сделать индекс функциональным:

CREATE UNIQUE INDEX ON test((value IS NULL), id);

Таким образом вместо целого поля будет использован логический тип всего с двумя значениями.

Конечно из-за выравнивания полей в индексе полноценно воспользоваться экономией в данном случае не получится, но хотя бы статистика для оптимизатора будет точнее за счёт однообразности значений.

Первый запрос ожидаемо улучшился:

Index Scan using test_expr_id_idx on test  (cost=0.42..33882.43 rows=495100 width=4) (actual time=0.056..519.584 rows=498778 loops=1)
   Index Cond: ((value IS NULL) = true)
 Planning Time: 0.182 ms
 Execution Time: 894.551 ms

А второй — нет:

Sort  (cost=62738.26..64000.51 rows=504900 width=4) (actual time=1046.582..1471.180 rows=501222 loops=1)
   Sort Key: id
   Sort Method: external merge  Disk: 6880kB
   ->  Seq Scan on test  (cost=0.00..14910.00 rows=504900 width=4) (actual time=0.033..522.071 rows=501222 loops=1)
         Filter: (value IS NOT NULL)
         Rows Removed by Filter: 498778
 Planning Time: 0.136 ms
 Execution Time: 1841.282 ms

Несмотря на то, что в индексе содержится информация, достаточная для выполнения этого запроса, в данном случае «Постгрес» не понимает, что VALUE IS NOT NULL эквивалентно (VALUE IS NULL) = false.

Причём, если попробовать переписать запрос, чтобы условие выборки выглядело как (VALUE IS NULL) = false, то тут оптимизатор не оплошает — перепишет за нас условие в виде VALUE IS NOT NULL и опять не будет узнавать его в индексе.

Я вижу несколько путей решения этой проблемы, все с недостатками.

Во-первых, можно «замаскировать» конструкцию …IS [NOT] NULL, чтобы оптимизатор перестал его нормализировать:

CREATE UNIQUE INDEX ON test((CASE WHEN value IS NULL THEN TRUE ELSE FALSE END), id);

Недостатки очевидны: громоздкость конструкции, кроме того, легко забыть, что условия для этой таблицы надо писать именно так. Можно поместить эту конструкцию внутрь функции, но это устранит только первый недостаток.

Во-вторых, можно создать два индекса вместо одного:

CREATE UNIQUE INDEX ON test((value IS NULL), id);
CREATE UNIQUE INDEX ON test((value IS NOT NULL), id);

Недостаток: у нас два индекса — при вставке или обновлении будем писать в два места, раздуваться (bloat) у нас будут тоже два индекса вместо одного.

В-третьих, можно не выпендриваться, а создать обычный индекс:

CREATE UNIQUE INDEX ON test(value, id);

Фатальный недостаток: индекс, по сути, бесполезен — он содержит всю таблицу целиком, «Постгрес» скорее всего выберет последовательное сканирование таблицы, а индекс использовать не будет.

И, наконец, вариант, который мне нравится в этой ситуации больше всего — частичные индексы:

CREATE UNIQUE INDEX ON test(id) WHERE value IS NULL;
CREATE UNIQUE INDEX ON test(id) WHERE value IS NOT NULL;

Да, у нас опять два индекса, но они довольно компактны по размеру: в каждом примерно половина данных, кроме того, в нём вообще только одно поле — второго нет вовсе, его заменяет условие индекса.

Добавлено позднее: мне тут подсказывают, что нужно ещё учитывать селективность. Это верно, но мне совершенно не хотелось затрагивать ещё и эту тему. В реально задаче в индексе больше полей.

 1 комментарий    368   1 мес   sql   постгрес   программирование

Длинные имена и PHP

На днях наткнулись на немаловажную особенность, — на системе примонтированной по CIFS (эта система — наследница небезызвестной «Самбы»), программа ПХП не видела некоторые файлы. В принципе, мы довольно быстро разобрались, что дело в длине имени — все функции для обхода директориев в языке такие файлы игнорировали.

Оказалось, что наша версия ПХП использует устаревший вызов readdir_r. У него много недостатков, но среди прочих — на некоторых системах он не может читать длинные имена. Проблему исправили в версии 7.4, но у нас всё ещё есть проекты на 7.3.

До конца перехода будем использовать системную утилиту /bin/ls, у неё таких трудностей не возникает.

 Нет комментариев    270   1 мес   php   программирование

Архив программ

Честно сказать, я считаю проект «Архив Интернета» более важным проектом, чем «Википедия», жаль что ему достаётся незаслуженно мало внимания. За 20 лет существования моего сайта, в старых заметках поумирало 100% ссылок и только «Архив Интернета» позволяет понять что когда-то по ним было.

В нём же я нередко ищу старые книги, подшивки газет и прочие бумажные материалы — некоторые издания можно найти в интернете только там. Но кроме этого, как оказалось, там бывают старые компьютерные программы, причём запускаемые прямо в окне браузера!

Препроцессор Ратфора в Фортран, запущенный в ДОСБоксе

Я тут заинтересовался препроцессором «Ратфор», который был разработан в 1970-х — он транслировал си-подобный синтаксис в популярный тогда язык программирования «Фортран». Кстати, Фортран до сих пор встречается в современных математических библиотеках, возможно поэтому и он сам, и «Ратфор» без проблем ставятся из «брю» на «Мак».

Пока гуглил информацию по «Ратфору» (литературы не так уж и много), в «Архиве Интернета» нашёл реализацию, запускаемую в окне браузера под портированным ДОСБоксом. Очень здорово придумано — можно сразу бегло посмотреть требуемое.

 1 комментарий    313   1 мес   айрхеология   программирование   фортран

«Я тебя по IP вычислю»

Сегодня утром, читая какую-то статью на «Хабре», зацепил в комментариях глазами шуточную угрозу «я тебя по айпи вычислю» и подумал, что лет так двадцать назад она совсем не была шуткой.

Я сам это делал несколько раз. Добытые магическим образом персональные данные остужали любого зарвавшегося оппонента. Правда никакой магии там не было — немного технической грамотности и личных знакомств.

Помню, в одном из местных веб-чатов, где я был администратором, зацепился языками с одним очень язвительным парнем, он доставал весь чат, но стоило его забанить, появлялся снова под новым ником.

Посмотрел его айпи-адрес (в те времена очень у многих они были «белыми»), оказалось — это адрес диалап-пула одного из казанских провайдеров. Поскольку айтишная Казань в те годы была маленькая и все друг друга знали, написал знакомому админу, который там работал, он мне за пиво выдал имя и фамилию абонента, этого хватило, чтобы навсегда отвадить его от чата, — получив в личку своё ФИО, хам моментально пропал с горизонта.

 3 комментария    346   2 мес   программирование
Ранее Ctrl + ↓