Всю прошлую неделю проходила квалификация на Yandex Cup 2023. Я решил тряхнуть стариной и вспомнить что такое спортивное программирование. Яндекс представил 8 задачек разной сложности, которые необходимо сделать за пять часов. Я принял участие. На старте был уверен в себе. Однако, получил плохие результаты. Следующие пол дня я чувствовал уныние и разочарование. Потом пришла идея, как это использовать.  Мое внимание зацепила одна задача, связанная с типизацией в Typescript. Я давно хотел написать статью про типы, показать бекенду какие сложные интерфейсы можно создавать в Typescript. Завариваем чаек, я начинаю. Чтобы работать более продуктивно, многие слушают музыку. Кому‑то помогает сосредоточиться lo‑fi, кому‑то — белый шум, а кто‑то максимально эффективен, когда слушает тяжёлый металл. Учёные Института Б выяснили, что способность концентрации человека зависит от уровня серотонина в крови во время прослушивания музыки. Причём каждый жанр, и даже трек, влияют на выработку гормона по‑разному. Ваша задача — написать программу, которая позволит получать и анализировать информацию об уровне серотонина в крови при прослушивании конкретных треков у разных фокус‑групп. Так мы сможем выявить композиции, которые лучше всего помогают настроиться на рабочий лад и повышают продуктивность. Условие Разработайте тип Get, который позволит по запросу выдавать параметры песни, такие как автор, дата выпуска песни или другие различные параметры и настройки музыкальных дорожек. На входе программа должна получать константный тип массива и строку с путём в формате точечно‑скобочной нотации (например, «keys→(0–2)→val»), а возвращать — литерал значений объектов по заданному пути. Тип должен позволить запросить информацию по определенным ключам аналогично примерам. Пример структуры: Примеры тестов: Ваше решение должно содержать определение типа Get. Основной вопрос, который может появиться после прочтения — зачем и как это применяется на практике?  Сложно ответить не находясь в контексте. Субъективно, для решения большинства задач подойдут простые типы.  Если судить по характеру примера, то видимо где‑то в Яндекс.музыке это используется. Если вы знаете ответ, обязательно напишите в комментариях.  Если вы не знакомы с Typescript, то советую обратиться к документации. Описать принцип работы типов в Typescript можно с помощью утверждения:  Если что-то выглядит как утка, плавает как утка и крякает как утка, то это, вероятно, и есть утка. Необходимые познания в Typescript: Generics Keyof Type Operator Indexed Access Types Conditional Types, особенно infer Mapped Types Template Literal Types Перейдем к решению задачи. От нас требуется вернуть тип, который по объекту ищет совпадение по строке свойств. Например, у нас есть песня (song). И мы хотим узнать ее автора (author).  Тогда цепочка обращений будет следующей: song.metaData.author. В строковом представлении — metaData->author. По всей видимости, Yandex любит PHP, и в качестве разделителя использует → вместо Нам необходимо написать type Get, который по объекту, вернет новый тип. В данном случае — AC/DC. Задачу разобьем на 2 этапа, где каждый делится еще на два: Получения списка свойств из строки. Разбиение Path по ‘->’. Создание диапазона значений в виде массива. Генерация нового типа из Record, используя список параметров. Прохождение по атрибутам Record вниз.  Перебор коллекции значений в Record.  Для задачи A первого этапа достаточно написать рекурсивный тип, который будет разбивать строку по шаблону. Используем следующий алгоритм: Берем текущее значение и ищем ->. Если находим, то левую часть без разделителя добавляем в массив, а над правой повторяем Шаг 1. Выражение — Value extends ${infer LValue}${Pattern}${infer RValue} проверяет, можно ли из входного параметра Value выделить 3 объекта: LValue, Pattern и RValue. Infer позволяет создать условный тип. В данном случае, истинным выражением будет если найдено ->, где звездочка это любое значение. Корректные варианты: a->b, a->, ->b, ->. Отмечу, Split не проверяет невалидные кейсы. Полагается, что каждый разделитель с обеих сторон имеет соседей, который не является → или пустой строкой. То есть, решения с meta->'' или ->-> считаются неверными. Для определения рекурсивного типа, достаточно вызвать самого себя, но уже с измененными параметрами:  В коллекцию добавляется LValue, а затем вызывается Split с RValue. Если не выполняется проверка, то возвращается сам объект в виде массива — [Value]. Для примера, опишу итерации: На первом шаге metaData->author разобьется на metaData и author. Соответственно в массив попадет [metaData] и алгоритм повторяется для author.  На втором шаге разделитель найден не будет, что следующим значением станет author.  В итоге получается type = . Задача сводится к созданию массива с фиксированным диапазоном. Для выражений (0-2) и (3-6) должны вернуться результаты  и . Используя подход из Split, аналогично найдем Start и End. Стоит отметить, что в данном случае мы получим коллекцию строк, а для создания диапазона необходим number. Если при приведении типов в JavaScript можно просто добавить плюс перед значением (+stringValue) или обернуть в Number(stringValue), то с типизацией это не пройдет. Для преобразования из строки в число также infer: Очень интересна сама реализация в Typescript. Я сталкивался с написанием трансляторов, но разработка оператора infer это что‑то из параллельной вселенной. Упростим решение и сделаем проверку внутри: Создадим перечисление фиксированной длины. Использование рекурсии просто зашкаливает. Стоит переименовать Typescript в RecursiveScript. Принцип работы алгоритма следующий:  Проверяем, что в массиве уже требуемое количество элементов.  Если истина, то возвращаем допустимые значения. В противном случае добавляем объект и переходим на шаг 1.  Пример использования Enumerate:  Самое неочевидное в реализации — это Accumulation[number]. Тут number выступает не в качестве численного типа, а в виде указания обращения к перечисляемому свойству.  Так как Accumulation это массив, то Accumulation[number] формально говорит компилятору — верни все возможные значения. Достаточно убрать number и тогда Enumerate будет возвращать массив . Теперь type Arr = Enumerate<2> вернет . Но как тогда получить диапазон значений? Нужно создать два списка (Start, End) и вычесть из End - Start. Реализации TS будет выглядеть следующим образом: Вычитание сделано с помощью стандартной утилиты — Exclude. Финальная версия примет вид: После того как мы научились разбивать строку на коллекцию параметров, а также превращать диапазон в массив значений, можно перейти к следующему этапу. Необходимо создать тип, который будет выбирать из объекта данные по предоставленному списку свойств. Можно выделить два кейса: в навигации нету перебора коллекций metaData->author. необходимо пройти несколько элементов tracks->(0-2)->volume. Начнем решение с реализации первого варианта.  Снова используем infer. Infer имеет такое же значение в Typescript как слово Ку на Плюке. Алгоритм следующий: Получаем Record и Path.  Если первое свойство является ключом в объекте, то читаем значение из Record и переходим на шаг 1. Иначе возвращаем сам Record. Реализовать это можно следующим образом: Проверяем утверждение — Path extends [first: infer FirstProperty extends keyof Record,...args: infer OtherProperties].  Если оно валидно, то тогда, рекурсивно вызываем ReadProperty<Record[FirstProperty], OtherProperties>. В противном случае, ничего делать не требуется и следует передать обратно Record. Как так выходит, что возвращается неизмененный Record? Когда мы спустились до низа Record, то у нас будет объект и нет аргумента.  Пример. Наша цель найти автора. На первой итерации условие выполняется и мы идем дальше. Спускаемся еще ниже. Как видно из примера, мы в самом низу и поэтому просто возвращаем Record. Немного изменим реализацию и учтем, что свойство из Path может быть не только ключом, но и диапазоном. Теперь FirstProperty является просто строкой, а не частью Record. Из-за этого добавилась проверка на принадлежность FirstProperty части Record.  В месте, где возвращается never, необхоидмо добавить логику с просмотром диапазона значений. Это будет сделано в следующей задаче. Задача сводится к тому, чтобы пройтись по диапазону и выбрать нужные значения.  Вот на этом моменте я и застопорился на Cup-е. Решение оказалось простым до гениальности: Для того чтобы выполнить обход, достаточно использовать объединение - “|”. Пример реализации: В данном случае мы перебираем массив с помощью infer.  Сначала мы вызываем ReadProperty, а уже потом снова рекурсивно ReadArrayProperty. На понимание того, как применить тип для первого элемента и затем пройти по оставшимся, я потратил больше двух часов.  На то оно и спортивное программирование - чьи мозги соображают быстрее, тот и побеждает. Если объединить все вышесказанное, то итоговое решение получается следующим: Примеры для проверки: Я не проверял эту финальную версию в Яндекс.Контесте, так как доделал задачу после завершения отведенного времени.  Будет забавно, если оно неверно. Я доверился только WebStorm: Напишите в комментариях свою реализацию. Мне очень интересно, можно ли как-то  упростить. Есть шанс, что лучшее решение будет на две строчки. UPD: В комментариях есть обновленная версия, в которой убрана задача 2.2. Вместо обхода массива, можно напрямую использовать перечисление. Спасибо @Alexandroppolus за подсказку. Senior Frontend Developer