Читать в телеге. Когда-то там были посты не только от меня.
Калькулятор
Когда-то я в качестве калькулятора использовал bc
.
Но потом мне открыли глаза на очевидную истину: лучший калькулятор для командной строки — это питон.
Только недавно я узнал (а может вспомнил, просто уже давно забыл), что в интерпретаторе можно использовать _
для использования предыдущего результата:
>>> 9 + 10
19
>>> _ * 12
228
>>>
Cvc5
По совету Петра Георгиевича после z3 решил попробовать солвер cvc5.
У него есть API, который почти полностью повторяет синтаксис z3 — надо просто заменить импорт на from cvc5.pythonic import *
и убрать z3.
. (Ну или в стиле злодеев сделать import cvc5.pythonic as z3
). Однако данные из модели выковыриваются немного по-другому: вместо
model.eval(el).as_long()
надо писать
model[el].ast.getIntegerValue()
С получением результатов еще связан один очень дебильный баг (и это не только у меня руки кривые).
Несмотря на то, что cvc5 побеждает на конкурсах, на моей машине работал он ощутимо медленнее. Если z3 решил судоку за 100мс примерно (и это с учетом запуска интерпретатора), то cvc5 пыхтел аж 9,5 секунд. Решение для магического квадрата 5x5 он не нашел. А для рюкзака нет аналога Optimization
из z3
.
За разумное время последние две проблемы я решить не смог. Вообще cvc5 показался существенно менее дружелюбным к пользователю с точки зрения документации и настройки по сравнению с z3. С учетом ниши, которую я описывал ранее, z3 выглядит более адекватно.
Проверка наличия команды
Оказывается, в баше есть встроенная команда command
, которая изначально предназначена для запуска исполняемого файла, имя которого совпадает со какой-нибудь встроенной командой баша. Однако command -v
можно использовать для проверки, существует ли исполняемый файл в принципе.
Более очевидный способ — использовать which
, который еще и путь покажет, но внезапно, это не самый лучший вариант:
which
может отсутствовать в некоторых системах (особенно если система урезана под контейнер);which
на некоторых системах может не устанавливать код ошибки;which
может вызывать под капотом пакетный менеджер.
Редактирование команды в редакторе
Чтобы отредактировать и выполнить длинную команду-колбасу с помощью редактора, можно воспользоваться Ctrl+X+E.
Мне это пригодилось для длинных curl
.
В редакторе может не быть включен мягкий перенос строк, тогда пользы будет немного. В nano
это решается Esc+$ или добавлением set softwrap
в ~/.nanorc
.
Мои впечатления от солвера z3
Солвер — это инструмент, который позволяет задать систему неравенств и прочих условий, и на выходе дать решение, которое удовлетворяет этой системе. По сути, это декларативное программирование, когда нам вообще не важно, как это будет работать, главное — результат.
Я еще несколько месяцев назад наискосок просмотрел немного документации (раз и два), но к практике на основе репозитория в итоге перешёл только в поезде без интернета, поэтому первые два задания я подглядел. В остальном все решалось достаточно просто, для решения задач нужно совсем немного конструкций.
В целом сама парадигма довольно прикольная: надушить четкое ТЗ и дело в шляпе (а недостаточно душнишь — держи тривиальное решение с нулями). Какое-нибудь судоку решать с помощью него — самое то, фигачится минут за 10. Да и задача о рюкзаке была реализована мной за примерно такое же время — это не динпрог вертеть.
Однако чудес солвер тоже не открыл. Магический квадрат для 4x4 он находит мгновенно, а вот 5x5 уже не смог (я не стал ждать больше 10 минут). При этом проц и память были вообще не нагружены, т.е. по умолчанию параллелизации нет. И рюкзак для «плохих» входных данных (где стоит приближенно решать) тоже быстро не отработал.
В общем, для чего-то простого, когда может человек решить с ручкой и бумагой за обозримое время или программист тупым перебором — вполне достойный вариант. Но если известен хороший алгоритм и задача не одноразовая, то стоит посмотреть альтернативы.
Обманчивая простота API Redis
Redis можно запустить как кластер для горизонтального масштабирования. И API вроде остается почти такое же — создал клиент чуть по-другому, и используй те же самые команды. Но есть нюанс — не все команды можно просто так взять и выполнить на кластере.
Пример — scan, которая позволяет искать ключи по паттерну.
В документации ничего подозрительного не написано, в клиенте используем метод scan
, тестируем локально, все зашибись.
На стейдже будет ждать сюрприз — приложение упадает с ошибкой.
Выясняется, что из-за шардинга нормально ключи не переберешь, и надо вместо scan
писать метод, чтобы найти все ноды кластера, и для каждой из них уже выполнить scan
.
Как мне кажется, это хороший пример нарушения принципа наименьшего удивления в API. Когда есть два клиента, отличающиеся только техническими деталями, ожидаешь, что их методы с одинаковыми названиями будут работать одинаково с точки зрения основной логики. А если нельзя сделать одинаковое поведение, то методы должны называться по-разному. Увы, реальность полна разочарований.
Понятность ошибок компилятора
Забавный пост-сравнение степени подробности ошибок в разных языках. В целом совпадает с моими впечатлениями (на всем этом мне довелось что-нибудь написать). Elm тут немного перехвалили, хотя когда писал на нем, подсказка про возможные опечатки была весьма полезна. Про Rust тоже писал, что ошибки классные, когда компилятор угадал в чем ты ошибся, если нет, то все грустно.
Очень жалко, что автор не добавил в сравнение C++, но мой мем про это вы уже видели.
Передача данных — убийца производительности
Занятная статья про проблемы производительности. В основном это выжимка из доклада одного из создателей BLAS и LAPACK (на которые полагается любимый многими numpy, если кто не в курсе). Сам доклад довольно неспешный, первые минут 15 там вообще идут биографические заметки, все основное в статье изложено неплохо.
TLDR: По CPU уже намасштабировались от души, сейчас основная проблема — передача данных. Лучшие компы работают на 5% мощности из-за того, что долго ждут на передаче данных. Раньше можно было сделать 1 вычисление с плавающей точкой на 1 перемещение, сейчас — уже 10-100. При этом очень мало внимания уделяется проектированию ПО так, чтобы оно учитывало особенности железа. Сейчас вполне нормально ситуация, когда асимптотически неэффективый алгоритм будет работать в разы быстрее оптимального просто за счет того, что ему нужно меньше перемещений данных.
Обновление версий библиотек в Gradle
Попадаются задачи, когда надо обновить все версии библиотек в проекте. Руками такое делать в наш век очень уныло.
В Gradle для этого есть несколько плагинов. Основной просто покажет отчет с последними версиями для всех библиотек. Там же есть и ссылки на плагины для автоматического обновления в скрипте билда.
Небольшая проблема в том, что указать версию библиотеки можно кучей разных способов. Простейший — в build.gradle
или build.kts
. При этом там же ее можно вынести в переменную или в какую-нибудь функцию. Другой способ — каталоги версий, когда версии хранятся в отдельном файле settings.kts
или вообще в toml
. Разные плагины для автоматического обновления поддерживают разные варианты описания версий — один только в основном скрипте, второй — только в каталогах версий. Первый я попробовал на своем древнем поделии, и там все нормально обновилось (кроме npm
, конечно).
Автоматическое обновление имеет минусы. Во-первых, подсовываются иногда beta и RC версии. Во-вторых, breaking changes и вообще списки изменений надо читать самому. Но так-то в проекте все хорошо покрыто тестами и можно не париться, правда же? :)
В Gradle еще есть возможность указать диапазоны версий, например, 1.7.+
— почти как в npm
. Но тут я не могу придумать вариант, когда привязка к последней версии лучше, чем воспроизводимая сборка с фиксированными версиями.
Список измененных файлов в ветке
Если для какого-нибудь пайплайна надо определить список измененных файлов, то сама команда довольно простая:
git diff-tree --no-commit-id --name-only -r %base_commit_hash% -r %commit_hash%
В качестве %base_commit_hash%
и %commit_hash%
можно использовать как хэш коммита, так и название ветки (origin/master
, например). В CI почти наверняка эти ревизии есть в переменных окружения. В GitLab это CI_MERGE_REQUEST_DIFF_BASE_SHA
и CI_COMMIT_SHA
.
Однако стоит внимательно следить за этими значениями, если их получаете самостоятельно. Оказывается, нельзя просто так взять и найти коммит, от которого началась ветка. Есть
git merge-base master HEAD
и эта команда будет неплохо работать в плюс-минус обычных сценариях. Но будет давать неверный результат, если master
подмерживается в фиче-ветку.