没有现有的交换方法,所以你必须自己创建一个。当然你可以对其进行 linqify,但必须牢记一个(不成文的?)规则:LINQ 操作不会更改输入参数!
在其他“linqify”答案中,(输入)列表被修改并返回,但此操作破坏了该规则。如果您有一个包含未排序项目的列表会很奇怪,请执行 LINQ“OrderBy”操作,然后发现输入列表也已排序(就像结果一样)。这是不允许的!
那么……我们该怎么做呢?
我的第一个想法是在完成迭代后恢复集合。但这是一个肮脏的解决方案,所以不要使用它:
static public IEnumerable<T> Swap1<T>(this IList<T> source, int index1, int index2)
{
// Parameter checking is skipped in this example.
// Swap the items.
T temp = source[index1];
source[index1] = source[index2];
source[index2] = temp;
// Return the items in the new order.
foreach (T item in source)
yield return item;
// Restore the collection.
source[index2] = source[index1];
source[index1] = temp;
}
这个解决方案很脏,因为它确实修改了输入列表,即使它将输入列表恢复到原始状态。这可能会导致几个问题:
- 该列表可能是只读的,这将引发异常。
- 如果列表由多个线程共享,则在此函数执行期间,其他线程的列表将发生变化。
- 如果在迭代过程中出现异常,列表将不会被恢复。 (这可以通过在 Swap 函数中编写 try-finally 来解决,并将 restore-code 放在 finally-block 中)。
有一个更好(更短)的解决方案:只需复制原始列表即可。 (这也使得使用 IEnumerable 作为参数而不是 IList 成为可能):
static public IEnumerable<T> Swap2<T>(this IList<T> source, int index1, int index2)
{
// Parameter checking is skipped in this example.
// If nothing needs to be swapped, just return the original collection.
if (index1 == index2)
return source;
// Make a copy.
List<T> copy = source.ToList();
// Swap the items.
T temp = copy[index1];
copy[index1] = copy[index2];
copy[index2] = temp;
// Return the copy with the swapped items.
return copy;
}
这个解决方案的一个缺点是它会复制整个列表,这会消耗内存,这使得解决方案相当慢。
您可以考虑以下解决方案:
static public IEnumerable<T> Swap3<T>(this IList<T> source, int index1, int index2)
{
// Parameter checking is skipped in this example.
// It is assumed that index1 < index2. Otherwise a check should be build in and both indexes should be swapped.
using (IEnumerator<T> e = source.GetEnumerator())
{
// Iterate to the first index.
for (int i = 0; i < index1; i++)
yield return source[i];
// Return the item at the second index.
yield return source[index2];
if (index1 != index2)
{
// Return the items between the first and second index.
for (int i = index1 + 1; i < index2; i++)
yield return source[i];
// Return the item at the first index.
yield return source[index1];
}
// Return the remaining items.
for (int i = index2 + 1; i < source.Count; i++)
yield return source[i];
}
}
如果你想输入参数为 IEnumerable:
static public IEnumerable<T> Swap4<T>(this IEnumerable<T> source, int index1, int index2)
{
// Parameter checking is skipped in this example.
// It is assumed that index1 < index2. Otherwise a check should be build in and both indexes should be swapped.
using(IEnumerator<T> e = source.GetEnumerator())
{
// Iterate to the first index.
for(int i = 0; i < index1; i++)
{
if (!e.MoveNext())
yield break;
yield return e.Current;
}
if (index1 != index2)
{
// Remember the item at the first position.
if (!e.MoveNext())
yield break;
T rememberedItem = e.Current;
// Store the items between the first and second index in a temporary list.
List<T> subset = new List<T>(index2 - index1 - 1);
for (int i = index1 + 1; i < index2; i++)
{
if (!e.MoveNext())
break;
subset.Add(e.Current);
}
// Return the item at the second index.
if (e.MoveNext())
yield return e.Current;
// Return the items in the subset.
foreach (T item in subset)
yield return item;
// Return the first (remembered) item.
yield return rememberedItem;
}
// Return the remaining items in the list.
while (e.MoveNext())
yield return e.Current;
}
}
Swap4 还会复制源(的子集)。所以最坏的情况是,它和函数 Swap2 一样慢且消耗内存。