交换List <T>中的两个项目


81

有没有一种LINQ方式可以交换a中两个项目的位置list<T>


57
为什么要这么做,这很重要。搜寻“交换清单项目C#”的人将需要直接回答此特定问题。
Daniel Macias'5

10
@DanielMacias这是真的。这些答案就像“但是为什么要这么做?” 太烦人了 我认为,在试图解释原因之前,至少应提供一个可行的答案。
julealgon 2014年

您为什么要使用LINQ做到这一点?如果LINQ具体为什么不改变所有权添加LINQ
伊纳

Answers:


118

C#中检查Marc的答案:Swap方法的良好/最佳实现

public static void Swap<T>(IList<T> list, int indexA, int indexB)
{
    T tmp = list[indexA];
    list[indexA] = list[indexB];
    list[indexB] = tmp;
}

可以像这样

public static IList<T> Swap<T>(this IList<T> list, int indexA, int indexB)
{
    T tmp = list[indexA];
    list[indexA] = list[indexB];
    list[indexB] = tmp;
    return list;
}

var lst = new List<int>() { 8, 3, 2, 4 };
lst = lst.Swap(1, 2);

26
这不是LINQified。
杰森

10
为什么此扩展方法需要返回列表?您正在就地修改列表。
vargonian'4

12
然后,您可以链接方法。
Jan Jongboom 2012年

3
“通用”也许?
Rhys van der Waerden

5
警告:“ LINQified”版本仍会更改原始列表。
Philipp Michalski

32

也许有人会想到一种聪明的方法来做到这一点,但您不应该这样做。交换列表中的两个项本质上是副作用,但是LINQ操作应该没有副作用。因此,只需使用一个简单的扩展方法:

static class IListExtensions {
    public static void Swap<T>(
        this IList<T> list,
        int firstIndex,
        int secondIndex
    ) {
        Contract.Requires(list != null);
        Contract.Requires(firstIndex >= 0 && firstIndex < list.Count);
        Contract.Requires(secondIndex >= 0 && secondIndex < list.Count);
        if (firstIndex == secondIndex) {
            return;
        }
        T temp = list[firstIndex];
        list[firstIndex] = list[secondIndex];
        list[secondIndex] = temp;
    }
}

副作用?你能详细说明吗?
托尼狮子

12
通过副作用,他的意思是他们更改了列表,甚至可能更改了列表中的项目-与仅查询不会修改任何内容的数据相反
秘密2010年

是否“ T temp = list [firstIndex];” 在列表中创建对象的深层副本?
Paul McCarthy

1
@PaulMcCarthy不,它创建一个指向原始对象的新指针,根本不进行任何复制。
NetMage

12

List<T>有一个Reverse()方法,但是它只会颠倒两个(或多个)连续项目的顺序。

your_list.Reverse(index, 2);

第二个参数2表示我们正在反转2个项目的顺序,从给定的项目开始index

来源:https : //msdn.microsoft.com/zh-cn/library/hf2ay11y(v=vs.110).aspx


1
尽管Google将我带到了这里,但这是我一直在寻找的东西。
Jnr

1
.Reverse()函数不是Linq,也不称为Swap,但似乎该问题的目的是找到执行交换的“内置”函数。这是唯一提供的答案。
Americus

但这仅用于交换相邻元素,因此并不是真正的答案。
NetMage

10

目前没有交换方法,因此您必须自己创建一个。当然,您可以进行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;
}

该解决方案是肮脏的,因为它确实修改了输入列表,即使将其恢复到原始状态也是如此。这可能会导致几个问题:

  1. 该列表可能是只读的,将引发异常。
  2. 如果该列表由多个线程共享,则在此功能期间,其他线程的列表将更改。
  3. 如果在迭代过程中发生异常,该列表将不会恢复。(可以解决此问题,以便在Swap函数中最终写入尝试,然后将恢复代码放入finally块中)。

有一个更好(更短)的解决方案:只需复制原始列表即可。(这也使得可以使用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函数一样慢且占用内存。


0

如果顺序很重要,则应在列表中的“ T”对象上保留一个表示顺序的属性。为了交换它们,只需交换该属性的值,然后在.Sort(与sequ​​ence属性比较)中使用它即可。


就是说,如果您可以在概念上同意您的T具有一个固有的“顺序”,但是如果您希望以没有固有的“顺序”的任意方式对它进行排序(例如在UI中),则不能。
Dave Van den Eynde

@DaveVandenEynde,我是一位相当新手的程序员,所以如果这不是一个大问题,请原谅我:如果情况不涉及顺序概念,那么交换列表中的项目是什么意思?
Mathieu K.

@ MathieuK.well,我的意思是说,这个答案表明顺序是从可以对序列进行排序的对象的属性中得出的。通常情况并非如此。
Dave Van den Eynde'Mar

我(可能是错误的)理解这个答案是说:“为对象编号,将数字存储在属性中。然后,交换两个项目,交换它们的编号。使用按此属性排序的列表。”
Mathieu K.
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.