【C#】ジェネリックメソッドの学習

次のような課題があった時の解決を通してメリットを見ていきます

解決したい要件

LINQを学習された方は、そちらの方がシンプルに構成できると思われると思いますが、今回は学習目的なので分解して考えてみましょう

要件

リスト内の要素をシャッフルしたい

サンプル

次のようなリストを想定します

 List<int> intList = new List<int> { 1, 2, 3, 4, 5 };

得られる結果

登録時には昇順できたが、結果はランダムな並びになります

3, 2, 5, 1, 4

要件を解決するサンプルコード

呼び出す側コード

引数は参照型なので、戻り値がなくてもリスト自体が更新されます
受け渡すのはリストがどの場所(メモリの番地)にあるかの情報なのでですね

ShuffleList(intList);

Console.WriteLine("Int リストのシャッフル : " + string.Join(", ", intList));

呼び出されるメソッド

Fisher-Yates シャッフルアルゴリズムを使用しています

void ShuffleList(List<int> list)
{
    Random random = new Random();

    int n = list.Count;
    while (n > 1)
    {
        n--;
        int k = random.Next(n + 1);
        int value = list[k];
        list[k] = list[n];
        list[n] = value;
    }
}

実は要件をよくみると

はい、整数と規定していませんね
小数点数の場合はどうなるでしょう

intのところを全てfloatに変更する必要がありますね

また、メソッド名は統一にすることはできるでしょうが(オーバーロード)、倍のコードが必要なことに変わりがありません

1つのメソッドで複数の型を扱えればいいのに・・・

こんな感じで呼び出したい

List<int> intList = new List<int> { 1, 2, 3, 4, 5 };
ShuffleList(intList);
Console.WriteLine("Int リストのシャッフル : " + string.Join(", ", intList));

List<float> floatList = new List<float> { 1.3f, 2.5f, 3.2f, 4.6f, 5.9f };
ShuffleList(floatList);
Console.WriteLine("Float リストのシャッフル : " + string.Join(", ", floatList));

解決するコード

intとはfloatとか入るところをGenericTypeに置き換えます
つまり、このGenericTypeには型を自由に入れることができるわけですね

void ShuffleList<T>(List<T> list)
{
    Random random = new Random();

    int n = list.Count;
    while (n > 1)
    {
        n--;
        int k = random.Next(n + 1);
        T value = list[k];
        list[k] = list[n];
        list[n] = value;
    }
}

一般的には、GenericTypeのところを簡略化してTとします

List<int> intList = new List<int> { 1, 2, 3, 4, 5 };
ShuffleList(intList);
Console.WriteLine("Int リストのシャッフル : " + string.Join(", ", intList));

List<float> floatList = new List<float> { 1.3f, 2.5f, 3.2f, 4.6f, 5.9f };
ShuffleList(floatList);
Console.WriteLine("Float リストのシャッフル : " + string.Join(", ", floatList));

void ShuffleList<T>(List<T> list)
{
    Random random = new Random();

    int n = list.Count;
    while (n > 1)
    {
        n--;
        int k = random.Next(n + 1);
        T value = list[k];
        list[k] = list[n];
        list[n] = value;
    }
}

おまけ

値の入れ替えにタプルを使う

void ShuffleList<T>(List<T> list)
{
    Random random = new Random();

    int n = list.Count;
    while (n > 1)
    {
        n--;
        int k = random.Next(n + 1);
        (list[k], list[n]) = (list[n], list[k]);
    }
}

元のオリジナルのリストは残しておいて、新しくリストを作る

List<int> intList = new List<int> { 1, 2, 3, 4, 5 };
List<int> shuffledIntList = ShuffleList(intList);
Console.WriteLine("Int リストのシャッフル : " + string.Join(", ", shuffledIntList));

List<float> floatList = new List<float> { 1.3f, 2.5f, 3.2f, 4.6f, 5.9f };
List<float> shuffledFloatList = ShuffleList(floatList);
Console.WriteLine("Float リストのシャッフル : " + string.Join(", ", shuffledFloatList));


List<T> ShuffleList<T>(List<T> list)
{
    Random random = new Random();
    List<T> shuffledList = list.ToList(); // オリジナルリストのコピーを作成

    int n = shuffledList.Count;
    while (n > 1)
    {
        n--;
        int k = random.Next(n + 1);
        (shuffledList[k], shuffledList[n]) = (shuffledList[n], shuffledList[k]);
    }

    return shuffledList; // シャッフルされたリストを返す
}

自作クラスのインスタンスをシャッフル

MyClass myClass1 = new MyClass { hp = 1 };
MyClass myClass2 = new MyClass { hp = 2 };
MyClass myClass3 = new MyClass { hp = 3 };

List<MyClass> myClassList = new List<MyClass> { myClass1, myClass2, myClass3 };
List<MyClass> shuffledMyClassList = ShuffleList(myClassList);

Console.Write("MyClass リストのシャッフル : ");
shuffledMyClassList.ForEach(myClass => Console.Write(myClass.hp + ", "));

class MyClass
{
    public int hp;
}

結果サンプル

MyClass リストのシャッフル : 3, 2, 1, 

番外編

学習目的でジェネリックメソッドの作成を進めてきましたが、LINQを使えば次のコードでできます

List<int> shuffledList = intList.OrderBy(x => Guid.NewGuid()).ToList();
  1. OrderByメソッドはLINQ(Language Integrated Query)の一部であり、要素の並べ替えを行うために使用されています。このメソッドは、指定したキーに基づいて要素を並べ替えることができます。ここでは、x => Guid.NewGuid()というラムダ式がキーとして指定されています。
  2. Guid.NewGuid()は、ランダムなグローバル一意識別子(GUID)を生成するメソッドです。各要素に対して新しいGUIDが生成されます。このGUIDはランダム性を持つため、要素の順序をランダムにするのに適しています。

シンプルなコード

List<int> intList = new List<int> { 1, 2, 3, 4, 5 };
// シャッフルする
List<int> shuffledIntList = intList.OrderBy(x => Guid.NewGuid()).ToList();

Console.WriteLine("Int リストのシャッフル : " + string.Join(", ", shuffledIntList));

解説動画

C#

Posted by hidepon