C# で順列生成


順列のすべての組み合わせに対して網羅的に作業を行いたいということはままあることです。

対象は配列のようにランダムアクセスが容易な場合が普通ですので,配列のインデックスの順列を生成してやれば良いことになります。そこで,まずは配列のインデックスを列挙する補助クラスを作成します。

public class IndexEnumerator : IEnumerator<int[]>
{
	private int[] current;
	public int[] Current
	{
		get
		{
			return current;
		}
	}
	object System.Collections.IEnumerator.Current
	{
		get
		{
			return Current;
		}
	}

	private readonly int rank;
	public int Rank
	{
		get
		{
			return rank;
		}
	}

	private readonly int[] lengths;

	public IndexEnumerator(params int[] lengths)
	{
		if (lengths == null)
		{
			throw new ArgumentNullException("lengths");
		}
		rank = lengths.Length;
		if (rank == 0)
		{
			throw new ArgumentException("0 次元の配列。", "lengths");
		}
		for (int index = 0; index < rank; index++)
		{
			if (lengths[index] < 0)
			{
				throw new ArgumentOutOfRangeException("lengths");
			}
		}
		this.lengths = lengths;
		this.current = new int[rank];
		Reset();
	}

	public bool MoveNext()
	{
		for (int dimensionIndex = Rank - 1; dimensionIndex >= 0; dimensionIndex--)
		{
			int index = current[dimensionIndex];
			if (++index >= lengths[dimensionIndex])
			{
				current[dimensionIndex] = 0;
			}
			else
			{
				current[dimensionIndex] = index;
				return true;
			}
		}
		return false;
	}

	public void Reset()
	{
		current[Rank - 1] = -1;
		for (int index = 0; index < Rank - 1; index++)
		{
			current[index] = 0;
		}
	}

	void IDisposable.Dispose()
	{
	}
}

これを用いて様々な組み合わせを生成することができます。たとえば,「最初の文字が半角英大文字で 2 文字目と 3 文字目が数字の文字列」を生成したい場合は次のようにします。

char[] firstCharacters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".ToCharArray();
char[] tailingCharacters = "0123456789".ToCharArray();
int[] lengths = new int[] { firstCharacters.Length, tailingCharacters.Length, tailingCharacters.Length };
using (var enumerator = new IndexEnumerator(lengths))
{
	while (enumerator.MoveNext())
	{
		int[] indices = enumerator.Current;
		char[] characters = new char[lengths.Length];
		characters[0] = firstCharacters[indices[0]];
		characters[1] = tailingCharacters[indices[1]];
		characters[2] = tailingCharacters[indices[2]];
		yield return new String(characters);
	}
}

この例だと普通に for ループで回しても簡単にできます。しかし,単純な for ループでは 3 重のループで深いネストになってしまいますが,この例では while の 1 重ネストですんでいます。