如何通过仅交互一次来交换枚举项



我正在尝试交换IEnumerable特定项目的顺序。

给定元素的IEnumerable<int> a;

1, 2, 3, 4, 5

我想做的是编写一个交换迭代器,它导致a.Exchange(1, 2)

1, 3, 2, 4, 5

但我不希望枚举项以这个简单的目的多次迭代。到目前为止,我所拥有的是:

public static IEnumerable<T> Exchange<T>(
    this IEnumerable<T> source, int index1, int index2) {
    var i=0;
    foreach(var y in source) {
        if(index1==i) {
            var j=0;
            foreach(var x in source) {
                if(index2==j) {
                    yield return x;
                    break;
                }
                ++j;
            }
        }
        else {
            if(index2==i) {
                var j=0;
                foreach(var x in source) {
                    if(index1==j) {
                        yield return x;
                        break;
                    }
                    ++j;
                }
            }
            else {
                yield return y;
            }
        }
        ++i;
    }
}

下面是一个假设,即index1index2不会超过可枚举元素。在大多数情况下,代码完成了交换(排序(的工作,但它确实迭代了不止一次。注意index1index2可能不是source的真正索引,它们将是枚举发生时的MthNth元素。

ToArrayToList也可能增加迭代次数。

WOLOG 假设index1小于 index2

创建自己的枚举器;不要使用foreach

对于最多 index1 个元素,正常迭代并产生每个元素。

然后,当您点击 index1 时,分配一个足够大的数组来容纳index1index2之间的元素——也就是说,包括第 index1 个元素,但不包括第 index2 个元素。

使用枚举器将元素读入该数组。

现在读取第 index2 个元素并生成它。

枚举器现在设置为 1 超出 index2

现在生成数组中除第 index1 个元素之外的所有元素。

然后生成第 index1 个元素。

然后正常生成其余元素。

完成后,不要忘记在

枚举器上调用Dispose

为了在不多次迭代原始数据的情况下执行此操作,您需要在要交换的索引之间存储子序列的内容。

以下是实现算法的方法(我将index1index2重命名为smallerIndexgreaterIndex(:

using (IEnumerator<T> e = source.GetEnumerator()) {
    IList<T> saved = new List<T>(greaterIndex-smallerIndex+1);
    int index = 0;
    while (e.MoveNext()) {
        // If we're outside the swapped indexes, yield return the current element
        if (index < smallerIndex || index > greaterIndex) {
            index++;
            yield return e.Current;
        } else if (index == smallerIndex) {
            var atSmaller = e.Current;
            // Save all elements starting with the current one into a list;
            // Continue until you find the last index, or exhaust the sequence.
            while (index != greaterIndex && e.MoveNext()) {
                saved.Add(e.Current);
                index++;
            }
            // Make sure we're here because we got to the greaterIndex,
            // not because we've exhausted the sequence
            if (index == greaterIndex) {
                // If we are OK, return the element at greaterIndex
                yield return e.Current;
            }
            // Enumerate the saved items
            for (int i = 0 ; i < saved.Count-1 ; i++) {
                yield return saved[i];
            }
            // Finally, return the item at the smallerIndex
            yield return atSmaller;
            index++;
        }
    }
}

在 ideone 上演示。

最简单的方法可能是这样的:

public static IEnumerable<T> Exchange<T>(
    this IEnumerable<T> source, int index1, int index2) 
{
    return source.Select((x, i) => new { x, i })
                 .OrderBy(p => p.i == index1 ? index2 : p.i == index2 ? index1 : p.i)
                 .Select(p => p.x);
}
new[] { 1, 2, 3, 4, 5 }.Exchange(1, 2); // { 1, 3, 2, 4, 5 }

在没有OrderBy的情况下做到这一点,我认为它看起来像这样:

public static IEnumerable<T> Exchange<T>(
    this IEnumerable<T> source, int index1, int index2) 
{
    if (index1 > index2)
    {
        int x = index1;
        index1 = index2;
        index2 = x;
    }
    int index = 0;
    List<T> itemsBetweenIndexes = new List<T>();
    bool betweenIndexes = false;
    T temp = default(T);
    foreach(var item in source)
    {
        if (!betweenIndexes)
        {
            if (index == index1)
            {
                temp = item;
                betweenIndexes = true;
            }
            else
            {
                yield return item;
            }
        }
        else
        {
            if (index == index2)
            {
                betweenIndexes = false;
                yield return item;
                foreach(var x in itemsBetweenIndexes)
                {
                    yield return x;
                }
                itemsBetweenIndexes.Clear();
                yield return temp;
            }
            else
            {
                itemsBetweenIndexes.Add(item);
            }
        }
        index++;
    }
}
最初,这循环通过

查找index1的项目,产生每个项目,直到找到它。找到后,它开始向内部队列添加项目,直到找到index2。此时,它按顺序生成 index2 处的项目,然后是队列中的每个项目,然后是 index1 处的项目。然后,它会返回到查找index1(它不会找到(,直到到达列表的末尾。

您可以通过创建一个List<T>然后将其作为IEnumerable<T>类型返回来一次性完成:

public static IEnumerable<T> Exchange<T>(this IEnumerable<T> source, int index1, int index2)
{
    // TODO: check index1 and index2 are in bounds of List/Enumerable
    var result = source.ToList(); // single enumeration
    // Swap vars
    var temp = result[index1];
    result[index1] = result[index2];
    result[index2] = temp;
    return result;
}

最新更新