Pull to refresh

Comments 31

Интересная вещь, стоит порешать. Спасибо за ссылку.

Решил почти так же, только Range чуть по другому - с флажком. В целом да, по мотивам type-challenges, если там решить несколько хардов/экстримов, то эту задачу сразу понятно как делать.

Основной вопрос, который может появиться после прочтения — зачем и как это применяется на практике?

Да почти никак. Это такой своеобразный дзен, для ценителей. Подобную задачу не зададут даже на собесе, не говоря уж о рабочем коде.

Решил почти так же, только Range чуть по другому - с флажком.

Интересный подход, даже не думал над таким. Все больше и больше хочу пройти type-challenges.

Да почти никак. Это такой своеобразный дзен, для ценителей.

Согласен, что для ценителей.

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

Основной вопрос, который может появиться после прочтения — зачем и как это применяется на практике?

Сложно ответить не находясь в контексте. Субъективно, для решения большинства задач подойдут простые типы.

Если судить по характеру примера, то видимо где‑то в Яндекс.музыке это используется.

Если вы знаете ответ, обязательно напишите в комментариях.

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

Эх, жалко.

Но задачка забавная, спасибо!

Кстати, тип Split из решения автора упирается в довольно суровые ограничения на глубину рекурсии - 50 итераций. Можно сильно увеличить, если переписать на хвостовую рекурсию:

type Split<
  Value extends string,
  Pattern extends string = '->',
  A extends string[] = [],
> = Value extends ${infer LValue}${Pattern}${infer RValue}
    ? Split<RValue, Pattern, [...A, LValue]>
    : [...A, Value];

Любую рекурсию, как известно, можно переделать на хвостовую. TS начиная с версии 4.5, если не ошибаюсь, умеет её оптимизировать и допускает гораздо большую глубину.

Так и есть. Один раз сталкивался с подобным.

Возьму на вооружение хвостовую рекурсию.

И ещё поинт: ReadArrayProperty не нужен. Не надо вручную обходить набор ключей, TS сделает это сам, если ключи заданы объединением. Вот, с набором 0 | 1:

type TSong = typeof song;

type TrackNames = TSong['tracks'][0 | 1]['name']; // "Piano" | "Guitar"

Так что достаточно превратить, например, '(1-3)' в объединение 1 | 2 | 3, и это уже будет готовый ключ.

Да, я увидел это еще в вашем решении.

Если переписать без обхода массива, то финальное решение немного укоротиться — play.

type Split<
  Value extends string,
  Pattern extends string = '->',
  Accumulation extends string[] = []
> = Value extends `${infer LValue}${Pattern}${infer RValue}`
    ? Split<RValue, Pattern, [...Accumulation, LValue extends `(${infer Start extends number}-${infer End extends number})`? Exclude<Enumerate<End>, Enumerate<Start>> : LValue]>
    : [...Accumulation, Value];

type Enumerate<
  Length extends number, 
  Accumulation extends number[] = []
> = Accumulation['length'] extends Length
  ? Accumulation[number]
  : Enumerate<Length, [...Accumulation, Accumulation['length']]>;

type ReadProperty<
  Record,
  Path
> = Path extends [first: infer FirstProperty extends keyof Record, ...args: infer OtherProperties]
  ? ReadProperty<Record[FirstProperty], OtherProperties>
  : Record;

type Get<T, Path extends string> = ReadProperty<T, Split<Path>>;

type UnionOfNumbersLessThan<N extends number, HelperArray extends number[] = []> = N extends HelperArray['length']
 ? HelperArray[number] 
 : UnionOfNumbersLessThan<N, [...HelperArray, HelperArray['length']]>

type NumberRange<L extends number, H extends number> = H | Exclude<UnionOfNumbersLessThan<H>, UnionOfNumbersLessThan<L>>


type ToNumber<S extends string> = S extends `${infer N extends number}` ? N : never;

type Cast<A1 extends any, A2 extends any> =
    A1 extends A2
    ? A1
    : A2


// split k1->k2->(s-e)->k3 like structure to array of keys
type SplitPath<Path extends string> = Path extends `${infer First}->${infer Rest}`? [First, ... SplitPath<Rest>] :[Path]


type RecursivePick<T extends unknown, Keys extends string[]> = Keys extends [infer Key, ...infer Rest] 
? Key extends `(${infer Start}-${infer End})` 
    // If we have (start-end) range then
    //@ts-ignore
    ? RecursivePick<T[Cast<NumberRange<ToNumber<Start>, ToNumber<End>>, keyof T> extends  T[Cast<Key, keyof T>] ? T[Cast<Key, keyof T>] : never], Cast<Rest, string[]>>
    // else recursively get property
    : RecursivePick<T[Cast<Key, keyof T>], Cast<Rest, string[]>> 
: T

type Get<T extends unknown, Path extends string> = SplitPath<Path>[0] extends keyof T
// if First key exists in object
? SplitPath<Path> extends [infer Key, ...infer Rest] 
    ? RecursivePick<Cast<T[Cast<Key, keyof T>], object>, Cast<Rest, string[]>>
    : never
: never


// TESTS!
type songAuthor = Get<typeof song, 'metaData->author'>; // AC/DC
type firstTrackVolume = Get<typeof song, 'tracks->0->volume'>; // 78
type tracksVolume = Get<typeof song, 'tracks->(0-2)->volume'>; // 78 | 60
type tracksVolume2 = Get<typeof song, 'tracks->(0-2)->regions->(0-2)->end'>; // 78 | 60
type notes = Get<typeof song, 'tracks->1->regions->0->midiData->(0-5)->note'>; // "F4" | "D4" | "E4" | "C4"
type midiData = Get<typeof song, 'tracks->1->regions->0->midiData->(0-2)'>; // { note: "C4", velocity: 10, 
// startTime: 0, duration: 1, } | { note: "E4", velocity: 20, startTime: 1, duration: 1 }
type thirdNoteVelocity = Get<typeof song, 'tracks->1->regions->0->midiData->3->velocity'>; // 40
type qwe = Get<typeof song, 'lala->nana'>; // never
type asd = Get<typeof song, 'metaData->nana'>; // "Highway to Hell" | "AC/DC" | "27.07.1979"

Вот мое решение, которое набрало 40/40 баллов, по сути суть та же, думаю можно сократить до 20 строчек. Жаль, что уже нельзя проверить будет на тех тестах, что в самом Яндекс контесте используется

Немного тяжело читатается, но работает. Добавил в play.

Там можно убрать все Cast, но тогда нужно немного переписать решение, иначе TypeScript будет ругаться, я делал быстро и старался, чтобы финальное решение просто работало)

Никакого осуждения :) Вы успели сдать задачку и получить баллы, а я нет.

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

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

У меня вот не получилось вторую сделать, Wrong Answer постоянно приходил, у Вас еще осталось решение? А то прям любопытно что там не так

У меня решения не осталось. Там же была потрясающая, настроенная среда :). Можно попробовать восстановить решение.

А где можно условия глянуть? Я не участвовал (задачу по типизации мне коллега скинул в телеграм в позавчера утром)

Да, была, но я лично не делал там, так как неудобно, что на каждый чих шел запуск кода, ну и нет prettier)

я сделал fractal-tree, но тоже не до конца. Я схлопнул рекурсию кроме сброса контекста, и долго не мог понять, как его запихнуть

Глянул задачи. Кажется, без рекурсии там вообще не надо возиться с контекстом, можно сразу вычислять концы отрезка (синусами/косинусами), причем только для левой половины, а для правой делать по симметрии.

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

// drawTree.js
const getColor = function () {
    const colors = {};

    return function (level) {
        if(typeof colors[level] !== "undefined") {
            return colors[level];
        }
        const color = computeColor(level);
        colors[level] = color;

        return color;
    }
}();

const getWidth = function createComputeWidth() {
    const width = {};

    return function (level) {
        if(typeof width[level] !== "undefined") {
            return width[level];
        }
        const color = computeWidth(level);
        width[level] = color;

        return color;
    }
}();

function draw(startY, angle, level) {
    const startX = canvas.width / 2;
    const len = length * Math.pow(depth, level);

    ctx.beginPath();
    ctx.save();

    ctx.translate(level ? 0 : startX, startY);
    ctx.rotate(angle * Math.PI / 180);
    ctx.moveTo(0, 0);
    ctx.lineTo(0, -len);

    ctx.strokeStyle = getColor(level);
    ctx.lineWidth = getWidth(level);

    ctx.stroke();

    if (len < 10) {
        ctx.restore();
    }
}

function drawTree(startY, angle, level = 0) {
    const restore = '__restore__';
    const stack = [];
    stack.push([restore]);
    stack.push([startY, angle, level]);

    do {
        const [y, an, lev] = stack.pop();

        if(y === restore) {
            ctx.restore();
            continue;
        }

        const len = length * Math.pow(depth, lev);
        draw(y, an, lev);

        if (len >= 10) {
            stack.push([restore]);
            stack.push([-len, an - angleOffset, lev + 1]);
            stack.push([-len, an + angleOffset, lev + 1]);
        }
    } while (stack.length > 0);
}

Думаю, такое решение прошло бы.

Исходники для задачи можно взять тут github, спасибо за это @MihailPertsev.

Там только заменить drawTree.js на код, который представлен выше.

Постфактум уже же нельзя никак проверить, прошло бы решение или нет? Или все-таки можно? Может после всего соревнования или через год/два/три?

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

Тут может ответить только кто-то из Yandex. Поговори с @veged может он знает кого-то, кто поможет узнать :)

Ух ты, не знал что можно с помощью infer выводить числа из строки! Я для этого задания написал свой парсер чисел используя массивы в качестве чисел...

Текст решения
type Add<A extends any[], B extends any[]> = [...A, ...B];
type Mul<A extends any[], B extends any[]> = A extends [infer H, ...infer T] ? [...B, ...Mul<T, B>] : [];

type Ten = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

type AsNumber<N extends string, Acc extends any[] = []> =
    N extends `0${infer R}` ? AsNumber<R, Mul<Acc, Ten>> :
    N extends `1${infer R}` ? AsNumber<R, Add<Mul<Acc, Ten>, [1]>> :
    N extends `2${infer R}` ? AsNumber<R, Add<Mul<Acc, Ten>, [1, 2]>> :
    N extends `3${infer R}` ? AsNumber<R, Add<Mul<Acc, Ten>, [1, 2, 3]>> :
    N extends `4${infer R}` ? AsNumber<R, Add<Mul<Acc, Ten>, [1, 2, 3, 4]>> :
    N extends `5${infer R}` ? AsNumber<R, Add<Mul<Acc, Ten>, [1, 2, 3, 4, 5]>> :
    N extends `6${infer R}` ? AsNumber<R, Add<Mul<Acc, Ten>, [1, 2, 3, 4, 5, 6]>> :
    N extends `7${infer R}` ? AsNumber<R, Add<Mul<Acc, Ten>, [1, 2, 3, 4, 5, 6, 7]>> :
    N extends `8${infer R}` ? AsNumber<R, Add<Mul<Acc, Ten>, [1, 2, 3, 4, 5, 6, 7, 8]>> :
    N extends `9${infer R}` ? AsNumber<R, Add<Mul<Acc, Ten>, [1, 2, 3, 4, 5, 6, 7, 8, 9]>> :
    Acc;

type Get<T extends unknown, Path extends string> = Path extends '' ? T :
    Path extends `${infer Key}->${infer Rest}` ? Get1<T, Key, Rest> :
    Get1<T, Path, ''>;

type Get1<T extends unknown, Key extends string, Rest extends string> =
    Key extends `(${infer L}-${infer R})` ? GetLR<T, AsNumber<L>, AsNumber<R>, Rest> :
    T extends Record<Key, unknown> ? Get<T[Key], Rest> :
    never;

type GetLR<T extends unknown, L extends any[], R extends any[], Rest extends string> =
    T extends Record<L["length"], unknown> ? (
        R extends L ? Get<T[L["length"]], Rest> :
        Get<T[L["length"]], Rest> | GetLR<T, [1, ...L], R, Rest>
    ) : never

Можно давать подобную задачку на собеседовании: "Напишите парсер числа из строки, используя массивы".

Эти самодельные парсеры довольно дохлые по ограничениям) AsNumber из поста выше долетает только до 489. Вот такой вариант - до 9999. А вот - встроенный парсер.

Я вот пытался на скорую руку найти в интернете, как спарсить числовой тип из строкового, но не получилось. Пришлось выкручиваться =)

Ну и документация тоже не помогла

Sign up to leave a comment.

Articles