Комментарии 31
Чтобы подтянуть знания в TypeScript крайне рекомендую выделить пару дней и решить все задачки https://github.com/type-challenges/type-challenges без гугла, максимум с офф докой. Довольно много задачек там крайне интересные и нетривиальные.
Можно ещё в TS песочнице проверить) https://www.typescriptlang.org/play
Ага, забыл про это.
Добавляю ссылку на финальное решение — typescriptlang.org/play.
Решил почти так же, только 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 строчек. Жаль, что уже нельзя проверить будет на тех тестах, что в самом Яндекс контесте используется
Там можно убрать все Cast, но тогда нужно немного переписать решение, иначе TypeScript будет ругаться, я делал быстро и старался, чтобы финальное решение просто работало)
Никакого осуждения :) Вы успели сдать задачку и получить баллы, а я нет.
А Вы какие задачи еще делали?
Не считая первых двух, я сделал fractal-tree, но тоже не до конца. Я схлопнул рекурсию кроме сброса контекста, и долго не мог понять, как его запихнуть. В итоге решение пришло уже после завершения.
Еще мне понравилась задачка про веб-сокеты, но я понял, что не успею ее сделать и даже не брался.
У меня вот не получилось вторую сделать, Wrong Answer постоянно приходил, у Вас еще осталось решение? А то прям любопытно что там не так
У меня решения не осталось. Там же была потрясающая, настроенная среда :). Можно попробовать восстановить решение.
А где можно условия глянуть? Я не участвовал (задачу по типизации мне коллега скинул в телеграм в позавчера утром)
@Alexandroppolus Выложил все условия в репозиторий -> https://github.com/GoldStrikeArch/yandex-cup-frontend-2023
Под второй задачей имеется в виду задача B
Да, была, но я лично не делал там, так как неудобно, что на каждый чих шел запуск кода, ну и нет 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
на код, который представлен выше.
Ух ты, не знал что можно с помощью 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
Решение задачи про определение типа в Typescript с Yandex Cup 2023