PowerShell で 配列をシャッフルする

Pocket

コード

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

参考: 配列やコレクションをシャッフルする(ランダムに並び替える): .NET Tips: C#, VB.NET

コメントを残す

メールアドレスが公開されることはありません。

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください