Open8

type challenge【最上級編】

yutake27yutake27

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
}
yutake27yutake27

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>
yutake27yutake27

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
yutake27yutake27

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 とかが綺麗に実装されていて参考になる。

yutake27yutake27

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>のように指定)はうまく動くがそれ以外がダメ。

他の人の解答

後で理解して書く

yutake27yutake27

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']]>

特に工夫せずともいけた。再帰上限が変わった??