Last active
August 29, 2015 14:20
-
-
Save nenono/fdb38f29f0e60c28bf2f to your computer and use it in GitHub Desktop.
シーケンスのソート後の順番を、元の順番を保ったまま取得したい
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(* | |
お題: | |
https://ideone.com/gtRf08 | |
*) | |
(* | |
rankBy 関数 (renameしました withRanking → rankBy) | |
シーケンスを、指定されたkeySelectorで取得したキーを使用してソートした結果のインデックスとのペアに変換します。 | |
*) | |
let rankBy (keySelector: 'a -> 'b when 'b : comparison) (source:'a seq) = | |
let temp = | |
source | |
|> Seq.map (fun x -> (x, ref 0)) // 参照セルとのペアに変換 | |
|> Seq.toArray // 一旦シーケンスを列挙しておく | |
temp | |
|> Seq.sortBy (fun (x, _) -> keySelector x) // キーでソート | |
|> Seq.iteri (fun index (x, resultRef) -> | |
resultRef := index) // ソート結果のインデックスを参照セルに格納 | |
temp | |
|> Seq.map (fun (x, indexRef) -> (x, !indexRef)) // 参照セルを通常の整数値に変換 | |
(* | |
テスト | |
車を重量または値段を基準に順位をつけます。元のリストの順序は変更したくありません。 | |
*) | |
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>] | |
module Car = | |
type Car = { Price : int; Weight : int } | |
open Car | |
let cars = | |
[ | |
{ Price = 10; Weight = 50 }; | |
{ Price = 50; Weight = 10 }; | |
{ Price = 20; Weight = 20 }; | |
{ Price = 30; Weight = 60 } | |
] | |
let test1 = | |
let rankedByWeight = cars |> rankBy (fun x -> x.Weight) |> Seq.toList | |
rankedByWeight = | |
[ | |
({ Price = 10; Weight = 50 }, 2); | |
({ Price = 50; Weight = 10 }, 0); | |
({ Price = 20; Weight = 20 }, 1); | |
({ Price = 30; Weight = 60 }, 3) | |
] | |
// -> true | |
let test2 = | |
let rankedByPrice = cars |> rankBy (fun x -> x.Price) |> Seq.toList | |
rankedByPrice = | |
[ | |
({ Price = 10; Weight = 50 }, 0); | |
({ Price = 50; Weight = 10 }, 3); | |
({ Price = 20; Weight = 20 }, 1); | |
({ Price = 30; Weight = 60 }, 2) | |
] | |
// -> true | |
(* | |
テスト | |
別の型(ここではHuman)にも当然対応します | |
*) | |
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>] | |
module Human = | |
type Human = { Weight : int; Age : int} | |
(* | |
同一名のフィールドを持つレコードを使うなら、モジュールを切り分けたい。HumanWeightやCarWeight等としない。 | |
ただ、「Weightフィールドを持つレコード」に対する関数を作るというような、多相的な扱いはできないので注意。 | |
SML#みたいにはいかない。 | |
型推論にデメリットが出るかもしれないので、フィールド名重複を許すかは状況や主義次第になるかも。 | |
*) | |
open Human | |
let people = | |
[ | |
{ Age = 19; Weight = 50 }; | |
{ Age = 50; Weight = 40 }; | |
{ Age = 20; Weight = 60 }; | |
{ Age = 30; Weight = 70 } | |
] | |
let test3 = | |
let rankedByWeight = people |> rankBy (fun x -> x.Weight) |> Seq.toList | |
rankedByWeight = | |
[ | |
({ Age = 19; Weight = 50 }, 1); | |
({ Age = 50; Weight = 40 }, 0); | |
({ Age = 20; Weight = 60 }, 2); | |
({ Age = 30; Weight = 70 }, 3) | |
] | |
// -> true | |
(* | |
応用編 軽い/安い順ではなくて重い/高い順にします。 | |
Seq.sortByDescending(F# 4.0 http://stackoverflow.com/questions/3111448/f-seq-sortby-in-descending-order)や、 | |
System.Linq.Enumerable.OrderByDescendingを使いたいところ。この場合だけならkeySelectorで整数の逆数を取る小細工でお茶を濁しても可ですが。 | |
*) | |
// ソート関数を差し替え可能にしてみる | |
let __rankBy sorting keySelector source = // 型を明示するのがつらくなったので推論に任せる | |
let temp = | |
source | |
|> Seq.map (fun x -> (x, ref 0)) // 参照セルとのペアに変換 | |
|> Seq.toArray // 一旦シーケンスを列挙しておく | |
temp | |
|> sorting (fun (x, _) -> keySelector x) // キーでソート | |
|> Seq.iteri (fun index (x, resultRef) -> | |
resultRef := index) // ソート結果のインデックスを参照セルに格納 | |
temp | |
|> Seq.map (fun (x, indexRef) -> (x, !indexRef)) // 参照セルを通常の整数値に変換 | |
// 関数名が上と重複するのでrankBy'としておく | |
let rankBy'<'a, 'b when 'b : comparison> : ('a -> 'b) -> 'a seq -> ('a * int) seq = // 型を明示しないとValue Restrictionに引っかかる模様。つらい。 | |
__rankBy Seq.sortBy | |
let rankByDescending<'a, 'b when 'b : comparison> : ('a -> 'b) -> 'a seq -> ('a * int) seq = // 同上のため型明示。 | |
let sorting keySelector source = System.Linq.Enumerable.OrderBy(source, new System.Func<_, _>(keySelector)) // 何故かOrderByDescendingが昇順になってしまう。 | |
__rankBy sorting | |
let test4 = | |
let rankedByWeight = cars |> rankBy' (fun x -> x.Weight) |> Seq.toList | |
rankedByWeight = | |
[ | |
({ Price = 10; Weight = 50 }, 2); | |
({ Price = 50; Weight = 10 }, 0); | |
({ Price = 20; Weight = 20 }, 1); | |
({ Price = 30; Weight = 60 }, 3) | |
] | |
// -> true | |
let test5 = | |
let rankedByWeightReverse = cars |> rankByDescending (fun x -> - x.Weight) |> Seq.toList | |
rankedByWeightReverse = | |
[ | |
({ Price = 10; Weight = 50 }, 1); | |
({ Price = 50; Weight = 10 }, 3); | |
({ Price = 20; Weight = 20 }, 2); | |
({ Price = 30; Weight = 60 }, 0) | |
] | |
// -> true |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment