LoginSignup
7
0

More than 1 year has passed since last update.

型レベルで【#ゆめみからの挑戦状★第10弾】に挑戦する

Last updated at Posted at 2022-12-09

#ゆめみからの挑戦状とはなんぞ

最近株式会社ゆめみでは、 Twitter にて #ゆめみからの挑戦状 という企画を定期開催しています
ゆめみの社員がプログラミングに関する問題を出して、皆さんに引用リツイートで答えていただくというものです

#ゆめみからの挑戦状★第10弾

// ワンライナーでkeyとvalueを入れ替えてください。

const keyVal = {
  "gu": "グー",
  "choki": "チョキ",
  "pa": "パー"
}

const valKey = /* ここに解答を書いてください */

console.log(valKey)
//       ↓
// {
//   "グー": "gu",
//   "チョキ": "choki",
//   "パー": "pa"
// } 

出題内容は、 {[key: string]: string} なオブジェクトの key と value をワンライナーで入れ替えるというものです

JavaScript での回答例

本記事の挑戦内容

JavaScriptのプログラムとしてではなく、TypeScriptの で挑戦します

なお、株式会社ゆめみ Advent Calendar 2022 ではありますが、出題者としてではなく、1人の挑戦者として挑む 非公式 なものです

問題:TypeScript の型バージョン

// ワンライナーでkeyとvalueを入れ替えてください。

type KeyVal = {
  "gu": "グー",
  "choki": "チョキ",
  "pa": "パー"
}

type ValKey = /* ここに解答を書いてください */
//   ^?
//       ↓
// {
//   "グー": "gu",
//   "チョキ": "choki",
//   "パー": "pa"
// } 

1. 値のユニオン型を得る

まずは KeyVal から 'グー'|'チョキ'|'パー' を得ます

オブジェクトの型から値の型を得るには Indexed Access Types を使います

// 'gu' プロパティの型を得るにはこのように書く
type Gu = KeyVal['gu']
//   ^? "グー"

// 'gu' | 'choki' | 'pa' プロパティの型を得るにはこのように書く
type Values = KeyVal['gu' | 'choki' | 'pa']
//   ^? "グー" | "チョキ" | "パー"

オブジェクトのすべてのキーをユニオン型で得るには keyof 型演算子 を使用すればよいので、 KeyVal 型の取りうる値のユニオン型はこのように書けます

type Values = KeyVal[keyof KeyVal]
//   ^? "グー" | "チョキ" | "パー"

2. 値をキーにしたオブジェクトの型を作る

次に 'グー'|'チョキ'|'パー' をキーにしたオブジェクトを作ります

キーを反復処理してオブジェクトの型を作るには Mapped Types を使います

{[P in K]: T}

このような構文で、 P は新しく定義される型パラメータで K の取りうる型を反復しその型が入ります
また PT の中で使用できます

// 'a' 'b' 'c' を反復し 'a' 'b' 'c' がそれぞれキーとなるオブジェクト
type ValKey = {[Key in 'a' | 'b' | 'c']: any}
//   ^?
// {
//   "a": any;
//   "b": any;
//   "c": any;
// }

Values ( 'グー'|'チョキ'|'パー' ) を使うとこのようになります

type ValKey = {[Val in Values]: Val}
//   ^?
// {
//   "グー": "グー";
//   "チョキ": "チョキ";
//   "パー": "パー";
// }

3. 値からキーを探す

オブジェクトの値からキーを探してみます
まずは 特定の値で KeyVal の各プロパティの値をフィルタリングします

ある型が別な型と互換性があるかどうかで型を分岐させたいときは Conditional Types を使用します

SomeType extends OtherType ? TrueType : FalseType

このような構文で、 SomeTypeOtherType と互換性がある場合 TrueType ない場合 FalseType になります

いままでに出てきた、 keyofMapped TypesIndexed Access Types と組み合わせて、 特定の値で KeyVal の各プロパティの値をフィルタリングして、更に値の部分をキーにします

存在しないようにしたいプロパティの値は never にしておきます

type Val = 'チョキ'
type Filtered = {[Key in keyof KeyVal]: KeyVal[Key] extends Val ? Key : never}
//   ^?
// {
//   "gu": never;
//   "choki": "choki";
//   "pa": never;
// }

そして、さらに Indexed Access Types を使い、取りうる値の型を取得すると never は無視され目的のキーを得られます

type Val = 'チョキ'
type FoundKey = {[Key in keyof KeyVal]: KeyVal[Key] extends Val ? Key : never}[keyof KeyVal]
//   ^? "choki"

4. 1つにまとめてワンライナーにする

今までの内容を1つにまとめてワンライナーにするとこうなります

type ValKey = {
  [Val in KeyVal[keyof KeyVal]]: {
    [Key in keyof KeyVal]: KeyVal[Key] extends Val ? Key : never
  }[keyof KeyVal]
}
// {
//   "グー": "gu",
//   "チョキ": "choki",
//   "パー": "pa"
// } 

終わりに

#ゆめみからの挑戦状 は今後も出題されると思います
型レベルで解く必要は1ミリもないのでぜひ軽い気持ちでご参加ください
(逆に型レベルプログラミングに興味がでた方は type challenges に挑戦だ!)

7
0
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
0