Open16

💻 Type challenge / Medium (4 of 10) やっていくスレ

snakasnaka

String to Union

Capitalize でやったのと同じ、テンプレートリテラルの infer で文字列の分解と、再帰によってその分解を再帰的に適用することによって達成できた

type StringToUnion<T extends string> =
  T extends `${infer F}${infer Rest}`
    ? F | StringToUnion<Rest>
    : never
snakasnaka

Merge

Append to object と同じ考え方で愚直に解いた

type Merge<F, S> = {
  [K in keyof F | keyof S]:
    K extends keyof S
      ? S[K]
      : K extends keyof F
        ? F[K]
        : never
}
  • F の key と S の key を Union でまとめ、K として分配
    • K が S のプロパティであれば S[K] の型を適用
    • K が F のプロパティであれば F[K] の型を適用
    • そのどちらでも無いというケースは存在し得ない ( keyof F | keyof S なので ) ので never
snakasnaka

KebabCase

ちょっと惜しい、文字列の先頭が大文字のケースが対応できていない

type UtoL = {
  'A': 'a'
  'B': 'b'
  'C': 'c'
  'D': 'd'
  'E': 'e'
  'F': 'f'
  'G': 'g'
  'H': 'h'
  'I': 'i'
  'J': 'j'
  'K': 'k'
  'L': 'l'
  'M': 'm'
  'N': 'n'
  'O': 'o'
  'P': 'p'
  'Q': 'q'
  'R': 'r'
  'S': 's'
  'T': 't'
  'U': 'u'
  'V': 'v'
  'W': 'w'
  'X': 'x'
  'Y': 'y'
  'Z': 'z'
}
type KebabCase<S> =
  S extends `${infer First}${infer Rest}`
    ? First extends keyof UtoL
      ? `-${UtoL[First]}${KebabCase<Rest>}`
      : `${First}${KebabCase<Rest>}`
    : ''

以下のテストが失敗している

  Expect<Equal<KebabCase<'FooBarBaz'>, 'foo-bar-baz'>>,
  Expect<Equal<KebabCase<'Foo-Bar'>, 'foo--bar'>>,
  Expect<Equal<KebabCase<'ABC'>, 'a-b-c'>>,
snakasnaka

評価対象の S が再帰の一番外側かどうかを識別する Outer 型変数を追加してみた

type UtoL = {
  'A': 'a'
  'B': 'b'
  'C': 'c'
  'D': 'd'
  'E': 'e'
  'F': 'f'
  'G': 'g'
  'H': 'h'
  'I': 'i'
  'J': 'j'
  'K': 'k'
  'L': 'l'
  'M': 'm'
  'N': 'n'
  'O': 'o'
  'P': 'p'
  'Q': 'q'
  'R': 'r'
  'S': 's'
  'T': 't'
  'U': 'u'
  'V': 'v'
  'W': 'w'
  'X': 'x'
  'Y': 'y'
  'Z': 'z'
}
type KebabCase<S, Outer = true> =
  S extends `${infer First}${infer Rest}`
    ? First extends keyof UtoL
      ? Outer extends true
        ? `${UtoL[First]}${KebabCase<Rest, false>}`
        : `-${UtoL[First]}${KebabCase<Rest, false>}`
      : `${First}${KebabCase<Rest, false>}`
    : S

一応テストは通ったが... ブラッシュアップできそう

snakasnaka

ループ(再帰)と変換を分離してすこし見やすくした

type UtoL = {
  'A': 'a'
  'B': 'b'
  'C': 'c'
  'D': 'd'
  'E': 'e'
  'F': 'f'
  'G': 'g'
  'H': 'h'
  'I': 'i'
  'J': 'j'
  'K': 'k'
  'L': 'l'
  'M': 'm'
  'N': 'n'
  'O': 'o'
  'P': 'p'
  'Q': 'q'
  'R': 'r'
  'S': 's'
  'T': 't'
  'U': 'u'
  'V': 'v'
  'W': 'w'
  'X': 'x'
  'Y': 'y'
  'Z': 'z'
}
type Kebab<S, Outer> =
  S extends keyof UtoL
    ? Outer extends true
      ? UtoL[S]
      : `-${UtoL[S]}`
    : S
type KebabCase<S, Outer = true> =
  S extends `${infer First}${infer Rest}`
    ? `${Kebab<First, Outer>}${KebabCase<Rest, false>}`
    : S
snakasnaka

他の人の回答見た感じTypeChallenge で組み込みの Utility Type 使ってもいいんだな...

snakasnaka

Diff

愚直にやってみたけどもっとコンパクトにできそう

type Diff<O, O1> = {
  [
    K in keyof O | keyof O1
      as K extends keyof O
        ? K extends keyof O1
          ? never
          : K
        : K
  ]: K extends keyof O
    ? O[K]
    : K extends keyof O1
      ? O1[K]
      : never
}
  • O と O1 のプロパティの key をまとめた Union を作って K にひとつづつ Mapping する
    • as の右辺で K が O と O1 両方に存在する key の場合は never として型から除外する
    • それ意外は K を型に含める
  • 値の型については K が O と O1 それぞに存在する型である場合に O[K], O1[K] で型を適用
    • それ以外は never とする
snakasnaka

K が O と O1 の両方の key として含まれているかどうかの判定を Intersection に置きかえた

type Diff<O, O1> = {
  [
    K in keyof O | keyof O1
      as K extends keyof O & keyof O1
        ? never
        : K
  ]: K extends keyof O
    ? O[K]
    : K extends keyof O1
      ? O1[K]
      : never
}
snakasnaka

AnyOf

できた、と思ったけどテストが1つ通っていない

type Truthy = true | 1 | [any, ...any] | { [idx: string | number]: any }
type AnyOf<T extends readonly any[]> =
  T extends [infer F, ...infer Rest]
    ? F extends Truthy
      ? true
      : AnyOf<Rest>
    : false

以下が通らない

  Expect<Equal<AnyOf<[0, '', false, [], {}]>, false>>,