0

我正在尝试交换特定项目的顺序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;
    }
}

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

ToArray或者ToList也可能增加迭代次数。

4

4 回答 4

8

WOLOG 假设index1小于index2.

制作自己的枚举器;不要使用foreach.

对于不超过 的元素index1,正常迭代并产生每个。

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

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

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

您的枚举器现在设置为超过 1 index2

现在产生数组中index1th 元素之外的所有内容。

然后产生第index1th 个元素。

然后正常产生其余元素。

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

于 2013-06-03T21:36:20.773 回答
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按顺序产生 at 的项目,然后是队列中的每个项目,然后是 at 的项目index1。然后它会返回寻找index1(它不会找到),直到它到达列表的末尾。

于 2013-06-03T21:34:26.720 回答
2

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

以下是实现算法的方法(我将 and 重命名index1index2and 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 上的演示

于 2013-06-03T21:38:52.187 回答
0

您可以通过创建 aList<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;
}
于 2013-06-03T21:36:27.193 回答