将两个相同的数组随机化而不重叠的最佳方法?

时间:2015-12-29 01:11:15

标签: c# arrays sorting random

假设我们有两个相同的数组{"A", "B", "C", "D", "E", "F"}。有没有一种快速的方法来随机化每个顺序,确保两个排队时,相同的字母永远不会在同一个索引? (显然我们可以生成一个新的索引,如果它会导致匹配,但我想知道是否有一种产生较少重复的方法)。

4 个答案:

答案 0 :(得分:3)

这很有效,我认为这很容易理解。

var source = new [] { "A", "B", "C", "D", "E", "F" };

var output1 = (string[])null;
var output2 = (string[])null;

var rnd = new Random();
Action shuffle = () =>
{
    output1 = source.OrderBy(x => rnd.Next()).ToArray();
    output2 = source.OrderBy(x => rnd.Next()).ToArray();
};

shuffle();
while (output1.Zip(output2, (o1, o2) => new { o1, o2 })
    .Where(x => x.o1 == x.o2)
    .Any())
{
    shuffle();
}

答案 1 :(得分:2)

您可以分两步执行O(n)复杂性。

[第1步] 以每个字母改变其原始位置的方式对一个数组进行随机播放,如下所示:

var rnd = new Random(0);
var x = new char[] { 'A', 'B', 'C', 'D', 'E', 'F' };
for(int i = 0; i < x.Length; i++)
{
    var j0 = (i == x[i] - 'A')? i + 1: i;
    var j = rnd.Next(j0, x.Length);
    // x[i] ⟷ x[j]
    var t = x[i]; x[i] = x[j]; x[j] = t;
}

确保第一个和第二个数组在每个位置都不同。

[第2步] 同步使用Fisher–Yates shuffle 两个阵列

var y = new char[] { 'A', 'B', 'C', 'D', 'E', 'F' };
for(int i = 0; i < x.Length; i++)
{
    var j = rnd.Next(i, x.Length);
    // x[i] ⟷ x[j]; y[i] ⟷ y[j]
    var
    t = x[i]; x[i] = x[j]; x[j] = t;
    t = y[i]; y[i] = y[j]; y[j] = t;
}

确保两者随机化,在每个位置保持差异。

答案 2 :(得分:0)

我最好的建议是制作你自己的随机函数方法,该方法有两个参数:数组要被洗牌,数组不允许匹配。

这是一个包含2个字符串数组的类的快速示例,它将通过调用(objectName)来进行混洗.Shuffle();

public class ArrayShuffler {
    public String[] arr1;
    public String[] arr2;

    public ArrayShuffler() {
        arr1 = new String[] { "A", "B", "C", "D", "E", "F" };
        arr2 = new String[] { "A", "B", "C", "D", "E", "F" };
    }
    public void Shuffle() {
        shuffleArr(arr1);
        shuffleArr(arr2, arr1);
    }

    /// <summary>
    /// Can shuffle array, maching against a second array to prevent dublicates in same intex spot.
    /// </summary>
    /// <param name="arr">Array to be shuffled</param>
    /// <param name="validate">Array to mach against</param>
    private void shuffleArr(String[] arr, String[] validate = null) {
        Random r = new Random();
        int indx = 0;
        while(indx < arr.Length){
            int rIndx = r.Next(indx, arr.Length);
            string tmp = arr[indx];
            if(validate != null) { //Will only be performed if you specify an array to be matched against.
                if(arr[rIndx] != validate[indx]) {
                    arr[indx] = arr[rIndx];
                    arr[rIndx] = tmp;
                    indx++;
                }
                else if(indx == arr.Length - 1) {
                    shuffleArr(arr, validate);
                }
            }
            else { //Default operation
                arr[indx] = arr[rIndx];
                arr[rIndx] = tmp;
                indx++;
            }
        }
    }
}

答案 3 :(得分:0)

假设您正在尝试最小化不必要的重新滚动的数量,并且两个结果必须与彼此不匹配(允许输出字符在一个特定的索引来匹配该索引输入中的字符),我想我已经为你找到了解决方案。

它的要点是我们动态地构建结果字符串,跟踪每个列表中没有选择哪些字符,并暂时从我们选择的候选项中删除我们为特定索引首先选择的字符串第二个。我不认为这种方法有任何偏见,但我无疑是这方面的专家。

public void Shuffle(int seed)
{
    char[] orig = { 'A', 'B', 'C', 'D', 'E', 'F' };
    List<char> buffer1 = new List<char>();
    List<char> buffer2 = new List<char>();

    // Keep track of which indexes haven't yet been used in each buffer.
    List<int> availableIndexes1 = new List<int>(orig.Length);
    List<int> availableIndexes2 = new List<int>(orig.Length);

    for (int i = 0; i < orig.Length; i++)
    {
        availableIndexes1.Add(i);
        availableIndexes2.Add(i);
    }

    Random rand = new Random(seed);

    // Treat the last 2 specially.  See after the loop for details.
    for (int i = 0; i < orig.Length - 2; i++)
    {
        // Choose an arbitrary available index for the first buffer.
        int rand1 = rand.Next(availableIndexes1.Count);

        int index1 = availableIndexes1[rand1];

        // Temporarily remove that index from the available indices for the second buffer.
        // We'll add it back in after if we removed it (note that it's not guaranteed to be there).
        bool removed = availableIndexes2.Remove(index1);
        int rand2 = rand.Next(availableIndexes2.Count);
        int index2 = availableIndexes2[rand2];
        if (removed)
        {
            availableIndexes2.Add(index1);
        }

        // Add the characters we selected at the corresponding indices to their respective buffers.
        buffer1.Add(orig[index1]);
        buffer2.Add(orig[index2]);

        // Remove the indices we used from the pool.
        availableIndexes1.RemoveAt(rand1);
        availableIndexes2.RemoveAt(rand2);
    }

    // At this point, we have 2 characters remaining to add to each buffer.  We have to be careful!
    // If we didn't do anything special, then we'd end up with the last characters matching.
    // So instead, we just flip up to a fixed number of coins to figure out the swaps that we need to do.
    int secondToLastIndex1Desired = rand.Next(2);
    int secondToLastIndex2Desired = rand.Next(2);

    // If the "desired" (i.e., randomly chosen) orders for the last two items in each buffer would clash...
    if (availableIndexes1[secondToLastIndex1Desired] == availableIndexes1[secondToLastIndex2Desired] ||
        availableIndexes1[(secondToLastIndex1Desired + 1) % 2] == availableIndexes2[(secondToLastIndex2Desired + 1) % 2])
    {
        // ...then swap the relative order of the last two elements in one of the two buffers.
        // The buffer whose elements we swap is also chosen at random.
        if (rand.Next(2) == 0)
        {
            secondToLastIndex1Desired = (secondToLastIndex1Desired + 1) % 2;
        }
        else
        {
            secondToLastIndex2Desired = (secondToLastIndex2Desired + 1) % 2;
        }
    }
    else if (rand.Next(2) == 0)
    {
        // Swap the last two elements in half of all cases where there's no clash to remove an affinity
        // that the last element has for the last index of the output, and an affinity that the first
        // element has for the second-to-last index of the output.
        int t = secondToLastIndex1Desired;
        secondToLastIndex1Desired = secondToLastIndex2Desired;
        secondToLastIndex2Desired = t;
    }

    buffer1.Add(orig[availableIndexes1[secondToLastIndex1Desired]]);
    buffer1.Add(orig[availableIndexes1[(secondToLastIndex1Desired + 1) % 2]]);

    buffer2.Add(orig[availableIndexes2[secondToLastIndex2Desired]]);
    buffer2.Add(orig[availableIndexes2[(secondToLastIndex2Desired + 1) % 2]]);

    Console.WriteLine(new string(buffer1.ToArray()));
    Console.WriteLine(new string(buffer2.ToArray()));
}

请注意,如果这用于特别是长数组,那么从List<T>.Remove / List<T>.RemoveAt移动的数据和前者完成的线性搜索可能无法很好地扩展。