配列・コレクションをシャッフルする方法について。

配列・コレクション・IEnumerable<T>のシャッフル

Enumerable.OrderByメソッドを使って任意のIEnumerable<T>をシャッフルするためのメソッドの例。 OrderByメソッドに対してランダムなキーを与えることで、ランダムな順序に並べ替える。

この例では、ランダムなキーの生成にRandomNumberGeneratorクラスを使用しているが、Randomクラスを用いても同じように実装できる。 また、使いやすいように拡張メソッドとして呼び出せるようにしてある。

このメソッドはOrderByメソッドを用いているため、オリジナルのIEnumerable<T>には変更を加えない(非破壊的)。

任意のIEnumerable<T>をシャッフルする拡張メソッド
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;

public static class IEnumerableExtensions {
  public static IOrderedEnumerable<TSource> Shuffle<TSource>(this IEnumerable<TSource> source)
  {
    return Shuffle(source, RandomNumberGenerator.Create());
  }

  public static IOrderedEnumerable<TSource> Shuffle<TSource>(this IEnumerable<TSource> source, RandomNumberGenerator rng)
  {
    if (source == null)
      throw new ArgumentNullException("source");

    var bytes = new byte[4];

    return source.OrderBy(delegate(TSource e) {
      rng.GetBytes(bytes);

      return BitConverter.ToInt32(bytes, 0);
    });

    /* Randomクラスを使う場合
    var random = new Random();

    return source.OrderBy(delegate(TSource e) {
      return random.Next();
    });
    */
  }
}

class Sample {
  static void Main()
  {
    var col1 = new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

    Print(col1);
    Print(col1.Shuffle());

    var col2 = new Queue<string>(new[] {"foo", "bar", "baz"});

    Print(col2);
    Print(col2.Shuffle());

    var col3 = new List<int>();

    Print(col3);
    Print(col3.Shuffle());
  }

  private static void Print<T>(IEnumerable<T> source)
  {
    foreach (var val in source) {
      Console.Write(val);
      Console.Write(" ");
    }

    Console.WriteLine();
  }
}
任意のIEnumerable<T>をシャッフルする拡張メソッド
Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.Security.Cryptography

Module IEnumerableExtensions
  <System.Runtime.CompilerServices.Extension()> _
  Public Function Shuffle(Of TSource)(ByVal source As IEnumerable(Of TSource)) As IOrderedEnumerable(Of TSource)
    Return Shuffle(source, RandomNumberGenerator.Create())
  End Function

  Public Function Shuffle(Of TSource)(ByVal source As IEnumerable(Of TSource), ByVal rng As RandomNumberGenerator) As IOrderedEnumerable(Of TSource)
    If source Is Nothing Then Throw New ArgumentNullException("source")

    Dim bytes(3) As Byte

    Return source.OrderBy(Function(e)
      rng.GetBytes(bytes)

      Return BitConverter.ToInt32(bytes, 0)
    End Function)

    ' Randomクラスを使う場合
    'Dim random As New Random()
    '
    'Return source.OrderBy(Function(e)
    '  Return random.Next()
    'End Function)
  End Function
End Module

Class Sample
  Shared Sub Main()
    Dim arr1() As Integer = New Integer() {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

    Print(arr1)
    Print(arr1.Shuffle())

    Dim arr2 As New Queue(Of String)(New String() {"foo", "bar", "baz"})

    Print(arr2)
    Print(arr2.Shuffle())

    Dim arr3 As New List(Of Integer)()

    Print(arr3)
    Print(arr3.Shuffle())
  End Sub

  Private Shared Sub Print(Of T)(ByVal source As IEnumerable(Of T))
    For Each val As T In source
      Console.Write(val)
      Console.Write(" ")
    Next

    Console.WriteLine()
  End Sub
End Class
実行結果の例
0 1 2 3 4 5 6 7 8 9 
8 9 3 0 4 6 1 2 5 7 
foo bar baz 
baz bar foo 


Press any key to continue

配列のシャッフル

配列のシャッフルを行うためのメソッドの例。 使いやすいように拡張メソッドとして呼び出せるようにしてある。 オリジナルの配列には変更を加えず(非破壊的)、シャッフルした配列を戻り値として返す。

配列をシャッフルする拡張メソッド
using System;

public static class ArrayExtensions {
  public static T[] Shuffle<T>(this T[] array)
  {
    return Shuffle(array, new Random());
  }

  public static T[] Shuffle<T>(this T[] array, Random random)
  {
    if (array == null)
      throw new ArgumentNullException("array");
    if (random == null)
      throw new ArgumentNullException("random");

    var shuffled = (T[])array.Clone();

    if (shuffled.Length < 2)
      return shuffled;

    for (var i = 1; i < shuffled.Length; i++) {
      var j = random.Next(0, i + 1);

      // swap
      var temp    = shuffled[i];
      shuffled[i] = shuffled[j];
      shuffled[j] = temp;
    }

    return shuffled;
  }
}

class Sample {
  static void Main()
  {
    var arr1 = new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

    Print(arr1);
    Print(arr1.Shuffle());

    var arr2 = new string[] {"foo", "bar", "baz"};

    Print(arr2);
    Print(arr2.Shuffle());

    var arr3 = new int[] {};

    Print(arr3);
    Print(arr3.Shuffle());
  }

  private static void Print<T>(T[] arr)
  {
    foreach (var val in arr) {
      Console.Write(val);
      Console.Write(" ");
    }

    Console.WriteLine();
  }
}
配列をシャッフルする拡張メソッド
Imports System

Module ArrayExtensions
  <System.Runtime.CompilerServices.Extension()> _
  Public Function Shuffle(Of T)(ByVal array As T()) As T()
    Return Shuffle(array, New Random())
  End Function

  Public Function Shuffle(Of T)(ByVal array As T(), ByVal random As Random) As T()
    If array Is Nothing Then Throw New ArgumentNullException("array")
    If random Is Nothing Then Throw New ArgumentNullException("random")

    Dim shuffled As T() = DirectCast(array.Clone(), T())

    If shuffled.Length < 2 Then Return shuffled

    For i As Integer = 1 To shuffled.Length - 1
      Dim j As Integer = random.Next(0, i + 1)

      ' swap
      Dim temp As T = shuffled(i)
      shuffled(i) = shuffled(j)
      shuffled(j) = temp
    Next

    Return shuffled
  End Function
End Module

Class Sample
  Shared Sub Main()
    Dim arr1 As Integer() = New Integer() {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

    Print(arr1)
    Print(arr1.Shuffle())

    Dim arr2 As String() = New String() {"foo", "bar", "baz"}

    Print(arr2)
    Print(arr2.Shuffle())

    Dim arr3 As Integer() = New Integer() {0}

    Print(arr3)
    Print(arr3.Shuffle())
  End Sub

  Private Shared Sub Print(Of T)(ByVal arr As T())
    For Each val As T In arr
      Console.Write(val)
      Console.Write(" ")
    Next

    Console.WriteLine()
  End Sub
End Class
実行結果の例
0 1 2 3 4 5 6 7 8 9 
4 8 5 9 3 0 2 6 1 7 
foo bar baz 
bar foo baz 


Press any key to continue