コード
PowerShell で 極力簡単に配列をシャッフル (ランダムにソート) する方法。
速度や制度などはガン無視して、簡単に書けることを重視。
$shuffled = $array | sort -prop @{Exp={[Guid]::NewGuid()}}
ハイ終わり。
検証
ホントに実用的なシャッフルになっているか心配なので、実際に 0~19 のシャッフルを 2000 回行って、先頭に来た数字と、10 が来たインデックスを集計してみる。
$r = 0..1999
$r | %{ $r[$_] = 0..19 | sort -prop @{Exp={[Guid]::NewGuid()}}; }
$r | %{ $_[0] } | group | Format-Table -AutoSize
$r | %{ $_.IndexOf(10) } | group | Format-Table -AutoSize
Count Name Group ----- ---- ----- 104 16 {16, 16, 16, 16...} 91 19 {19, 19, 19, 19...} 111 8 {8, 8, 8, 8...} 102 7 {7, 7, 7, 7...} 95 18 {18, 18, 18, 18...} 105 3 {3, 3, 3, 3...} 95 12 {12, 12, 12, 12...} 83 15 {15, 15, 15, 15...} 100 17 {17, 17, 17, 17...} 89 5 {5, 5, 5, 5...} 103 13 {13, 13, 13, 13...} 103 11 {11, 11, 11, 11...} 101 10 {10, 10, 10, 10...} 110 4 {4, 4, 4, 4...} 100 6 {6, 6, 6, 6...} 88 2 {2, 2, 2, 2...} 123 9 {9, 9, 9, 9...} 92 1 {1, 1, 1, 1...} 107 0 {0, 0, 0, 0...} 98 14 {14, 14, 14, 14...} Count Name Group ----- ---- ----- 102 17 {17, 17, 17, 17...} 93 15 {15, 15, 15, 15...} 100 9 {9, 9, 9, 9...} 109 18 {18, 18, 18, 18...} 91 4 {4, 4, 4, 4...} 104 5 {5, 5, 5, 5...} 111 3 {3, 3, 3, 3...} 107 7 {7, 7, 7, 7...} 83 1 {1, 1, 1, 1...} 106 10 {10, 10, 10, 10...} 101 13 {13, 13, 13, 13...} 93 8 {8, 8, 8, 8...} 111 16 {16, 16, 16, 16...} 101 0 {0, 0, 0, 0...} 83 2 {2, 2, 2, 2...} 104 14 {14, 14, 14, 14...} 103 12 {12, 12, 12, 12...} 98 11 {11, 11, 11, 11...} 81 19 {19, 19, 19, 19...} 119 6 {6, 6, 6, 6...}
だいたい 100 に収束しているし、問題ないんじゃない?
とはいえ、 Guid クラスは、一意性はある程度期待できると書かれているものの、ランダム性は特に言及されていない。
出現にパターンがある可能性もあるし、厳密にランダムである必要がある場合は、使わない方がいいかもしれない。
速度比較
ランダムの要素数が少なければ、 Get-Random コマンドの方が早いかなと思ったら、全然そんなこと無かった。
Measure-Command { $r = 0..1999; $r | %{ $r[$_] = 0..19 | sort -prop @{Exp={[Guid]::NewGuid()}}; }; }
Measure-Command { $t = 0..128KB | sort -prop @{Exp={[Guid]::NewGuid()}}; }
TotalSeconds : 1.5642975 TotalSeconds : 5.8651884
Measure-Command { $r = 0..1999; $n = New-Object Random; $r | %{ $r[$_] = 0..19 | sort -prop @{Exp={$n.Next()}}; }; }
Measure-Command { $n = New-Object Random; $t = 0..128KB | sort -prop @{Exp={$n.Next()}}; }
TotalSeconds : 2.0828075 TotalSeconds : 7.0780194
Measure-Command { $r = 0..1999; $r | %{ $r[$_] = 0..19 | sort -prop @{Exp={Get-Random}}; }; }
Measure-Command { $t = 0..128KB | sort -prop @{Exp={Get-Random}}; }
TotalSeconds : 5.0709305 TotalSeconds : 16.7063711
Core i5-3317U CPU 1.70GHz + 4GB メモリ + Windows8 + PowerShell v3 にて計測
コマンドレット遅すぎだろ JK。
そしてよくわからない GUID の早さ。System.Random が遅いのか?
まぁ、GUID 使っときゃ良さそう。
でも、コンパイルした方がずっと早い
コマンドレットが遅いと言うことは、言うまでも無く、ソート処理も C# などで書いて IL にコンパイルしてしまった方が、段違いに早い。
ついでに言うと、パイプも遅いゲイン員なので、foreach ステートメントに切り替えとく。
$tt = Add-Type ('_' + [Guid]::NewGuid().ToString().Replace('-', '')) -Member 'public static object[] shuffle(object[] target) { return target.OrderBy(i => Guid.NewGuid()).ToArray(); }' -Language CSharpVersion3 -UsingNamespace System.Linq, System.Collections.Generic -PassThru;
Measure-Command { $r = 0..1999; foreach ($item in $r) { $r[$item] = $tt::shuffle(0..19) }; }
Measure-Command { $t = $tt::shuffle(0..128KB); }
TotalSeconds : 0.0374192 TotalSeconds : 0.1770128
IL を避けて、リフレクションを使う
IL コンパイルを避けて、リフレクション使って実行するならば、こんな感じだろうか。
IL と比べれば一桁遅いが、 Sort-Object にパイプするよりは早い。
Add-Type -AssemblyName System.Core
$OrderByMethod = [Linq.Enumerable].GetMethods() | ?{$_.Name -eq 'OrderBy' -and $_.IsGenericMethod -and $_.GetGenericArguments().Length -eq 2 } | Select-Object -First 1 | %{ $_.MakeGenericMethod(@([Int32], [Guid])) }
$FuncGuidGen = {[Guid]::NewGuid()} -as [Func``2].MakeGenericType(@([Int32], [Guid]))
Measure-Command { $r = 0..1999; foreach ($item in $r) { $r[$item] = $OrderByMethod.Invoke($null, @((0..19 -as [Int32[]]), $FuncGuidGen)) }; }
Measure-Command { $OrderByMethod.Invoke($null, @((0..128KB -as [Int32[]]), $FuncGuidGen)) }
TotalSeconds : 0.0564569 TotalSeconds : 1.7335277