在List <t> </t>中交换两个项目

时间:2010-01-19 14:46:14

标签: c# linq swap

是否有LINQ方式来交换list<T>内的两个项目的位置?

5 个答案:

答案 0 :(得分:96)

C#: Good/best implementation of Swap method检查Marc的答案。

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

可以像linq-i-fied一样

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);

答案 1 :(得分:30)

也许有人会想到一个聪明的方法来做到这一点,但你不应该。在列表中交换两个项本身就是副作用,但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;
    }
}

答案 2 :(得分:10)

没有现有的Swap方法,所以你必须自己创建一个。当然你可以使用它,但是必须考虑一个(不成文的?)规则: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函数中编写try-finally,并将restore-code放在finally块中。)
  4. 有一个更好(更短)的解决方案:只需复制原始列表即可。 (这也可以使用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一样慢且占用内存。

答案 3 :(得分:6)

列表有反向方法。

your_list.Reverse(i, 2) // will swap elements with indexs i, i + 1. 

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

答案 4 :(得分:0)

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