Category: it

Category was added automatically. Read all entries about "it".

кукяй

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

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

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

#!/bin/bash
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 437 "$0")
:;

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

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

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



Ссылка на оригинал.
кукяй

Брандмауэр

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

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

Брандмауэр около дома по адресу ул. Волкова, 42 со стороны снесённого соседнего дома

То и другое слово распадается на два: «пожар» и «стена». То есть «файервол» или «брандмауэр» — это «противопожарная стена».

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

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

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

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



Ссылка на оригинал.
кукяй

День рождения Рунета и немного статистики

Я тут узнал, что два дня назад (7 апреля) был день рождения Рунета. В этот день 27 лет назад был зарегистрирован и внесён в международную базу данных национальных доменов верхнего уровня домен «.ru».

Интересно, что с высоты лет, мой сайт не кажется сильно моложе — сейчас я отсчитываю «электронную» часть своего сайта с апреля 1999 года, когда на сайте «CIT Forum», где айтишники размещали свои материалы в 90-е и «нулевые», появилась моя первая в интернете статья. То есть первому моему контенту 22 года. Если отсчитывать с того момента, когда я начал что-то выкладывать на своём собственном сайте, то ситуация не сильно меняется — это произошло год спустя, осенью 2000-го.

В связи с этими двумя апрельскими датами — 27 лет Рунету и 22 года первой моей публикации в интернете, мне пришло в голову подбить кое-какую статистику на своём сайте.

За всё время я намолотил 5284 поста, включая те, что я перенёс из своих бумажных дневников (там осталось ещё три толстые тетради, но у меня пока нет времени ими заняться).

Первая заметка «электронной части» называется «Что нового в JavaScript версии 1.3 от Netscape» и рассказывает о ныне почившем браузере компании «Нетскейп» версий 4.06 и 4.5, её я упоминал выше, — она перенесена с сайта «CIT Forum».

Первая заметка на моём собственном сайте датируется 11 ноября 2000 и содержит шесть реализаций считалочки «Песня о пиве» — на языках PostgreSQL, MySQL, xSSI, CMD, PHP и JUnix shell.

Самый длинный текст на сайте (44182 байт) не мой, — это «Сказка о сисадмине и его сыне», найденная где-то в интернете в 2001-м году.

Самая популярная заметка, согласно внутренней статистике движка, который стоит у меня на сайте последние несколько лет, — «Где и когда родились ваши предки, один из способов узнать», на настоящий момент там больше 22 тысяч переходов.

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

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



Ссылка на оригинал.
кукяй

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);

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

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

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



Ссылка на оригинал.
кукяй

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

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

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

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

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



Ссылка на оригинал.
кукяй

Смежить

У программистов есть термин «смержить», от английского «to merge» — «сливать», «объединять». Обычно речь идёт о слиянии веток (различных наборов последовательностей) в репозиториях (хранилищах историй изменений) кода. Собственно команда для этого действия там называется «merge», отсюда и термин.

Мне тут подумалось, что в русском языке есть похожее слово с тем же смыслом, но отличающееся всего на одну букву — «смежить». Оно мало распространено, но всё же используется — обычно в выражении «смежить веки».

Собираюсь начать использовать его в работе, вдруг приживётся; буду считать это ещё одним своим вкладом в импортозамещение.



Ссылка на оригинал.
кукяй

Блог-марафон подходит к концу

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

Время от времени я себя проверяю запросом к базе данных, не отстал ли от ожидаемой цифры:

SELECT COUNT(*) "Факт", DAYOFYEAR(NOW()) "План"
FROM e2BlogNotes
WHERE YEAR(FROM_UNIXTIME(Stamp))=2020

На сегодняшний момент «Факт» — 276, а «План» — 268, то есть иду с опережением на восемь пунктов. Очень надеюсь продержаться до конца года, несмотря на то, что время от времени на меня накатывает желание избавиться уже от этой ноши.

Буду ли продолжать в следующем году, не знаю, не факт. У «марафона» есть и плюсы, и минусы. Думаю, в новогодние праздники надо будет внимательно присмотреться к этому опыту и уже потом что-то для себя решить.



Ссылка на оригинал.
кукяй

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

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

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



Ссылка на оригинал.
кукяй

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

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

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

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

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

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

END

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

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



Ссылка на оригинал.
кукяй

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

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



Ссылка на оригинал.