将数组改组到与原始数组匹配的位置后,为什么将它们等同 return 为假?

After shuffling an array to the point where it matches the original, why does equating them return false?

考虑以下洗牌方法,给定一个对象数组a

  1. a中取出第一个元素并将其放入b。考虑 b 中此元素的索引为 x.
  2. a 中的第二个元素放在 b[x] 的前面,这样它现在就在 b[x-1]
  3. 的位置
  4. a 中的第三个元素放在 b[x] 后面,这样它现在就在 b[x+1]
  5. 的位置
  6. a中的第四个元素放在b[x - 1]的前面,这样它现在就在b[x-2]
  7. 的位置
  8. a 中的第 5 个元素放在 b[x+1] 后面,使其现在位于 b[x+2]
  9. 的位置
  10. 重复此过程,直到 b 以新的随机顺序包含 a 中的所有元素。

我写了一些代码来执行此操作,如下所示。它会在上面的过程中不断对数组进行shuffle,直到shuffle后的数组与原数组匹配,然后return shuffle的次数

public class BadShuffler
{
    public BadShuffler(object[] _arrayToShuffle)
    {
        originalArray = _arrayToShuffle;
        Arrays = new List<object[]>
        {
            originalArray
        };
    }

    private object[] originalArray;
    private int count;
    public List<object[]> Arrays { get; set; }

    public int Shuffle(object[] array = null)
    {
        if (array == null)
            array = originalArray;

        count++;
        object[] newArray = new object[array.Length];
        bool insertAtEnd = false;
        int midpoint = newArray.Length / 2;
        newArray[midpoint] = array[0];
        int newArrayInteger = 1;
        int originalArrayInteger = 1;

        while (newArray.Any(x => x == null))
        {
            if (insertAtEnd)
            {
                newArray[midpoint + newArrayInteger] = array[originalArrayInteger];
                newArrayInteger++;
            }
            else
            {
                newArray[midpoint - newArrayInteger] = array[originalArrayInteger];
            }

            originalArrayInteger++;
            insertAtEnd = !insertAtEnd;
        }

        Arrays.Add(newArray);
        return (newArray.All(x => x == originalArray[Array.IndexOf(newArray, x)])) ? count : Shuffle(newArray);
    }
}

虽然不是世界上最漂亮的东西,但它确实可以。示例如下:

Shuffled 6 times.

1, 2, 3, 4, 5, 6

6, 4, 2, 1, 3, 5

5, 1, 4, 6, 2, 3

3, 6, 1, 5, 4, 2

2, 5, 6, 3, 1, 4

4, 3, 5, 2, 6, 1

1, 2, 3, 4, 5, 6

但是,如果我给它一个 [1, 2, 3, 3, 4, 5, 6] 的数组,它最终会抛出 WhosebugException。然而,在调试时,我发现它确实达到了新的混洗数组与原始数组匹配的程度,如下所示。

然后继续调用 Shuffle(newArray),即使数组中的所有值都相互匹配。

这是什么原因造成的?为什么 Linq 查询 newArray.All(x => x == originalArray[Array.IndexOf(newArray, x)]) return false?

这是一个DotNetFiddle link,其中包括我用来打印结果的代码

您正在比较 objects。 object 使用与 == 的引用相等进行比较,而不是值相等。您的示例使用数字,但由于您的代码布局方式,这些数字被隐式装箱到 object

为避免这种情况,您应该使用 .Equals() 函数(比较对象时)。

newArray.All(x => x.Equals(originalArray[Array.IndexOf(newArray, x)]))

您还应该在 class 中使用泛型,而不是到处乱扔垃圾 object[] 以确保类型安全 - 除非您使用此洗牌器的目的之一是允许洗牌器洗牌混合类型的数组(这似乎值得怀疑,因为很难从中提取任何有用的信息)。

请注意,每当您比较引用类型时都会出现此行为;仅允许 value 类型传递给您的结构的一种方法(即,只能通过值相等而不是引用相等来比较原始值)是使用 struct通用约束。例如:

class BadShuffler<T> where T : struct
{
    public bool Shuffle(T[] array)
    {
        ...
        return newArray.All(x => {
            var other = originalArray[Array.IndexOf(originalArray, x)];
            return x == other;
        });
    }
}

这会像您预期的那样工作。

评论中提到的

SequenceEqual 也是一个好主意,因为您的 .All() 调用会说 [1, 2, 3] 等于 [1, 2, 3, 4],但是 [1, 2, 3, 4] 将不等于 [1, 2, 3] - 这两种情况都是不正确的,更重要的是不是可交换的[1],相等操作应该是。

如果您不使用 object[],请确保实现您自己的 EqualityComparer。

也就是说,我认为您想结合使用这两种方法并使用 SequenceEqual 和我的方法,除非您需要洗牌对象(即一副纸牌)而不是数字?


作为旁注,我通常建议返回 ,打乱 T[] 而不是修改原始 in-place.

[1]:可交换意味着以一种方式完成的操作可以以相反的方式完成,并且您会得到相同的结果。例如,加法是可交换的:您可以按任何顺序将 1、2 和 3 相加,但结果始终为 6。