#ゆめみからの挑戦状とはなんぞ
最近株式会社ゆめみでは、 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
の取りうる型を反復しその型が入ります
また P
は T
の中で使用できます
// '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
このような構文で、 SomeType
が OtherType
と互換性がある場合 TrueType
ない場合 FalseType
になります
いままでに出てきた、 keyof
、 Mapped Types
、 Indexed 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 に挑戦だ!)