FiveHeartsの技術記事

Listの重複した要素を取り出す方法

ゲーム開発において、Listの中から重複した要素を抽出したい場面に
遭遇した場合の実装方法について記述しておきます。
まずはDuplicationCheckerクラスを作ります。
これは引数となるリストの要素に重複があった場合に、
重複したリストの要素を返すクラスになります。

				  
using System;
using System.Linq;
using System.Collections.Generic;

public class DuplicationChecker
{
    //引数のリストに重複があればその要素のリストを返す
    public static List<T> Check<T>(List<T> list)
    {
        List<T> result = list.GroupBy(x => x)
                        .Where(g => g.Count() > 1)
                        .Select(x => x.Key)
                        .ToList();
        return result;
    }
}
				  
				

以下はその実例です。(処理部分のみusingは省略)
3体の敵が0,1,2,3,4のいずれかのx,y座標を取りうる時に
(つまり25通りの中からランダムに3つ選ばれる)
重複が発生していたら、座標の割り振りをやり直すという処理になります。
これでランダムに決定する敵シンボルの座標が重なることを回避できます。
この実例ではduplication.Count > 0で判定を取っているので
直接的に要素を抽出していませんがduplication[0]などで
直接要素にアクセスすることももちろん可能です。
(NULLへのアクセスには気を付けて下さい)

				
List<Vector2Int> enemypos;
int enemycount;
bool retry = true;

private void EnemySetting(){
//敵の数だけ座標をセット
	enemycount = 3;
	enemypos = new List<Vector2Int>();
	List<Vector2Int> duplication = new List<Vector2Int>();

	while (retry == true)
	{
		duplication.Clear();
		enemypos.Clear();
		for (int i = 0; i < enemycount; i++)
		{
			int x = Random.Range(0, 5);
			int y = Random.Range(0, 5);
			Vector2Int pos = new Vector2Int(0, 0);
			pos.x = x;
			pos.y = y;
			enemypos.Add(pos);
		}
		foreach (var v in DuplicationChecker.Check<Vector2Int>(enemypos))
		{
			duplication.Add(v);
		}
		if (duplication.Count > 0)
		{
			retry = true;
		}
		else
		{
			retry = false;
		}
	}
}
				
				

あと初心者の時にやりがちなのが、Random.Rangeの範囲です。
intだと最大値を候補に含まないので注意が必要です。
逆にfloatは最大値を候補に含みます。
あと当たり前ですが敵の数が多すぎるとこの実例では
Whileループを中々抜けれなくなるので
厳密に重複した座標のみを再抽選する場合はもう少し工夫が必要です。
25個の中からそれぞれが3つを選び取りうるパターンは15625通り
重複しないようにそれぞれ3つが選び取るパターンは13800通り
88.32%でそもそも重複せずに選ばれます。
つまりwhileループを3周以上する確率は0.2%しかありません。
実装には実用上どうかという点も加味して
実装方法を選択する必要があります。

今回の記事はここまでです。
それではまた別の記事でお会いしましょう。