如何只通过一次交换来交换枚举项?

时间:2013-06-03 21:27:29

标签: c# linq ienumerable enumeration

我正在尝试交换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也可能会增加迭代次数。

4 个答案:

答案 0 :(得分:8)

WOLOG认为index1小于index2

建立自己的普查员;不要使用foreach

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

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

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

现在阅读index2元素并将其生成。

您的枚举器现在设置为index2以外的一个。

现在产生数组中的所有内容,除了 index1元素。

然后产生index1元素。

然后正常产生其余元素。

完成后不要忘记在枚举器上调用Dispose

答案 1 :(得分:2)

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

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(它将找不到),直到它到达列表的末尾。

答案 2 :(得分:2)

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

以下是如何实施该算法(我将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++;
        }
    }
}

Demo on ideone

答案 3 :(得分:0)

您可以通过创建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;
}