交换列表中的两个项目<T>

问题描述 投票:0回答:7

是否有 LINQ 方法来交换

List<T>
中两个项目的位置?

c# linq swap
7个回答
136
投票

查看 Marc 的答案,来自 C#: Swap 方法的良好/最佳实现

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

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

22
投票

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

your_list.Reverse(index, 2);

第二个参数

2
表示我们要颠倒 2 个项目的顺序,从给定
index
处的项目开始。

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


20
投票

从 C# 7 开始你就可以做到

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

10
投票

目前没有现有的 Swap 方法,因此您必须自己创建一个。当然,您可以对其进行 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 函数中编写 try-finally 并将恢复代码放入 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(与序列属性比较)

中使用它

0
投票

为了补充@user1920925的答案,以下是我在我这边测试过的一些示例:

List<string> items = new List<string>();

items.Add("item 1");
items.Add("item 2");
items.Add("item 3");
items.Add("item 4");

Console.WriteLine("Move item 3 down");
items.Reverse(items.IndexOf("item 3"), 2);

Console.WriteLine("Move item 4 up");
items.Reverse(items.IndexOf("item 4")-1, 2);

// extra, but must check if out of bounds etc
var downOffset = 2;
Console.WriteLine("Move item 4 down by " + downOffset);
items.Reverse(items.IndexOf("item 4"), downOffset+1);

// outputs
Original
item 1
item 2
item 3
item 4

Move item 3 down
item 1
item 2
item 4
item 3

Move item 4 up
item 1
item 4
item 2
item 3

Move item 4 down by 2
item 1
item 3
item 2
item 4
© www.soinside.com 2019 - 2024. All rights reserved.