type challenge【最上級編】

の最上級編をやる

Get Readonly Keys
readonlyなプロパティのkeyを取得
解答
上級編でほぼ同じget mutable keysをやっていたので瞬殺
type GetReadonlyKeys<T> = keyof {
[k in keyof T as Equal<Pick<T, k>, Readonly<Pick<T, k>>> extends true ? k : never]: any
}

Query String Parser
query文字列(k1=v1&k2=v2
みたいなやつ)をparseする型を実装
条件としてkeyだけ与えられた時はvalue=trueとする。
また同じkeyが複数与えられた時はvalueをもつ配列とする(重複したvalueの場合は無視)
解答
例外処理がだいぶ面倒で複雑な処理になってしまった。
メインのQueryStringParser型では文字列をparseして、その結果をもとにオブジェクトにプロパティと値を足すKeyValueToObject型を作成した。既に存在するkey, valueが与えられた時に新しく追加しないように重複を考慮して配列に追加するようにした。
type HasDuplicate<Arr extends any[], Value> = Arr extends [infer Head, ...infer Rest]
? Head extends Value
? true
: HasDuplicate<Rest, Value>
: false
type AppendArrayExcludeDuplicate<Arr extends any[], Value> = HasDuplicate<Arr, Value> extends true
? Arr
: [...Arr, Value]
type KeyValueToObject<K extends string, V, O> = {
[k in keyof O | K as k]: k extends K
? k extends keyof O
? O[k] extends any[]
? AppendArrayExcludeDuplicate<O[k], V>
: O[k] extends V
? V
: [O[k], V]
: V
: k extends keyof O
? O[k]
: never
}
type ParseQueryString<S extends string, Ret extends object = {}> = S extends `${infer Key}=${infer Value}&${infer Rest}`
? ParseQueryString<Rest, KeyValueToObject<Key, Value, Ret>>
: S extends `${infer Key}&${infer Rest}`
? ParseQueryString<Rest, KeyValueToObject<Key, true, Ret>>
: S extends `${infer Key}=${infer Value}`
? KeyValueToObject<Key, Value, Ret>
: S extends ""
? Ret
: KeyValueToObject<S, true, Ret>

slice
slice関数の実装
解答
負のIndexを与えられた時以外は自力正解できた
// 負のIndexを与えた時に通らない
type Slice<Arr extends number[], Start extends number = 0, End extends number = Arr['length'], IndexArr extends any[] = [], Ret extends number[] = []> = Arr extends [infer Head extends number, ...infer Rest extends number[]]
? Start extends IndexArr['length']
? End extends IndexArr['length']
? Ret
: Slice<Rest, Start, End, [...IndexArr, any], [...Ret, Head]>
: End extends IndexArr['length']
? Ret
: Ret['length'] extends 0
? Slice<Rest, Start, End, [...IndexArr, any], Ret>
: Slice<Rest, Start, End, [...IndexArr, any], [...Ret, Head]>
: Ret
負のIndexの扱いの上手いやり方が思いつかず答えチラ見した。
Sliceを使うと負のIndexを正のIndexに変換できるようだったので変換したらいけた。
type ConvertNegativeIndex<Arr extends any[], Index> = `${Index}` extends `-${infer N extends number}`
? Slice<Arr, N>['length']
: Index
type Slice<
Arr extends number[],
Start = 0,
End = Arr['length'],
IndexArr extends any[] = [],
Ret extends number[] = [],
ConvertedStart = ConvertNegativeIndex<Arr, Start>,
ConvertedEnd = ConvertNegativeIndex<Arr, End>
> = Arr extends [infer Head extends number, ...infer Rest extends number[]]
? ConvertedStart extends IndexArr['length']
? ConvertedEnd extends IndexArr['length']
? Ret
: Slice<Rest, ConvertedStart, ConvertedEnd, [...IndexArr, any], [...Ret, Head]>
: ConvertedEnd extends IndexArr['length']
? Ret
: Ret['length'] extends 0
? Slice<Rest, ConvertedStart, ConvertedEnd, [...IndexArr, any], Ret>
: Slice<Rest, ConvertedStart, ConvertedEnd, [...IndexArr, any], [...Ret, Head]>
: Ret

Integers Comparator
数値の比較
解答
再帰の問題があって巨大な数は比較できないが↓で一応小さい数なら比較できる。
正の数同士を比較するComparatorForPositive
型を作成し、与えられた引数がお互いに負の数だった場合は引数の順番を逆にしてComparatorForPositive
を呼ぶようにした。片方だけ負の数の場合はその時点で大小関係が決まる。
enum Comparison {
Greater,
Equal,
Lower,
}
type MinusOne<T extends number, A extends any[] = []> = [...A, any] extends { length: T} ? A['length'] : MinusOne<T, [...A, any]>
type ComparatorForPositive<A extends number, B extends number> = A extends 0
? B extends 0
? Comparison.Equal
: Comparison.Lower
: B extends 0
? Comparison.Greater
: ComparatorForPositive<MinusOne<A>, MinusOne<B>>
type Comparator<A extends number, B extends number> = `${A}`extends `-${infer AbsA extends number}`
? `${B}` extends `-${infer AbsB extends number}`
? ComparatorForPositive<AbsB, AbsA>
: Comparison.Lower
: `${B}` extends `-${any}`
? Comparison.Greater
: ComparatorForPositive<A, B>
巨大な数同士を比較できるようにするには配列の長さではなく数字を文字として扱って大小関係を定義する必要がありめんどくさいのでやめた。
数字を比較するやり方は https://github.com/type-challenges/type-challenges/issues/11444 とかが綺麗に実装されていて参考になる。

Currying 2
ダメだった解答
type GetArrayHead<A extends unknown[], N extends number, Ret extends unknown[] = []>
= Ret['length'] extends N
? [Ret, A]
: A extends [infer Head, ...infer Rest]
? GetArrayHead<Rest, N, [...Ret, Head]>
: [Ret, A]
type a = GetArrayHead<[1, 2, 3], 1>
type _currying<Args extends unknown[], Ret, ArgNum extends number = 1, ArgsHead extends [unknown[], unknown[]] = GetArrayHead<Args, ArgNum>> = Args extends []
? Ret
: (...args: ArgsHead[0]) => Currying<ArgsHead[1], Ret>
type NumUnion<N extends number, Ret extends number[] = [0]> = Ret['length'] extends N
? Exclude<[...Ret, Ret['length']][number], 0>
: NumUnion<N, [...Ret, Ret['length']]>
type Currying<Args extends unknown[], Ret, ArgNumUnion extends number = NumUnion<Args['length']>> = ArgNumUnion extends ArgNumUnion
? _currying<Args, Ret, ArgNumUnion>
: never
declare function DynamicParamsCurrying<T>(fn: T): T extends (...args: infer U) => infer V
? U extends []
? V
: Currying<U, V>
: never
こんな感じで渾身の型を書いたがダメだった。1つ1つ引数の数を指定した場合(Currying<U, V, 2>
のように指定)はうまく動くがそれ以外がダメ。
他の人の解答
後で理解して書く

Inclusive Range
Lower, Higherの間の数字をもつ配列を作成
再帰の上限にかからないように工夫する必要がある
解答
type InclusiveRange<Lower extends number, Higher extends number, Count extends unknown[] = [], Ret extends number[] = []> = Count['length'] extends Lower
? Count['length'] extends Higher
? [...Ret, Lower]
: InclusiveRange<Lower, Higher, [...Count, unknown], [...Ret, Count['length']]>
: Count['length'] extends Higher
? Ret extends []
? Ret
: [...Ret, Higher]
: Ret extends []
? InclusiveRange<Lower, Higher, [...Count, unknown], Ret>
: InclusiveRange<Lower, Higher, [...Count, unknown], [...Ret, Count['length']]>
特に工夫せずともいけた。再帰上限が変わった??