Skip to content

Instantly share code, notes, and snippets.

@nenono
Last active August 29, 2015 14:20
Show Gist options
  • Save nenono/fdb38f29f0e60c28bf2f to your computer and use it in GitHub Desktop.
Save nenono/fdb38f29f0e60c28bf2f to your computer and use it in GitHub Desktop.
シーケンスのソート後の順番を、元の順番を保ったまま取得したい
(*
お題:
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