为什么 List<T>.RemoveRange(index, count) 在索引之前更改值?

Why List<T>.RemoveRange(index, count) changes value before index?

我正在实现一个经典的遗传算法。在交叉阶段,我发现了一些奇怪的行为。

private static void Crossover(ref List<CrossoverPair> pairs)
{
    var random = new Random();
    //TODO debug this
    foreach (var pair in pairs)
    {
        for (var i = 0; i < pair.First.Chromosomes.Count; i++)
        {
            var locus = random.Next(1, 12);
            var crossoverLength = pair.First.Chromosomes[i].Genes.Count - locus;
            var swapFirst = pair.First.Chromosomes[i].Genes.Skip(locus).Take(crossoverLength).ToList();
            var swapSecond = pair.Second.Chromosomes[i].Genes.Skip(locus).Take(crossoverLength).ToList();
            pair.First.Chromosomes[i].Genes.RemoveRange(locus - 1, crossoverLength);
            pair.First.Chromosomes[i].Genes.AddRange(swapSecond);
            pair.Second.Chromosomes[i].Genes.RemoveRange(locus - 1, crossoverLength);
            pair.Second.Chromosomes[i].Genes.AddRange(swapFirst);
        }
    }
}

每条染色体包含12个基因。它从随机定义的基因座开始交换同源部分。例如,如果我们有 locus = 8crossoverLength = 4,我们首先使用 RemoveRangeGenes[8] 删除基因到 Genes[11],然后我们使用 RemoveRange 添加来自另一个染色体的基因 AddRange.

有时会发生一些奇怪的事情:当我们使用 RemoveRange 时,Genes[7](对于本例)将其值从 0 更改为 1 或从 1 更改为 0。它不会在每次迭代中发生,有时一切正常。我注意到 locus = 7..11.

更常发生这种情况

它不会对算法造成太大伤害(只是更多的突变 :D)。但是有人知道为什么它会反转值吗?

更新:

非常感谢 BJ Myers 的无懈可击的回答。 稍后阅读此 post 的其他人可能会对为什么会这样感兴趣。解释的不错here.

RemoveRange 未更改指定索引之前的值。看起来是这样的原因是因为你的索引差了一个。

看看这一行:

pair.First.Chromosomes[i].Genes.RemoveRange(locus - 1, crossoverLength);

如果我们假设(如您的示例)locus = 8,因此假设 crossoverLength = 4,则所需的行为是删除索引为 [8][11] 的元素。但是,由于您从 locus 中减去一个,因此您将 7 作为第一个参数传递给 RemoveRange,因此从 [10] 中删除了元素 [7]

对于 RemoveRange:

的任一调用,正确的代码不应包含 - 1 偏移量
pair.First.Chromosomes[i].Genes.RemoveRange(locus, crossoverLength);

您认为元素 [7] 发生变化的行为实际上是元素 [7][10] 被删除以及之前位于 [11] 的元素向下移动的结果进入位置 [7]。如果您的 "genes" 始终是二进制的,那么作为 RemoveRange 调用的结果,该值有 50/50 的可能性 "change"。