我正在尝试交换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
不会超过可枚举元素。在大多数情况下,代码完成了交换(排序(的工作,但它确实迭代了不止一次。注意index1
和index2
可能不是source
的真正索引,它们将是枚举发生时的Mth
和Nth
元素。
ToArray
或ToList
也可能增加迭代次数。
WOLOG 假设index1
小于 index2
。
创建自己的枚举器;不要使用foreach
。
对于最多 index1
个元素,正常迭代并产生每个元素。
然后,当您点击 index1
时,分配一个足够大的数组来容纳index1
到index2
之间的元素——也就是说,包括第 index1
个元素,但不包括第 index2
个元素。
使用枚举器将元素读入该数组。
现在读取第 index2
个元素并生成它。
枚举器现在设置为 1 超出 index2
。
现在生成数组中除第 index1
个元素之外的所有元素。
然后生成第 index1
个元素。
然后正常生成其余元素。
完成后,不要忘记在枚举器上调用Dispose
。
为了在不多次迭代原始数据的情况下执行此操作,您需要在要交换的索引之间存储子序列的内容。
以下是实现算法的方法(我将index1
和index2
重命名为smallerIndex
和greaterIndex
(:
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;
}