シーケンスのソート後の順番を、元の順番を保ったまま取得したい
Twitterでお題が出されたので、F# でやってみました。
sortして、indexとzipして、またsortし直すなんて、「ずいぶんとダサいコードを書いてるのね!」って思いました。*1
例のコードの「人」と「車」のデータ型にcarRanking
とhumanRanking
とがあるのも、型の利用しやすさを考えるとちょっと微妙に思えるので、それとなく改修したり。単に「元のデータとランキング結果のタプル」として取得することにします。
解法
アルゴリズム的には、以下の形です。コードを見たほうが早いですけどね。
- 「元のシーケンスの要素と参照セルのペア」のリスト(配列)を作る
- 1.のリストをそのまま置いておいて、キーでソートしたシーケンスを生成
- 2.のシーケンスを列挙して、参照セルにソート後のインデックスを破壊的代入*2
- 1.のリストの参照セルにはソート結果のインデックスが入ってるので、参照セルを不変な値に変換
破壊的代入の辺りが微妙にダサい*3*4ので、その辺りうまくやれそうなサンプルがあるといいですね。どなたか。
考え方
設計・実装の流れは、概ね以下のイメージです。
- データ型を決める
Human
,Car
にRanking
属性は含めたくない
- 関数のシグネチャを決める
- 個別のデータ型に依存したくない → 型変数にしてしまう
- ランキングのソート条件を指定するには、キーを取得する関数を渡すか、または
IComparer
を渡すか - 戻り値はシーケンスの要素と順位のタプルにする
- 関数を書いてみる。宣言部分だけでも*5。
- テストを書く。とにかく書く。書いて動かしてみる。
問について
お題の問に関して、私なりに回答してみます。
- makeCarRankingをHumanにも使えるように汎用的にするにはどうしたらいいか?
→ Generic関数を使います。
この場合はkeySelector
を引数として渡すことで、個別のデータに関わる処理を外出ししました。
型クラスやクラス継承(インターフェース)で多相性を持たせるのも手段ではありますが、とりあえずのところ、こうしてしまうのが手っ取り早いのではないでしょうか。*6
- makeCarRankingのように全体を見てから全部の要素に値を設定する操作はどうしたらスマートか?
→ 機能を区切った関数の内部で、部分的に副作用を使ったコーディングをするのが楽そうです。 完全に純粋にやる方法もありそうですが、純粋さにこだわるよりは、むしろ関数外部に見せるシグネチャが扱いやすいものかどうか、よく考えたほうが良いと思います。
ということで今回はこの辺りで。