Т.е. для number Boolean не является корректным предикатом типа.
Так он проверяет не на число, а на "nonfalsy". То есть правильная тайпгардная типизация внутри BooleanConstructor была бы примерно такая:
<T>(value?: T): value is Exclude<T, null | undefined | 0 | 0n | '' | false>;
Любопытно, что если добавить эту сигнатуру в BooleanConstructor через слияние интерфейсов, то напрямую тайпгард начинает работать (if (Boolean(v)) { ... v ... }), а в filter не подхватывается
Когда у нас значение 0, то Boolean возвращает false. Поэтому Boolean не является корректным предикатом типов для number
Почему? Boolean отсеивает значения undefined и null, и вполне мог бы быть, например, тайпгардом (x: number | undefined) => x is number. Но он протипизирован не как тайпгард, а как обычный предикат, потому и не выкидывает undefined и null, как при вызове напрямую, так и через filter.
В версии 5.5 просто добавили несколько эвристик, по которым в ряде случаев не заданное явно возвращаемое значение функции будет выведено как предикат типа, а не просто логическое, например const isNum = (x: number | undefined) => x != null;. К случаю с Boolean это не относится
Относительно решения: кажется, для второй компоненты надо брать не 24, а 26. У нас тут 7 ребер, надо выкинуть 3 из них. Всего вариантов это сделать С(7, 3) = 35, "плохих" вариантов 9 штук: 4 тройки ребер, смежных с 4 вершинами, и 5 троек, в которых два ребра смежны с пятой вершиной. Итого 35 - 9 = 26
Для асимметричной монеты есть еще такой паззл: надо бросить честный жребий на 5 чел., но мы имеем право на старте выбрать и зафиксировать p (и q = 1 - p) по своему разумению. Придумать это самое "p" и алгоритм, чтобы гарантировать результат не более чем за N бросков (чем меньше N, тем лучше, знаю решение за N=5)
Итого, N мышей могут проверить 2^N колб. Но это за 1 раунд, то есть когда есть запас времени всего 1 час. А если у нас К раундов, т.е. К часов? Как обобщить?
Тогда можно проверить (K+1)^N колб. Номера колб представляем в (K+1)-чной системе счисления, это будет число из N разрядов, возможно с нулями впереди, чтобы было ровненько. Каждая мышь отвечает за свой разряд. На i-м раунде (i = 1..k) мышь с номером p пробует из тех колб, в номере которых p-я цифра равна i. Если погибла, то в номере искомой колбы p-й разряд равен i.
При его строительстве особенно чувствуется разница между тем как TS обрабатывает length у кортежей и у строк шаблонных литералов с известной длиной.
Это только запутывает читателя - непонятно, почему нельзя использовать результаты с предыдущей итерации (точнее, частично можно, а частично нет). Длину мы всё равно смотрим у счетчика Count. Соответственно, у конструируемого значения не обязательно должен быть какой-то свой "показатель длины", и неважно, как мы получаем значение на новой итерации. Просто в первом примере мы собирали кортеж и могли смотреть длину прямо у него, поэтому отдельный вспомогательный счетчик был не нужен. А когда делаем строку (или, например, дерево заданной глубины, или ещё что-то), приходится использовать вот этот прием с отдельным счетчиком.
Тут на самом деле без разницы. TS всё равно превращает это в объединение строковых литералов, потому что Digit - конечный набор значений. Просто делается декартово произведение, и всё. Посмотрите в плейграунде результат, по наведению мыши.
Странный какой-то комментарий. В статье говорилось о контравариантности параметров, и флаге strict, для того чтобы это нормально проверялось. Причем тут перегрузки?
BuildHexString не совсем соответствует своему jsdoc, он здесь дает строки длиной от 1 до N включительно. Если нужна длина ровно N, то надо убрать Result | из рекурсивного вызова. Ну и разумеется, тут не надо возиться с созданием кортежа и последующим join, проще сразу конкатенировать шаблонную строку, длина всё равно считается отдельным кортежем Count:
type _BuildHexString<N extends number, Result extends string, Count extends unknown[]> = Count['length'] extends N
? Result
: _BuildHexString<N, Result | `${Result}${Digit}`, [1, ...Count]>;
Ещё можно добавить, что, несмотря на вложенный цикл, там O(n) по времени, потому что во вложенном цикле делается снятие со стека, а каждый элемент добавится в стек один раз, итого суммарно n внутренних итераций.
Так он проверяет не на число, а на "nonfalsy". То есть правильная тайпгардная типизация внутри
BooleanConstructor
была бы примерно такая:Любопытно, что если добавить эту сигнатуру в BooleanConstructor через слияние интерфейсов, то напрямую тайпгард начинает работать (
if (Boolean(v)) { ... v ... }
), а в filter не подхватываетсяПочему? Boolean отсеивает значения undefined и null, и вполне мог бы быть, например, тайпгардом
(x: number | undefined) => x is number
. Но он протипизирован не как тайпгард, а как обычный предикат, потому и не выкидывает undefined и null, как при вызове напрямую, так и через filter.В версии 5.5 просто добавили несколько эвристик, по которым в ряде случаев не заданное явно возвращаемое значение функции будет выведено как предикат типа, а не просто логическое, например
const isNum = (x: number | undefined) => x != null;
. К случаю с Boolean это не относитсяС пунктом 4 есть ещё более интересный прикол. Да, в object напрямую не засунуть примитив, но можно так:
То есть
object
оказался надтипом для более широкого{}
, что идет вразрез с иерархией типов.П.3 широко известен, во многих библиотеках есть его исправление, добавляющее перегрузку для метода filter.
П.5 - следствие ковариантности изменяемого массива, добавленной для удобства и в своё время вызвавшей много споров.
Из тех, кто в июне 1941 зашел на территорию СССР, большинство были патриотами, причем довольно упоротыми.
Наверно, им ещё гарантировали нормальную бронь от мобы, если в нужный момент "ничего не сломается".
Относительно решения: кажется, для второй компоненты надо брать не 24, а 26. У нас тут 7 ребер, надо выкинуть 3 из них. Всего вариантов это сделать С(7, 3) = 35, "плохих" вариантов 9 штук: 4 тройки ребер, смежных с 4 вершинами, и 5 троек, в которых два ребра смежны с пятой вершиной. Итого 35 - 9 = 26
Индийский дрифт!
Для асимметричной монеты есть еще такой паззл: надо бросить честный жребий на 5 чел., но мы имеем право на старте выбрать и зафиксировать p (и q = 1 - p) по своему разумению. Придумать это самое "p" и алгоритм, чтобы гарантировать результат не более чем за N бросков (чем меньше N, тем лучше, знаю решение за N=5)
Как послематчевые пенальти
В стандартной библиотеке есть SortedSet, у него под капотом сабж
https://github.com/dotnet/runtime/blob/5535e31a712343a63f5d7d796cd874e563e5ac14/src/libraries/System.Collections/src/System/Collections/Generic/SortedSet.cs
Итого, N мышей могут проверить 2^N колб. Но это за 1 раунд, то есть когда есть запас времени всего 1 час. А если у нас К раундов, т.е. К часов? Как обобщить?
Тогда можно проверить (K+1)^N колб. Номера колб представляем в (K+1)-чной системе счисления, это будет число из N разрядов, возможно с нулями впереди, чтобы было ровненько. Каждая мышь отвечает за свой разряд. На i-м раунде (i = 1..k) мышь с номером p пробует из тех колб, в номере которых p-я цифра равна i. Если погибла, то в номере искомой колбы p-й разряд равен i.
Причем это так только на голове
Это только запутывает читателя - непонятно, почему нельзя использовать результаты с предыдущей итерации (точнее, частично можно, а частично нет). Длину мы всё равно смотрим у счетчика Count. Соответственно, у конструируемого значения не обязательно должен быть какой-то свой "показатель длины", и неважно, как мы получаем значение на новой итерации. Просто в первом примере мы собирали кортеж и могли смотреть длину прямо у него, поэтому отдельный вспомогательный счетчик был не нужен. А когда делаем строку (или, например, дерево заданной глубины, или ещё что-то), приходится использовать вот этот прием с отдельным счетчиком.
Тут на самом деле без разницы. TS всё равно превращает это в объединение строковых литералов, потому что Digit - конечный набор значений. Просто делается декартово произведение, и всё. Посмотрите в плейграунде результат, по наведению мыши.
Странный какой-то комментарий. В статье говорилось о контравариантности параметров, и флаге strict, для того чтобы это нормально проверялось. Причем тут перегрузки?
Не понял, в чем затруднение, у меня всё норм. работает
BuildHexString
не совсем соответствует своему jsdoc, он здесь дает строки длиной от 1 до N включительно. Если нужна длина ровно N, то надо убратьResult |
из рекурсивного вызова. Ну и разумеется, тут не надо возиться с созданием кортежа и последующим join, проще сразу конкатенировать шаблонную строку, длина всё равно считается отдельным кортежем Count:Ещё можно добавить, что, несмотря на вложенный цикл, там O(n) по времени, потому что во вложенном цикле делается снятие со стека, а каждый элемент добавится в стек один раз, итого суммарно n внутренних итераций.