Advent of Code 2021

Первое задание Advent of Code 2021 откроется первого декабря.

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

Борьба с приватностью

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

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

Что-то я не слышу возгласов из «либеральной» тусовки про то, что Европа с Австралией — тоталитарные помойки. Или это другое?

Fzf-Lsp

Если вы пользуетесь Neovim в Windows, сразу можно готовиться переписывать плагины за другими.

Не успел я объяснить автору nvim-fzf как работают named pipes в Windows, чтобы он наконец сделал нормальную поддержку этой ОС, пришлось чинить fzf-lsp. Последний использует «стандартный» плагин fzf.vim, написанный самим автором fzf, для показа сообщений из встроенного в Neovim LSP.

Конечно же, написать кроссплатформенно автор не может, у него же нет Windows (непреодолимое препятствие в наши дни!). Дёргать preview.sh, который на моей машине дёргает bash из дистибутива Git и открывает всплывающее чёрное окно для fzf preview — вот кроссплатформенность для разработчиков в наши дни.

Вот как это фиксится.

diff --git a/lua/fzf_lsp.lua b/lua/fzf_lsp.lua
index 79c0365..09c6fe0 100644
--- a/lua/fzf_lsp.lua
+++ b/lua/fzf_lsp.lua
@@ -6,7 +6,7 @@ local M = {}
local __file = debug.getinfo(1, "S").source:match("@(.*)$")
assert(__file ~= nil)
local bin_dir = fn.fnamemodify(__file, ":p:h:h") .. "/bin"
-local bin = { preview = (bin_dir .. "/preview.sh") }
+local bin = { preview = (bin_dir .. '/' .. vim.env.FZF_PREVIEW_COMMAND) }
-- }}}

-- utility functions {{{
@@ -325,8 +325,8 @@ end

local function fzf_locations(bang, prompt, header, source, infile)
   local preview_cmd = (infile and
-    (bin.preview .. " " .. fn.expand("%") .. ":{}") or
-    (bin.preview .. " {}")
+    ('for /f "delims=: tokens=1" %L in ({}) do @(IF %L GEQ 10 (set /a "pre=%L-10">nul) ELSE (set "pre=0"))&&@cmd /a /q /s /v:on /c "' .. bin.preview .. ' --line-range %pre%: --highlight-line %L ' .. fn.expand("%") .. '"')  or
+    ('for /f "delims=: tokens=1,2" %L in ({}) do @(IF %M GEQ 10 (set /a "pre=%M-10">nul) ELSE (set "pre=0"))&&@cmd /a /q /s /v:on /c "' .. bin.preview .. ' --line-range %pre%: --highlight-line %M %L"')
   )
   local options = {
   "--prompt", prompt .. ">",

Одной строкой cmd мы делим строки из диагностики на куски (разделитель: :), и берём первый из них (номер строки) для показа диагностики для текущего буфера, и два (имя файла и номер строки) — для диагностики во всех буферах сразу (это :Diagnostics и :DiagnosticsAll в fzf-lsp). Полученное скармливаем в качестве параметров программе предварительного просмотра, bat. Выводим диапазон -10 от текущей строки, подсвечиваем текущую строку.

Я в курсе, что автор ничего не должен никому. Можно даже сказать, что FOSS расшифровывается как «Fuck Off, [we're writing] Shitty Software». Поэтому придётся поддерживать собственные форки как для fzf.vim, так и для fzf-lsp.

Cozette

Для тех, кто проводит много времени в терминале, немалое значение имеет шрифт. Большой удобно читать, но информации на экране поместится меньше. Мелкие шрифты практически все страдают плохой читабельностью, если используется ClearType (я, как обычно, пишу о Windows). Несколько улучшают ситуацию шрифты растровые — если в эмуляторе терминала можно отключить ClearType, то шрифт будет невероятно резким, поскольку будет отображаться в соответствии с пиксельной сеткой монитора.

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

Автор поставил себе амбициозную задачу: сделать растровый шрифт с хорошей поддержкой Unicode. Что ещё интереснее, эту поддержку он планировал реализовать на площадке глифа размером 6×13 пикселей. Когда я увидел это впервые, я был приятно удивлён, автор проделал отличную работу!

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

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

/files/images/cozettefont.png

А пока он не принял PR и не собрал шрифт, можно использовать мою сборку.

Vim Quoted-Printable Decode

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

Экспорт книжки с самсунговского телефона возможен (правда, выбор способов ограничен, и отправить книжку по электронной почте, например, не получилось; к счастью, я могу передать файл через Bluetooth на рабочую машину). Но вот незадача — весь не-латинский текст закодирован в Quoted-Printable (см. раздел 6.7 RFC 2045).

По сути, кодируется последовательность байт, что позволяет кодировать и UTF-8, и UTF-32, просто будет два литерала (=FF=FF) или четыре литерала соответственно.

Кодирование это видимо вызвано тем, что телефонная книжка экспортирует контакты в формате vCard 2.1.

К своему разочарованию, я обнаружил, что подходящих декодеров нет. Идея-то была в том, чтобы натравить декодер либо на телефонную книжку целиком, либо воспользоваться командой в Vim/Neovim [range]g/regex/command, то есть выполнить команду command на тех строках, в которых встречается регулярное выражение regex.

Писать на Rust или Go такую утилиту я поленился. Помогли китайцы.

Я бы сказал, что китайское присутствие чувствуется даже на GitHub, и оно часто полезно; к примеру, лаунчер Wox, которым я пользуюсь каждый день — китайского авторства. Автор проект забросил, но я собрал последнюю версию самостоятельно и пользуюсь с большим удовольствием уже давно. Он по крайней мере работает. Новый проект, взявший за основу код Wox, после установки падает сразу же, да и работает в принципе значительно хуже.

Так и тут: нашлось несколько строк, которые позволяют декодировать Quoted-Printable в соответствии со стандартом. Плагин к Notepad++ не в состоянии сделать даже этого (к примеру, CRLF должен быть представлен в виде закодированной последовательности, но авторы плагинов к Notepad++ стандарт не читали).

Пришлось чуть освежить в памяти С++:

#include <iostream>
#include <string>
#include "QuotedPrintable.hpp"

int main()
{
   for (std::string line; std::getline(std::cin, line); )
   {
      std::string qstring;
      for (int i = 0; i < line.length(); i++) {
         if (strchr("=", line[i]) != NULL) {
            if (strchr("0123456789ABCDEF", line[i+1]) != NULL) {
               if (strchr("0123456789ABCDEF", line[i+2]) != NULL) {
                  qstring += line[i];
                  qstring += line[i+1];
                  qstring += line[i+2];
                  i += 2;
               }
               else { std::cout << line[i]; }
            }
            else { std::cout << line[i]; }
         }
         else {
            std::cout << QuotedPrintable::decode(qstring);
            qstring = "";
            std::cout << line[i];
         }
      }
      std::cout << QuotedPrintable::decode(qstring) << std::endl;
   }
}

Колхозно? О да. Работает? Более чем.

Программа считывает произвольное количество строк с STDIN и возвращает тот же текст, но с раскодированным Quoted-Printable. Командой %g/\v(%u003d[A-F0-9]{2}){2,}/.!qpdecode/ находим все строки, в которых есть Quoted-Printable, и передаём их по одной в qpdecode. На выходе получаем нормальный UTF-8.

多谢, xuzheyang!

Отключение обновлений Firefox

На Hacker News за последние несколько недель появлялись публикации, посвященные Firefox. Общее впечатление у всех такое, что Mozilla намеренно делает всё, чтобы уничтожить даже ту постоянно уменьшающуюся пользовательскую аудиторию, которая у неё ещё осталось. Принудительное обновление интерфейса до Photon UI меня, сказать по правде, взбесило, и я начал искать способы обеспечить стабильность браузера.

Собственно, я уже давно посвящаю много времени обеспечению стабильности рабочего компьютера (взять хотя бы Rollback RX). Но Firefox очень надоел со своими предложениями обновиться, и с этим надо бороться.

Для начала откатился на v88, где этого гнусного Photon UI нет.

Помешать Firefox задалбывать вас обновлениями можно, как выяснилось, только одним способом — через настройку групповых политик (я понимаю логику Mozilla, это попытка сделать Firefox корпоративным браузером). Это можно сделать по-разному: если речь только об обновлениях, достаточно установить параметр REG_DWORD DisableAppUpdate равным 1 по адресу HKEY_CURRENT_USER\Software\Policies\Mozilla\Firefox.

Второй вариант интереснее с точки зрения выбора опций для отключения:

  • по адресу https://github.com/mozilla/policy-templates из раздела релизов загружаем файл групповых политик для Firefox нужной версии;

  • загружаем удобный маленький редактор групповых политик Policy Plus по адресу https://github.com/Fleex255/PolicyPlus;

  • распаковываем архив групповых политик, загружаем в Policy Plus файлы mozilla.admx и firefox.admx (в этой последовательности) и ищем «Отключить обновления» прямо в корне дерева политик Firefox;

  • по желанию изучаем все остальные настройки групповых политик для Firefox;

  • обязательно нажимаем <CTRL>+<S> для сохранения изменений.

Advent of Code 2020 D13

День 13 занял у того же товарища, что решает AoC на Zig, чуть более трёх часов (из которых два с половиной часа он решал вторую часть). Я, думая что могу решить быстрее, потратил на попытки сделать это два дня :( В итоге всё равно получается решение «в лоб» и грубой силой.

Часть 1

Приведу текст задачи (часть 1) полностью.

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

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

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

Время, которое занимает этот круг, является номером (ID) автобуса: автобус с номером 5 отправляется из морского порта в моменты 0, 5, 10, 15 и так далее. Автобус с номером 11 отправляется в 0, 11, 22, 33 и так далее. Если вы будете там к моменту отправления автобуса, сможете на нём уехать в аэропорт.

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

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

К примеру, заметки у вас следующие:

939
7,13,x,x,59,x,31,19

Самая ранняя метка времени, в которую вы сможете уехать — 939, а автобусы на линии — 7, 13, 59, 31 и 19. Рядом с меткой 939, эти автобусы отправляются во время, отмеченное буквой D.

time   bus 7   bus 13  bus 59  bus 31  bus 19
929      .       .       .       .       .
930      .       .       .       D       .
931      D       .       .       .       D
932      .       .       .       .       .
933      .       .       .       .       .
934      .       .       .       .       .
935      .       .       .       .       .
936      .       D       .       .       .
937      .       .       .       .       .
938      D       .       .       .       .
939      .       .       .       .       .
940      .       .       .       .       .
941      .       .       .       .       .
942      .       .       .       .       .
943      .       .       .       .       .
944      .       .       D       .       .
945      D       .       .       .       .
946      .       .       .       .       .
947      .       .       .       .       .
948      .       .       .       .       .
949      .       D       .       .       .

Самый ранний автобус, на которым вы сможете уехать — 59. Он не уедет ранее метки 944, так что потребуется подождать 944 − 939 = 5 минут до отправления. Умножение номера автобуса на число минут ожидания даст 295.

Каково число: номер самого раннего автобуса, на котором вы сможете уехать в аэропорт, умноженный на число минут ожидания этого автобуса?

Входные данные:

1003055
37,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,41,x,x,x,x,x,x,x,x,x,433,x,x,x,x,x,x,x,23,x,x,x,x,x,x,x,x,17,x,19,x,x,x,x,x,x,x,x,x,29,x,593,x,x,x,x,x,x,x,x,x,x,x,x,13

Решение, конечно, элементарное: поскольку все автобусы отправляются с заданной периодичностью, равной числу в списке, нужно найти число, у которого разница между самим числом (номером автобуса) и остатком от деления начального времени на этот номер минимальна. То есть, искомое минимальное время равно ID - @mod(Timestamp, ID), где @mod возвращает остаток от деления.

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

while (it.next()) |b| {
   if (std.mem.eql(u8, b, "x")) continue;
   var n = std.fmt.parseInt(usize, b, 10) catch 999999;
   var wait_time = n - @mod(start_time, n);
   if ( wait_time < minutes_wait ) {
      bus_number = n;
      minutes_wait = wait_time;
   }
}

Переходим ко второй части, которая заняла два с половиной часа у стримера.

Часть 2

Компания-перевозчик объявила конкурс: золотую монету тому, кто найдёт самую раннюю отметку времени, чтобы первый автобус отправлялся в это время, и чтобы последующие автобусы отправлялись в соответствующую минуту. (Первая строка исходных данных больше не важна).

К примеру, у вас тот же список автобусов, что и ранее:

7,13,x,x,59,x,31,19

Символ x означает, что ограничений на то, какой автобус отправится, в этом поле нет.

Это значит, что вы ищете самую раннюю метку времени (t), чтобы:

  • Автобус 7 отправлялся во время t.

  • Автобус 13 отправлялся на минуту позже t.

  • На вторую и третью минуты после t ограничения не накладываются.

  • Автобус 59 отправляется на 4 минуты позже t.

  • На пятую минуту после t ограничения не накладываются.

  • Автобус 31 отправляется через шесть минут после t.

  • Автобус 19 отправляется через семь минут после t.

Важны только те автобусы, которые указаны на соответствующих местах (смещениях) после t. Эти автобусы могут отправляться в другое время, и другие автобусы могут отправиться в это время. К примеру, в списке ниже, поскольку автобус 19 должен отправиться через семь минут после отметки времени, в которую отправится 7, автобус 7 также будет отправляться одновременно с автобусом 19 после t.

В этом примере наиболее ранняя отметка времени, в которую это произойдёт, 1068781:

time     bus 7   bus 13  bus 59  bus 31  bus 19
1068773    .       .       .       .       .
1068774    D       .       .       .       .
1068775    .       .       .       .       .
1068776    .       .       .       .       .
1068777    .       .       .       .       .
1068778    .       .       .       .       .
1068779    .       .       .       .       .
1068780    .       .       .       .       .
1068781    D       .       .       .       .   <<<
1068782    .       D       .       .       .
1068783    .       .       .       .       .
1068784    .       .       .       .       .
1068785    .       .       D       .       .
1068786    .       .       .       .       .
1068787    .       .       .       D       .
1068788    D       .       .       .       D
1068789    .       .       .       .       .
1068790    .       .       .       .       .
1068791    .       .       .       .       .
1068792    .       .       .       .       .
1068793    .       .       .       .       .
1068794    .       .       .       .       .
1068795    D       D       .       .       .
1068796    .       .       .       .       .
1068797    .       .       .       .       .

В примере выше, автобус 7 отправится во время 1068788 (через 7 минут после t). Это нормально; единственное требование в том, что 19 должен отправиться в это же время, что и происходит.

Другие примеры:

  • Наиболее ранняя метка времени для 17,x,13,19 — 3417.

  • 67,7,59,61 происходит во время 754018.

  • 67,x,7,59,61 происходит во время 779210.

  • 67,7,x,59,61 происходит во время 1261476.

  • 1789,37,47,1889 происходит во время 1202161486.

Однако, с тем количеством автобусов в вашем списке, самая ранняя метка времени будет больше 100000000000000!

Какая самая ранняя метка времени, в которую все указанные автобусы отправятся в соответствии со своими смещениями в списке?

Исходные данные не изменились.

Я рассуждал так: поскольку первый автобус должен отправиться во время, кратное его номеру (как и все остальные автобусы), то

  • получаем ближайшее кратное первому автобусу и максимально близкое к числу-подсказке 100000000000000, и далее

  • последовательно увеличиваем это кратное на номер этого первого автобуса (проход по кратным значениям) (x);

  • проверяем, кратно ли число x + номер позиции в списке автобусов номеру автобуса на этой позиции.

Если первый автобус отправляется во время t, то следующий автобус по индексу i должен отправиться во время t + i, а число t + i кратно этому номеру автобуса.

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

Единственный вариант оптимизации, который мне доступен: проход от наиболее редких чисел к наиболее частым: сначала ищем число, кратное наибольшему номеру автобуса (таких чисел будет меньше остальных), и уже для этих чисел проверяем соответствие условиям.

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

// от наибольшего номера автобуса к наименьшему
for (list.items[1..]) |item| {
   // текущее число, кратное наибольшему номеру автобуса, уменьшаем на смещение этого наибольшего номера и прибавляем смещение следующего (в сторону уменьшения) номера автобуса; это число кратно следующему по величине номеру автобуса
   var current = @mod(value - list.items[0].index + item.index, item.bus) == 0;
   success = success and current;
   if (!success) break; // если остаток от деления [числа минус смещение плюс смещение нового] не равен нулю, переходим к следующему
}

За пять часов алгоритм проверил числа от 100000000000000 до ≈414148637009525.

Автобусы из исходных данных уезжают…

  • № 37 первым;

  • № 41 через 27 минут;

  • № 433 через 37 минут;

  • № 23 через 45 минут;

  • № 17 через 54 минуты;

  • № 19 через 56 минут;

  • № 29 через 66 минут;

  • № 593 через 68 минут;

  • № 13 через 81 минуту.

После ещё нескольких часов подсчётов я получил число 600691418730595, которое:

  • делится нацело на 37: 600691418730595 % 37 == 0;

  • (600691418730595 + 27) % 41 == 0;

  • (600691418730595 + 37) % 433 == 0;

  • (600691418730595 + 45) % 23 == 0;

  • (600691418730595 + 54) % 17 == 0;

  • (600691418730595 + 56) % 19 == 0;

  • (600691418730595 + 66) % 29 == 0;

  • (600691418730595 + 68) % 593 == 0;

  • (600691418730595 + 81) % 13 == 0.

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

Дополнение

Решение задачи — в китайской теореме об остатках.

Как остаться без телефона

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

Сегодня я заметил, что телефон не реагирует на кнопку «убавить громкость» (нижняя часть качельки громкости). Я решил, что это может быть программным багом, и перезагрузил его, что происходит очень редко. Однако, оказалось, что телефон считает, что кнопка убавления громкости нажата постоянно! Если включить телефон Samsung, удерживая нажатой кнопку убавления громкости, то телефон будет загружен в «безопасном режиме». Безопасный режим позволяет загружать пользовательские настройки, включая аккаунт, но не загружает и не позволяет запускать какие-либо приложения, установленные в пользовательской части системы.

Для несведущих напомню, что в OC Android, как основанной на Linux, есть системная часть, и часть пользовательская. Правильнее было бы сказать, что есть пользовательский раздел жёсткого диска, который как правило шифруется во избежание доступа к нему посторонних лиц. Соответственно, при загрузке в безопасном режиме пользовательский раздел монтируется с ограничениями — получаем практически чистый телефон (настройки которого, скорее всего, не сохранятся, поскольку сохранять их некуда, сохранение в системную область недопустимо, а пользовательский раздел похоже что недоступен на запись).

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

Купить новый и перенести содержимое телефона с одного на другой мне пока крайне затруднительно, поскольку Samsung Sync (или как там зовётся приложение для миграции данных между телефонами) загрузить невозможно, копировать из безопасного режима нечего, а выяснять возможности ADB и других способов подключения в присутствии такого аппаратного дефекта нет ни времени, ни сил.

Приёмщик сказал что такое очень редко, но бывает. Этот телефон не проработал и трёх лет.

Advent of Code 2020 D11 и язык Zig

Advent of Code — конкурс для программистов, о котором я уже писал ранее. К нему я возвращаюсь нерегулярно, но в этот раз совпало два момента.

Первый: язык Zig (но изучать мне показалось удобнее здесь). Этот язык, издалека и с большим прищуром похожий на Rust, на самом деле позиционирует себя как улучшенная версия C. Мне он понравился, несмотря на то, что я довольно быстро добился падения компилятора. Думаю, если в него добавить несколько современных функций и доработать, он со временем будет весьма успешен.

Второй: стрим на YouTube с решениями Advent of Code 2020 на языке Zig. Не буду упоминать канал, но его несложно найти; в реальности на YouTube едва ли наберётся десять каналов, где Zig упоминается.

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

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

Тут легко узнаётся игра в «жизнь», клеточные автоматы.

Исходные данные представлены в виде текста, разделенного на строки (длиной 10 для тестовых данных, 95 для реальных). В тексте L обозначает свободное место, # обозначает занятое место, а . обозначает проход, который никогда не будет занят.

Раунды заканчиваются тогда, когда состояние поля становится стабильным, то есть состояние клеток не меняется.

Учитывая, что Zig поддерживает опциональные типы (!) — хорошая замена C! — мне кажется более чем логичным представить эту доску в виде ?bool, то есть одного из трёх состояний: true, false, null.

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

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

Служебные функции:

  • функция равенства, сравнивающая два массива ?bool;

  • функция печати массива в нужном виде (для дебага);

  • функция, возвращающая количество занятых мест (это ответ для задачки).

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

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

  • состояние текущей клетки;

  • состояние соседних клеток;

  • ряд и колонку текущей клетки через деление нацело и остаток от деления (они удобнее в расчётах).

Дальше очевидное: если текущая клетка расположена во второй колонке, то можно посмотреть состояние соседней слева (потому что она существует всегда). Если клетка расположена в предпоследней строке в предпоследней колонке, то для неё точно существует сосед справа снизу… логика понятна.

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

Язык порекомендую любопытствующим.

Luarocks в Windows

Всё началось с Moonscript и Haxe: мне захотелось поразвлечься с этими новыми для меня языками. Оба позволяют транспилировать свои программы в Lua, а Lua, на мой взгляд, это один из наиболее актуальных языков, поскольку много какое используемое мною ПО позволяет на нём писать внутренние скрипты.

В Lua .hx (Haxe) транспилировался, но выполняться отказался, требуя модуль lua-utf8. Его необходимо было поставить через luarocks.

На мой взгляд, luarocks документирован плохо и совершенно не очевидно, как оно работает. А работает он так.

luarocks при запуске ищет в директориях по умолчанию (c:\users\user\appdata\luarocks) конфигурационный файл, имя которого зависит от версии Lua. В моём случае это config-5.3.lua.

В этом файле много чего требуется написать. У меня конфиг сейчас выглядит вот так:

arch = "win32-x86_64"
cmake_generator = "MinGW Makefiles"
deploy_bin_dir = "c:/users/user/.luarocks/bin"
deploy_lib_dir = "c:/users/user/.luarocks/lib/lua/5.3"
deploy_lua_dir = "c:/users/user/.luarocks/share/lua/5.3"
export_path_separator = ";"
external_deps_dirs = {
   "c:/mingw",
   "c:/windows/SysWOW64",
   "c:/windows/System32"
}
external_deps_patterns = {
   bin = {
      "?.exe",
      "?.bat"
   },
   include = {
      "?.h"
   },
   lib = {
      "lib?.dll.a",
      "?.dll.a",
      "lib?.a",
      "cyg?.dll",
      "lib?.dll",
      "?.dll",
      "?.lib"
   }
}
external_deps_subdirs = {
   bin = "bin",
   include = "include",
   lib = {
      "",
      "lib",
      "bin"
   }
}
external_lib_extension = "dll"
home = "C:\\Users\\user\\AppData\\Roaming"
home_tree = "C:\\Users\\user\\AppData\\Roaming/luarocks"
homeconfdir = "C:\\Users\\user\\AppData\\Roaming/luarocks"
lib_extension = "dll"
link_lua_explicitly = true
local_by_default = false
local_cache = "C:\\Users\\user\\AppData\\Local/LuaRocks/Cache"
lua_extension = "lua"
lua_interpreter = "lua.exe"
lua_version = "5.3"
makefile = "Makefile"
no_manifest = false
obj_extension = "o"
processor = "x86_64"
rocks_trees = {
   {
      name = "user",
      root = "C:\\Users\\user\\AppData\\Roaming/luarocks"
   }
}
rocks_dir = "c:/users/user/.luarocks/lib/luarocks/rocks-5.3"
rocks_servers = {
   {
      "https://luarocks.org",
      "https://raw.githubusercontent.com/rocks-moonscript-org/moonrocks-mirror/master/",
      "http://luafr.org/moonrocks/",
      "http://luarocks.logiceditor.com/rocks"
   }
}
runtime_external_deps_patterns = {
   bin = {
      "?.exe",
      "?.bat"
   },
   include = {
      "?.h"
   },
   lib = {
      "?.dll",
      "cyg?.dll",
      "lib?.dll"
   }
}
runtime_external_deps_subdirs = {
   bin = "bin",
   include = "include",
   lib = {
      "",
      "lib",
      "bin"
   }
}
static_lib_extension = "a"
target_cpu = "x86_64"
variables = {
   AR = "C:\\MinGW\\bin\\ar.exe",
   CC = "C:\\MinGW\\bin\\gcc.exe",
   CFLAGS = "-O2",
   CMAKE = "cmake",
   LD = "C:\\MinGW\\bin\\gcc.exe",
   LIBFLAG = "-shared",
   LIB_EXTENSION = "dll",
   LUALIB = "lua53.dll",
   LUA_LIBDIR_FILE = "lua53.dll",
   LUA_DIR = "C:/ProgramData/chocolatey/lib/lua53/tools",
   LUA_BINDIR = "C:/ProgramData/chocolatey/lib/lua53/tools",
   LUA_INCDIR = "C:/Lua/include",
   LUA_LIBDIR = "C:/Lua/lib",
   MSVCRT = "msvcrt",
   ROCKS_TREE = "c:/users/user/.luarocks/lib/luarocks/rocks-5.3"
}
verbose = true

При таких настройках библиотеки нормально собираются (за исключением тех, которые имеют типично линуксовые зависимости) в dll и кладутся в папку C:\Users\user\AppData\Roaming\LuaRocks\lib\lua\5.3 (см. deploy_lib_dir).

Lua 5.3 — x64; его можно установить при помощи Chocolatey, а можно просто взять бинарники на SourceForge, взяв заодно и lua-5.3.6_win64_dllw6_lib.zip. MinGW x64 можно взять на nuwen.net и распаковать его в C:\MinGW.

После этого создадим C:\Lua\includes и положим туда файлы заголовков из lua-5.3.6_win64_dllw6_lib.zip, а .dll и .a положим в C:\Lua\lib.

Необходимо добавить к окружению переменную LUA_CPATH=C:\Users\user\AppData\Roaming\LuaRocks\lib\lua\5.3\?.dll, которая означает, что Lua будет искать C-библиотеки в этой папке с именем библиотека.dll. Если собрать luafilesystem (luarocks install luafilesystem), то в этой папке появится lfs.dll. Эту библиотеку можно подгрузить к интерпретатору командой lua -l lfs, и если всё нормально, то интерпретатор загрузится без сообщений.

Для точности хорошо бы ещё добавить к LUA_CPATH строки C:\Users\user\AppData\Roaming\LuaRocks\lib\lua\5.3\?\core.dll и .\?.dll.

На будущее сразу стоит добавить переменную окружения LUA_PATH: LUA_PATH=C:\Users\user\AppData\Roaming\LuaRocks\share\lua\5.3\?.lua;C:\Users\user\AppData\Roaming\LuaRocks\share\lua\5.3\?\init.lua.

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

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

Командой luarocks install luautf8 скачиваем и собираем библиотеку. После того, как она установится, можно попробовать запустить интерпретатор, набрав в строке lua -l lua-utf8.

Теперь Lua-код, транспилированный из Haxe, исполняется и показывает юникод (проверено).

Значимая мелочь

Сатьяджит Сат в книге «Трейдеры, пушки и деньги» рассказывает историю о том, как прогорели азиатские производители лапши:

Трейдеры заключили новую сделку с ОСМ, достигнув новых вершин креативности. По условиям этой новой трансакции, прежняя невыгодная для ОСМ сделка была отменена, и компания не понесла за это убытков. Ее заменил новый своп. Сумма новой трансакции составила 600 миллионов долларов. По условиям нового свопа компания ОСМ обязывалась следующие три года выплачивать фиксированную сумму в долларах. Эта сумма была 4 миллиона долларов в месяц. Взамен дилер будет выплачивать ОСМ сумму, рассчитываемую по сложной формуле:

Максимум от [0; NP × {7 × [(LIBOR² × 1/LIBOR) − (LIBOR⁴ × LIBOR⁻³)]} × количество дней в месяце/360],

где NP — 600 миллионов долларов; LIBOR — шестимесячная ставка LIBOR в долларах

Финансовая механика была просто ослепительной, за исключением одной проблемы. Сложная формула, приведенная выше, если вы проделаете вычисления, всегда окажется равной нулю. Дилер никогда ничего не будет платить ОСМ, а вот ОСМ должна будет выплачивать дилеру по 4 миллиона долларов в месяц в течение трех лет. Это и был нужный дилеру результат.

В компании ОСМ были финансисты. Или «финансисты»?

В книге «Countdown to Zero Day» (книга посвящена Stuxnet, первой боевой малвари, нацеленной на противодействие иранской ядерной программе), её автор Зеттер (Zetter) рассказывает следующее. Некто Хан (Khan), работавший ранее в Urenco, украл и продал Пакистану, Ливии и Ирану чертежи центрифуг, которые, работая в каскаде, позволяют осуществлять обогащение урана, а также сами центрифуги. Где-то в 2000-е ЦРУ внедрило своих агентов в цепочки этих поставок и поставляла в эти страны модифицированные компоненты, которые выходили из строя, не вызывая подозрений. В частности, были модифицированы вакуумные помпы, которые должны были ломаться через случайные промежутки времени и таким образом, чтобы было трудно установить источник проблемы и выявить общность этих поломок.

Из всех модифицированных ЦРУ помп шесть ушли в Ливию, а седьмая приехала в Иран. Инспекторы МАГАТЭ, осуществлявшие надзор за иранской ядерной программой, наткнулись на последнюю в городе Натанз; иранцы не знали, что помпа была изменена. Помпа выделялась на фоне остальных одной мелочью: на ней была наклейка, указывающая на неё как на собственность американской Национальной лаборатории Лос Аламос, где эту помпу модифицировали. В ходе расследования МАГАТЭ выяснилось, что серийный номер на помпе следует за номерами тех, что были в Ливии, что указывает на то, что эти помпы из одной партии. Инспекторы отследили заказ на помпы до американской лаборатории. Никто так и не понял, как наклейка попала на модифицированную помпу в Натанзе, и почему иранцы ничего не заподозрили и не обратили на неё никакого внимания.

Как по мне, так это примеры легендарного восточного распиздяйства.

Visual Studio Nuget.org SSL/TLS error

Некоторое время Visual Studio стояла у меня без дела, но когда она мне понадобилась, выяснилось, что доступ к репозиторию пакетов недоступен с ошибкой SSL/TLS error приблизительно следующего содержания:

Unable to load the service index for source https://api.nuget.org/v3/index.json.
An error occurred while sending the request.
The request was aborted: Could not create SSL/TLS secure channel.

Доступ к указанному URL в Firefox работает.

Эта проблема возникла не в 2020 году: об этом написано в developercommunity.visualstudio.com, на SO и на GitHub. Как это часто бывает, всё обсуждавшееся тогда не имеет отношения к сегодняшней проблеме.

Основное решение:

New-Item 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.3\Server' -Force | Out-Null
New-ItemProperty -path 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.3\Server' -name 'Enabled' -value 0 -PropertyType 'DWord' -Force | Out-Null
New-ItemProperty -path 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.3\Server' -name 'DisabledByDefault' -value 1 -PropertyType 'DWord' -Force | Out-Null
New-Item 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.3\Client' -Force | Out-Null
New-ItemProperty -path 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.3\Client' -name 'Enabled' -value 0 -PropertyType 'DWord' -Force | Out-Null
New-ItemProperty -path 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.3\Client' -name 'DisabledByDefault' -value 1 -PropertyType 'DWord' -Force | Out-Null
Write-Host 'TLS 1.3 has been disabled.'

New-Item 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.2\Server' -Force | Out-Null
New-ItemProperty -path 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.2\Server' -name 'Enabled' -value 1 -PropertyType 'DWord' -Force | Out-Null
New-ItemProperty -path 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.2\Server' -name 'DisabledByDefault' -value 0 -PropertyType 'DWord' -Force | Out-Null
New-Item 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.2\Client' -Force | Out-Null
New-ItemProperty -path 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.2\Client' -name 'Enabled' -value 1 -PropertyType 'DWord' -Force | Out-Null
New-ItemProperty -path 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.2\Client' -name 'DisabledByDefault' -value 0 -PropertyType 'DWord' -Force | Out-Null
Write-Host 'TLS 1.2 has been enabled.'

Это тоже не помогло.

После того как я исполнил все ритуальные пляски (прописав в реестре отключение TLS 1.3 и включение 1.2, отключив TLS 1.1 в настройках IE) и оставшись на том же месте, я начал искать другое. Проверка (переход по ссылке https://www.ssllabs.com/ssltest/viewMyClient.html во внутреннем браузере VS) показала, что TLS 1.2 работает, а всё ненужное — не работает, так что проблема не в этом.

Проблема — в сертификате nuget.org. Поводом к такой мысли стала статья, где сообщалось об издыхании сертификата Microsoft и обещалось вскоре сообщить о новом сертификате для сайта nuget.org. На сайте nuget.org я такого сообщения не нашёл, но всё равно заподозрил, что сертификат более невалидный. Значит, необходимо просто взять актуальный, и загрузить его в системный реестр сертификатов.

В FF это просто: переходим на сайт, открываем свойства, выгружаем сертификат, загружаем его в реестр. В картинках это выглядит так:

/images/2020-12-19_174256.png

Нажать кнопку >.

/images/2020-12-19_174338.png

Нажать кнопку Подробнее. Откроется окно Информация о странице.

/images/2020-12-19_174401.png

Нажать на кнопку Просмотреть сертификат. В FF откроется окно просмотра сертификата.

/images/2020-12-19_174426.png

Прокрутить окно вниз до раздела Разное.

/images/2020-12-19_174444.png

Нажать на ссылку PEM (сертификат) и сохранить его.

/images/2020-12-19_174542.png

Открыть консоль сертификатов, и импортировать сохраненный сертификат в Сертификаты (локальный компьютер) > Доверенные лица > Реестр > Сертификаты.

Одно не ясно мне — почему новая установка Visual Studio в виртуальной машине не имела таких проблем и работала нормально?

FOSS

Отличительная особенность FOSS (Free Open Source Software) — часто его необходимо допиливать собственными руками. Если не готовы это делать, часто лучше не браться вообще, потому что такое допиливание исходника (с последующим pull request) предполагается.

К примеру, Neovim. Досадный баг, из-за которого установленные маркеры сохраняются, но не удаляются, до версии 0.5.0 дошёл без изменений и, насколько я могу судить, изменений в этом отношении пока не предвидится.

fzf.vim, мощнейший инструмент динамической фильтрации текста для Vim / Neovim, до сих пор хранит несколько досадных недоработок. Одна из полезнейших его опций: :Lines, фильтрация по строкам ВСЕХ одновременно открытых файлов; результаты нескольких выборок могут быть помечены маркером и отправлены в Quickfix List для дальнейшей проработки. Тут стоит напомнить, что содержимое Quickfix List можно сохранить под любым именем и восстановить в любое время, а также присоединить к любому другому такому списку. Отметил места для проработки, сохранил список на диск и в любое время вернулся к этой работе.

Логично внедрить тот же функционал в :BLines, поскольку эта команда делает то же самое, но только для текущего буфера. Тем не менее, это не реализовано; пришлось сделать самому, написав десять коротких строк. Сделал в fzf.vim опцию, открывающую окно Quickfix List на всю ширину экрана, а не внизу крайнего правого окна, как это сделано по умолчанию. Также пришлось дописать функцию :Registers, которая позволяет вставлять сразу несколько регистров одним нажатием.

riv, отличный плагин для Vim / Neovim для работы с reStructuredText, хорош многим, но немного страдает качество синтаксической подсветки текста, которая не охватывает всего синтаксиса языка. Вдобавок почему-то это подсветка написана так, что проверка правописания внутри директив отключена. Из-за этого в документацию пробралось несколько досадных опечаток. Проект фактически заброшен, а значит придётся писать подсветку и всю необходимую атрибутику самому. Только попытавшись повторить, понимаешь, сколько работы вложено в казалось бы такую малозаметную вещь, как подсветка.

Конец американской эпохи

Для наблюдательного человека давно не секрет, что эпоха американского доминирования закончилась. Как уже говорилось, ничего хорошего это не сулит. Мне показалась неплохой статья «Развенчание Америки», написанная Уэйдом Дэвисом.

Ниже — её русский перевод.

Развенчание Америки

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

За четверть года цивилизация была поставлена на паузу микроскопическим паразитом в 10 тысяч раз меньше крупинки соли. COVID-19 атакует не только наши тела, но и культурные основы нашей жизни, инструменты сообществ и коммуникаций, что для человека то же, что клыки и когти для тигра.

Наши действия до сего дня в основном были сосредоточены на снижении скорости распространения и сжатии кривой смертности. Лекарства нет, и нет уверенности в появлении вакцины в ближайшем будущем. Быстрее всего вакцина была разработана от свинки. Это заняло четыре года. COVID-19 убил 100 000 американцев за четыре месяца. Есть некоторые свидетельства, намекающие, что заражение не подразумевает иммунитета, что позволяет задаться вопросом, насколько эффективной будет вакцина, даже если предположить что она будет найдена. И она должна быть безопасной. Если будет иммунизироваться всё население планеты, то смертельные осложнения у одного на тысячу будут означать смерть миллионов.

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

Эпидемию COVID-19 запомнят как именно такой момент в истории, примечательное событие, чья значимость раскроется только на исходе кризиса. Она отметит эту эпоху, равно как убийство графа Фердинанда в 1914-м, падение фондового рынка в 1929-м и восхождение Адольфа Гитлера в 1933-м стали вехами прошлого столетия, предвестниками великих последующих перемен.

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

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

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

Читать статью …

Дело Кобринского и сексуальная утопия в действии

Американские развлечения с обвинениями «после секса» докатились и до нас. Теперь вот обвиняют некоего профессора Кобринского. Цитаты из статьи прекрасны:

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

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

В общем, девочке не понравилось. Но ведь если не понравилось, значит она этого и не захотела бы, а значит это против её воли, а значит это изнасилование!

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

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


СЕКСУАЛЬНАЯ УТОПИЯ В ДЕЙСТВИИ

Роджер Девлин

The Occidental Quaterly, vol.6, no.2, summer 2006

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

Я думаю, что затертое клише «сексуальной революции» следует воспринимать с более чем обыденной серьёзностью. Как и Французская революция, типичная политическая революция современности, это была попытка реализовать утопию, но сексуальную, а не политическую. И как Французская революция, она прошла через три фазы: первую, либеральную или анархическую фазу, в которой предполагалось, что утопия возникнет спонтанно, как только прошлое будет сметено; вторую, власть террора, в которой одна сторона захватила власть и попыталась диктаторскими методами реализовать свои планы; и третью, «реакцию», в которой человеческая натура постепенно отвоевала своё место. В этом эссе мы проследуем тем же путём.

ДВЕ УТОПИИ

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

Продолжение …

Clang Language Server

Я уже некоторое время как полностью перешёл на Neovim (после Vim), и это теперь мой основной рабочий редактор. В Sublime Text вернуться просто невозможно: ощущение такое, будто пересел с Ламборджини на советский трактор.

Писать софт в Neovim можно, хотя превращение текстового редактора в IDE скорее неразумно. Тем не менее, писать небольшие программки и скрипты в Neovim вполне удобно, если есть языковой сервер для выбранного языка. Lua, F#, Haskell вполне работают, хотя к последнему пока есть вопросы, но они скорее связаны с моей неопытностью в его экосистеме.

Описывать настройку этого не буду, но пару слов скажу про языковой сервер для C++, clangd.

Затык возник с #include. Компилирую-то я из командной строки, а редактирую в Neovim в графическом интерфейсе, в котором другие настройки переменных окружения (я не засоряю ими системную или пользовательскую область). И иногда хочется подгрузить что-нибудь из Boost, но шланг найдёт системный <iostream>, а <boost\iostreams\detail\iostream.hpp> не найдёт.

Можно сколько угодно прописывать в .clangd параметры типа --include-directory, -isystem, -cxx-system, это скорее всего не поможет. Не поможет и добавление директории с инклюдами в PATH. Мне помогла только установка переменной окружения CPLUS_INCLUDE_PATH.

Замена Dell 2007WFP

С 2007 года два монитора Dell 2007WFP служили мне верой и правдой, но в этом году один из старичков стал сдавать.

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

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

Я честно попытался реанимировать старичка. Вскрытие показало, что на плате инвертора была пара вздувшихся конденсаторов. Я заменил все три таких же на новые, но это ничего не дало. Я нашёл потенциального донора электрики: монитор Dell 2007FP; модель аналогична этой, но отличается только матрицей с другим соотношением сторон и физическим разрешением.

Удивило не то, что установка инвертора из «донора» ничего не исправила; «донор» оказался практически полностью не совместим по внутренностям! Плата инвертора — единственное, что оказалось возможным использовать для проверки: всё остальное оказалось совершенно другой, зеркальной конфигурации. Мне по наивности казалось, что два практически полностью идентичных монитора, отличающиеся по ширине на сантиметр, должны иметь идентичные компоненты для простоты производства, но в Dell сочли иначе.

Так что теперь у меня на руках полностью здоровый Dell 2007WFP, Dell 2007WFP с испорченным блоком матрицы экрана (я сомневаюсь что кто-то полезет во внутренности этого монолита) и 2007FP с разбитой матрицей.

Заменил на два простейших монитора Philips 223V7QDSB; чувство симметрии не позволяет мне держать на кронштейне два совершенно разных монитора, так что рабочим старичком придётся пожертвовать. Размер 21,5" вместо 20", и лишь небольшие рамки вокруг экрана.

Физические диски в виртуальной машине

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

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

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

В Windows для этого трюка нужно следующее.

Подключение диска к системе и определение его номера

В окне «Управление компьютером» (compmgmt.msc) в разделе «Управление дисками» смотрим в нижнюю половину окна слева: там надписи вида «Диск 1», «Диск 2» и т.д. Запоминаем номер диска.

Создание файла-ссылки на физический диск

Открываем командную строку от имени администратора, переходим в C:\Program Files\Oracle\VirtualBox, и выполняем команду vboxmanage internalcommands createrawvmdk -filename "C:\Users\happy\VirtualBox VMs\<directory_name>\<filename>.vmdk" -rawdisk \\.\PhysicalDrive#, где <directory_name> и <filename> — имя директории и файла, куда поместить файл-ссылку с расширением .vmdk, а # — номер диска из предыдущего пункта.

В Windows физический (raw) доступ к диску без прав администратора невозможен, а значит любая работа с этим файлом потребует прав администратора.

Запуск виртуальной машины

Запускаем Oracle VirtualBox от имени администратора, создаём виртуальную машину, но диск для неё не создаём. Переходим в настройки виртуальной машины, вкладка «Носители», и к контроллеру (IDE или AHCI) добавляем жёсткий диск. В открывшемся после этого окне в меню «Носитель» (под заголовком окна, как обычно) нажимаем «Добавить…» и выбираем созданный файл. Если запустили VirtualBox не от имени администратора, то открыть файл не получится. Смена владельца на локального пользователя ничего не даст, прямой доступ к диску так не получить.

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

Есть у меня сомнения в том, как будет (и будет ли вообще) работать функция слепков состояний такого диска.

ASIO и память

Ещё в копилку приколов.

В системе 256Гб памяти, которая доступна. Выделяем на рамдиск 160Гб для размещения там очередного датасета… и выясняется, что 1) при загрузке падает лаунчер UELI (ну это жабаскрипт, чего от него ждать); 2) аудиокарта перестаёт выдавать звук, причём в любом виде, будь это DirectSound, ASIO или WASAPI — звука не будет, совсем.

Аудиокарта — M-Audio Profire Lightbridge, подключающаяся по Firewire.

Есть подозрение, что драйвер этой аудиокарты (или любой?) может работать только в определенном адресном пространстве, которое сейчас занято рамдиском. В любом случае, устройство работает нормально, но принимать звук не способно.