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

Отсутствие cp1251 в iconv

На днях встретил дистрибутив Линукса, в котором не оказалось по-умолчанию кодировки CP1251 (то же, что и Windows-1251) в iconv.

Многим, конечно, покажется, что однобайтовые кодировки не нужны, но во-первых, некоторые данные в интеграциях приходят именно в них, а во-вторых, чего бы не сэкономить байты и процессорное время (в два раза, в случае русскоязычных текстов, по сравнению с UTF-8), если тексту в другом алфавите в продукте взяться неоткуда?

В общем, как бы то ни было, запишу себе, чтобы не забыть. Если iconv ругается на CP1251:

iconv -f cp1251 -t utf-8 /dev/null
iconv: failed to start conversion processing

то надо поставить пакет glibc-gconv-extra и ошибка пропадёт.

Оставить комментарий

Адвент-календарь

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

В начале задания совсем пустяковые, но постепенно сложность нарастает. Пока всё ещё несложно, в последнем задании, которое я выполнил — перебор с возвратом, но времени на решение у меня уходит всё больше и больше.

Скриншот README.md: «Advent of Code 2021: 18/50». Таблица решений (GNU dc, PHP, Rexx) и диаграмма: Pascal 15.8%, V 13.6%, AppleScript 12.3%, Shell 12.0%, Dart 10.6%, BASIC 6%.
Мой репозиторий с решениями задачи из адвент-календаря

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

На сегодня у меня выполнено восемнадцать заданий (любопытствующие могут заглянуть ко мне в репозиторий) на следующих языках: GNU dc, PHP, Regina Rexx, Perl, Tcl, PowerShell, Crystal, V, D, AppleScript, FOCAL-81, REFAL-5λ, GNU Awk, Vala, PCRE, FreePascal, Commodore BASIC и Dart.

Честно говоря, подбор языка занимает больше времени, чем написание на нём решения, тем более, часть хорошо знакомых языков я оставил на конец, — по совету товарищей, которые прорешивали такие календари в прошлые коды.

Единственный язык, где я гораздо дольше мучился с самим языком, чем с его подбором — это Рефал. Это язык программирования на сопоставлениях с образцом и я когда-то написал на нём простенькую программку. Но с тех пор утекло много воды и опять помещать в голову эту идеологию было настолько тяжело, что я всё накрепко позабыл и половину времени думал, что компилятор просто не работает.

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

4 комментария

«99 бутылок» на баше без цикла

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

Мне кажется совершенно естественным, что такой способ интерпретации должен дожить и до наших дней, он же очень простой в реализации и для многих вещей его вполне достаточно.

#!/bin/bash
len=$(wc -c<"$0"); : https://bolknote.ru 2021
echo -e "99 bottles of beer on the wall, 99 bottles of beer.
Take one down and pass it around, 98 bottles of beer on the wall.\n"
: ${i:=99}; ((--i)) && (grep -B5 -m1 ^G "$0" |
sed "s/9[8]/$(($i-1))/g;s/9[9]/$i/g;s/ [0] / no /;s/\([1] b[^s]*\)s/\\1/g" >> "$0") ||
(echo 'No more bottles of beer on the wall, no more bottles of beer.
Go to the store and buy some more, 99 bottles of beer on the wall.';truncate -s "$len" "$0")
:;

Пока разговаривали, у меня возникла смешная идея — попробовать написать считалочку «99 бутылок пива…» без циклов, на принципе дописывания кода, чтобы строки добавлялись в файл программы по мере интерпретации.

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

Результат чуть выше. Так как bash местами довольно сложен для понимания, попробую позже (если не заленюсь) сделать разбор странно выглядящих мест этой программы отдельным постом.

Оставить комментарий

PHPFuck для PHP8

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

Один из читателей написал, что программа PHPFuck (это способ писать программы без алфавитно-цифровых символов), которую я писал несколько лет назад, не работает с восьмой версией интерпретатора ПХП. Он попробовал починить её самостоятельно, но не преуспел.

В самом деле, восьмая версия стала куда строже к программисту, из-за чего починить написанное очень тяжело, проще написать всё заново с учётом новых ограничений.

<?=[[$_=@([].[]),$___=-!![],$$_=![]+![],$$_=$$_*$$_,$__=$$_+![],$$__=$$_+$__,
$____=$_[-![]-![]],$$___="{$$__}"^$____,$$___=($$___^"$___"). ($$___^"{$$__}"^"$___"^"{$$_}").
($$$_=$$___^"{$$_}"). $$$_.($_____="{$$__}"^"{$$_}"^++$____).("$___"&$____).
([$$___=$_[+![]],"{$$_}"|++$$___][+![]]).$_____.$_[+![]].$$$_. ("$__"^$_[-![]-![]]^"$___")
],$$___][+![]];

Выше новая версия.

Кстати, она компактнее, но при этом выводит больше текста — «hello world», вместо «Hello!» предыдущего варианта. Ниже я немного расскажу откуда взялась экономия и с чем ещё пришлось столкнуться.

Принцип формирования текста остался, в общих чертах, прежним — корнем всего является комбинация @([].[]), которая даёт слово ArrayArray из-за неявного преобразования массива в текст — это такая особенность ПХП (символ @ нужен тут, чтобы подавить предупреждение, которое выдаёт интерпретатор).

Из этой строки потом рождаются все остальные символы. Дело в том, что в ПХП над символами можно производить некоторые бинарные и математические операции, что иногда позволяет получить другие символы. Например, первая буква — h в слове hello получается из сочетания '9' ^ 'a' ^ '0' (где ^ — бинарная операция «исключающего или»).

При этом цифры я получаю довольно простым способом — 0 как +!![] (преобразованием false в число), «девятку»,  — набирая нужные цифры по единицам (из +![]), а a — как предпоследнюю букву из полученной ранее строки ArrayArray, то есть @([].[])[-2] (аналогично, -2 получается тут из двух замаскированных единиц со знаком «минус»).

По сравнению с предыдущим вариантом в новом коде сделаны следующие изменения.

Во-первых, вместо того, чтобы составлять из получаемых букв printf (функцию, которую я использовал в прошлый раз для вывода текста), тут я пользуюсь выводом через короткую конструкцию <?= — в этом основная экономия. Я выбрал этот вариант, так как конструкция <?, которую я использовал ранее, больше не работает, а заменившая её <?php содержит буквы.

В этой связи весь код теперь — большое выражение, все переменные в нём лежат внутри массива. Если давать им нормальные имена, то основной принцип выглядит как-то так: <?=[ [ $v1 = …, $v2 = …, $result = $v1 . $v2 . … ], $result ][1]; То есть они вычисляются внутри массива, последний элемент которого, содержащий вычисленное выражение, выводится.

Во-вторых, в ПХП стало больше строгости и числа, которые я использую для алхимии с буквами, теперь приходится преобразовывать в строки — раньше это происходило неявно, а сейчас интерпретатор останавливается с фатальной ошибкой. То же с не заданными константами — раньше они преобразовывались в строки (чем я пользовался, используя несуществующую константу, чтобы получить символ _ для дальнейших преобразований), теперь так делать нельзя.

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

Ниже приведен листинг этой программы, вдруг кому-то интересно:

<?php
declare(strict_types=1);

// Доступный алфавит
$abc = str_split(count_chars("Array0123456789", 3));

// буквы, которые надо получить
$need = str_split(count_chars("hello world", 3));

function gen(array $chrs): Generator
{
    // чем операции длиннее, тем ближе к концу
    // это нужно, чтобы более короткие конструкции выигрывали
    foreach ($chrs as $ch) {
        yield $ch => [$ch, $ch];
    }

    // возврат: из чего делаем, [как выглядит, значение]
    foreach ($chrs as $ch) {
        yield $ch => ["~$ch", ~$ch];
    }

    // для символа инкремент работает, а декремент — нет (особенность ПХП)
    foreach ($chrs as $ch) {
        $value = $ch;
        yield $ch => ["++$ch", (string)++$value];
    }
}

function eval_op(string $op, string $a, string $b): string
{
    return ([
        '|' => fn($a, $b) => $a | $b,
        '&' => fn($a, $b) => $a & $b,
        '^' => fn($a, $b) => $a ^ $b,
    ][$op])($a, $b);
}

function generate(array $set): Generator
{
    foreach (gen($set) as $src1 => [$p1, $ch1]) {
        foreach (gen($set) as $src2 => [$p2, $ch2]) {
            foreach (['|', '&', '^'] as $op) {
                $result = eval_op($op, $ch1, $ch2);

                if ($result !== $ch1 && $result !== $ch2) {
                    yield [
                        $src1,
                        $src2,
                        "$p1 $op $p2",
                        $result,
                    ];
                }
            }
        }
    }
}

function out(string $str): string
{
    return preg_replace_callback(
        '/[\x00-\x1F\x80-]/s',
        fn($m) => sprintf("\\x%02X", ord($m[0])),
        $str
    );
}

$set = $abc;

do {
    foreach (generate($set) as [$src1, $src2, $str, $res]) {
        $idx = array_search($res, $need, true);

        if (!isset($rules[$res])) {
            $rules[$res] = $str;
        }

        if ($idx !== false) {
            if (!in_array($src1, $abc, true)) {
                echo out("{$rules[$src1]} = '$src1'"), "\n";
            }

            if (!in_array($src2, $abc, true)) {
                echo out("{$rules[$src2]} = '$src2'"), "\n";
            }

            echo out("$str = '$res'"), "\n\n";

            unset($need[$idx]);
        }

        $set[] = $res;
    }

    $set = array_unique($set);
} while ($need);

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

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

У меня дробления нет, я использую предыдущие выражения целиком, а имена переменным моя программа вообще не подбирает.

2 комментария

Как скачать видео с URL «blob:»

Нередко в последнее время нередко встречается ситуация, когда хочется сохранить себе видео какого-либо курса или подкаста. Для известных хостингов видео, типа «Ютьба» или «Вконтакта» существуют плагины браузера, которые справляются с этой задачей, а с менее известными площадками могут возникать проблемы.

Бывает, что в теге проигрывания видео (который легко найти при помощи «Инструментов разработчика» браузера) указан файл, который можно скачать, но иногда там применяется протокол blob: и с ним не настолько всё просто.

Скриншот вкладки Network браузера с открытым файлом master.m3u8, содержащим строки #EXTM3U и #EXT-X-STREAM-INF с параметрами разрешения 640x360 и кодеков avc1.4d401e, mp4a.40.2.
Пример окна «Инструментов разработчика» с найденным файлом с расширением «m3u8»

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

Нужно открыть «Инструмент разработчика» на вкладке «Сеть» («Network»), запустить проигрывание видео и поискать в перехваченных урлах первый, который заканчивается на m3u8.

Этот урл надо скормить известной утилите для работе с видео ffmpeg:

ffmpeg -i 'https://example.org/master.m3u8' -c copy -bsf:a aac_adtstoasc ~/Downloads/output.mp4

Где https:​//example.org/master.m3u8 — перехваченный урл, а ~/Downloads/output.mp4 — файл, куда нужно будет сохранить искомое видео.

После этого остаётся только ждать, утилита ffmpeg сделает всё сама.

13 комментариев

Среднее специальное в «айти»

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

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

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

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

6 комментариев

Мои «99 бутылок» на клоне Эпла-I

Два года назад я написал на ассемблере первого компьютера компании Эпл (сейчас он условно называется «Эпл I») считалочку «99 бутылок пива на стене». Писал под эмулятор, но не оставлял надежду увидеть когда-нибудь как моя программа работает на «железе».

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

Запуск моей реализации считалочки «99 бутылок пива на стене» на клоне Эпла-I

Увы, позже ни на одно из моих писем владелец не ответил, зато один из энтузиастов ретро-техники в переписке пообещал, что если когда-нибудь оживит свой клон Элпа-I, то обязательно пришлёт мне доказательство того, что моя программа способна запуститься не только под эмулятором. Сегодня я получил от него видео.

Зелёная плата с чёрными микросхемами — это и есть сам компьютер. Программа, как можно заметить, вводится в виде аудиофайла через звуковой вход (через разъём справа к компьютеру подключен смартфон), это гораздо быстрее, чем набирать её на клавиатуре.

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

2 комментария

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

6 комментариев

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 — записываем результат запроса в переменные, имена которых равны именам столбцов запроса.

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

2 комментария

«ВАМПИР»

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

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

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

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

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

Чёрно-белый рисунок «HUNT THE WUMPUS» авторства Gregory 408: летучие мыши с номерами 19 и 20, спящий монстр W в пещере, яма с табличкой PIT и стрелки на земле.
«HUNT THE WUMPUS» By Gregory Yob, рисунок из статьи в «The Best of Creative Computing Volume 1», 1976

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

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

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

3 комментария

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

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

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

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

Листинг программы на BASIC с выводом меню выбора действий: «1. ADVANCE», «2. STAND STILL», «3. FIRE», «4. JUMP BEHIND THE WATERING TROUGH», «5. GIVE UP», «6. TURN TAIL AND RUN
Часть оригинального листинга игры «Ровно в полдень»

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

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

Ещё никак не мог понять в чём смысл части оригинального листинга с 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) внутри. В дальнейшем полученные в этом куске кода значения ни на что как будто бы не влияют.

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

4 комментария

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

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

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

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

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

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

END

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

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

10 комментариев

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

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

На сайте «Студии Лебедева», в разделе часов «Вербариус» есть языковые файлы, три из которых — для русского, английского и французского выгружены в виде обычных экселевских таблиц. Их легко преобразовать в формат 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, но если понравится, настрою как службу, с вызовом через горячую клавишу.

3 комментария

Особенности использования индексов в 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;

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

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

2 комментария

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

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

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

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

Оставить комментарий

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

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

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

Скриншот командной строки с выводом каталога файлов RATFOR (1985–1986) и фрагментом кода на языке RATFOR, включая цикл сравнения строк и условные переходы.
Препроцессор Ратфора в Фортран, запущенный в ДОСБоксе

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

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

1 комментарий

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

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

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

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

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

6 комментариев

ФОКАЛ

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

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

Описание функций FRAN и FSBR, их синтаксиса, примеров использования и диагностических сообщений кодов ошибок 27, 6 и 9.
«ФОКАЛ для микро- и мини-компьютеров». Л. Г. Осетинский, М. Г. Осетинский, А. Н. Писаревский. Л.: Машиностроение, 1988

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

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

2 комментария

Починил PogodaStatusBar

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

Строка статуса с иконкой терминала, текстом «Казань | +20°C ☀️⛅ | 3 🍏, Line 1, Column 1» на тёмном фоне.
Строка статуса: в Казани +20°C, пробки три балла

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

Остался ещё один баг — если «Яндекс» не отдаёт информацию о пробках (в некоторых небольших городах такой информации нет), то плагин работать не будет. Не знаю, стоит ли это править, всё-таки популярностью у него невелика́ и делал я его больше для себя.

2 комментария

Какие ещё графические форматы поддерживает ваш браузер?

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

Из всего, что я там проверяю, сразу не сработала только проверка на AVIF — это новейший формат, у меня на компьютере его поддерживает пока только «Файерфокс», ему важно указание типа содержимого, а «Гитхаб» передаёт неправильное значение.

Скриншоты браузеров: список поддерживаемых форматов изображений (JPEG, PNG, SVG, WEBP, BMP, GIF, TIFF, ICO, AVIF и др.).
Браузеры (сверху вниз): «Сафари» 13, «Файерфокс» 78 и «Опера» 69

Я подумал, что наверняка есть какой-то способ указать правильный тип в файле конфигурации сайта, но нет, так это не работает, такой настройки в нём нет. В принципе, известно откуда «Гитхаб» берёт свою базу типов содержимого — из проекта mime-db. Но этот путь тоже не сработал — я добавил туда тип для AVIF больше недели назад, но ничего не изменилось. Видимо синхронизация изменений происходит нечасто.

Пришлось идти другим путём — вместо того, чтобы загружать файл картинки, внедрил его внутрь при помощи протокола data:, это позволило обойти проблему. Можете протестировать на своём браузере.

Ссылка на проект: https://bolknote.github.io/detect-browser-graphics-formats/
Ссылка не репозиторий проекта: https://github.com/bolknote/detect-browser-graphics-formats

7 комментариев

← 1 16 17 18 19 20 67 →