Hoán đổi hai mục trong Danh sách <T>


81

Có cách LINQ nào để hoán đổi vị trí của hai mục bên trong a list<T>?


57
Tại sao nó lại quan trọng tại sao anh ấy muốn làm điều này. Những người đang tìm kiếm "các mục trong danh sách hoán đổi c #" trên Google sẽ muốn có câu trả lời thẳng thắn cho câu hỏi cụ thể này.
Daniel Macias

10
@DanielMacias Điều này rất đúng. Những câu trả lời kiểu như 'nhưng tại sao bạn lại làm điều này?' thật là khó chịu. Tôi nghĩ rằng người ta nên cung cấp ít nhất một câu trả lời khả thi trước khi cố gắng tranh luận lý do tại sao.
julealgon

tại sao bạn muốn sử dụng LINQ để làm điều này? nếu LINQ cụ thể tại sao không thay đổi tiêu đề để thêm LINQ
ina

Câu trả lời:


118

Kiểm tra câu trả lời từ Marc từ C #: Cách triển khai tốt / tốt nhất của phương pháp Hoán đổi .

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

có thể là linq-i-fied như thế nào

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
Đây không phải là LINQified.
jason

10
Tại sao phương thức mở rộng này cần trả về một danh sách? Bạn đang sửa đổi danh sách tại chỗ.
vargonian

12
Sau đó, bạn có thể chuỗi các phương pháp.
Jan Jongboom

3
Có lẽ đã được "gen hóa"?
Rhys van der Waerden

5
Cảnh báo: Phiên bản "LINQified" vẫn thay đổi Danh sách ban đầu.
Philipp Michalski

32

Có thể ai đó sẽ nghĩ ra một cách thông minh để làm điều này, nhưng bạn không nên. Việc hoán đổi hai mục trong một danh sách vốn có rất nhiều tác dụng phụ nhưng các hoạt động LINQ sẽ không có tác dụng phụ. Do đó, chỉ cần sử dụng một phương thức mở rộng đơn giản:

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

Phản ứng phụ? Bạn có thể xây dựng?
Tony The Lion,

12
By Side Effects ông có nghĩa là họ thay đổi danh sách và có thể là các mục trong danh sách - như trái ngược với chỉ truy vấn dữ liệu đó sẽ không thay đổi bất cứ điều gì
saret

hiện "T temp = list [firstIndex];" tạo một bản sao sâu của đối tượng trong danh sách?
Paul McCarthy ngày

1
@PaulMcCarthy Không, nó tạo một con trỏ mới đến đối tượng gốc, không sao chép gì cả.
NetMage

12

List<T>có một Reverse()phương thức, tuy nhiên nó chỉ đảo ngược thứ tự của hai (hoặc nhiều) mục liên tiếp .

your_list.Reverse(index, 2);

Trong đó tham số thứ hai 2cho biết chúng ta đang đảo ngược thứ tự của 2 mục, bắt đầu với mục ở vị trí đã cho index.

Nguồn: https://msdn.microsoft.com/en-us/library/hf2ay11y(v=vs.110).aspx


1
Mặc dù google đã đưa tôi đến đây, nhưng đây là những gì tôi đang tìm kiếm.
Jnr

1
Hàm .Reverse () không phải là Linq và không được gọi là Swap, nhưng có vẻ như mục đích của câu hỏi là tìm một hàm "tích hợp sẵn" thực hiện hoán đổi. Đây là câu trả lời duy nhất cung cấp điều đó.
Americus

Nhưng chỉ hữu ích để hoán đổi các phần tử liền kề, vì vậy không thực sự là một câu trả lời.
NetMage

10

Không có phương thức Hoán đổi hiện tại, vì vậy bạn phải tự tạo một phương thức. Tất nhiên bạn có thể linqify nó, nhưng điều đó phải được thực hiện với một quy tắc (bất thành văn?) Trong tâm trí: Phép toán LINQ không thay đổi các tham số đầu vào!

Trong các câu trả lời "linqify" khác, danh sách (đầu vào) được sửa đổi và trả về, nhưng hành động này ngăn chặn quy tắc đó. Nếu sẽ là kỳ lạ nếu bạn có một danh sách với các mục chưa được sắp xếp, hãy thực hiện thao tác LINQ "OrderBy" và khám phá ra rằng danh sách đầu vào cũng được sắp xếp (giống như kết quả). Điều này không được phép xảy ra!

Vì vậy, làm thế nào để chúng tôi làm điều này?

Suy nghĩ đầu tiên của tôi là khôi phục bộ sưu tập sau khi nó được lặp lại xong. Nhưng đây là một dung dịch bẩn , vì vậy không sử dụng nó:

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

Giải pháp này là bẩn vì nó không sửa đổi danh sách đầu vào, ngay cả khi nó khôi phục nó về tình trạng ban đầu. Điều này có thể gây ra một số vấn đề:

  1. Danh sách có thể chỉ được đọc và sẽ tạo ra một ngoại lệ.
  2. Nếu danh sách được chia sẻ bởi nhiều luồng, danh sách sẽ thay đổi cho các luồng khác trong thời gian của chức năng này.
  3. Nếu một ngoại lệ xảy ra trong quá trình lặp, danh sách sẽ không được khôi phục. (Điều này có thể được giải quyết bằng cách viết try-last bên trong Swap-function và đặt mã khôi phục bên trong khối cuối cùng).

Có một giải pháp tốt hơn (và ngắn hơn): chỉ cần tạo một bản sao của danh sách ban đầu. (Điều này cũng làm cho nó có thể sử dụng IEnumerable làm tham số, thay vì 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;
}

Một nhược điểm của giải pháp này là nó sao chép toàn bộ danh sách sẽ tiêu tốn bộ nhớ và điều đó làm cho giải pháp khá chậm.

Bạn có thể xem xét giải pháp sau:

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

Và nếu bạn muốn nhập tham số là 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 cũng tạo một bản sao của (một tập hợp con của) nguồn. Vì vậy, trong trường hợp xấu nhất, nó cũng chậm và ngốn bộ nhớ như hàm Swap2.


0

Nếu thứ tự quan trọng, bạn nên giữ một thuộc tính trên các đối tượng "T" trong danh sách biểu thị trình tự. Để hoán đổi chúng, chỉ cần hoán đổi giá trị của thuộc tính đó, sau đó sử dụng giá trị đó trong .Sort ( so sánh với thuộc tính chuỗi )


Đó là nếu bạn có thể đồng ý về mặt khái niệm rằng T của bạn có "thứ tự" cố hữu, nhưng không phải nếu bạn muốn nó được sắp xếp theo kiểu tùy ý mà không có "trật tự" cố hữu, chẳng hạn như trong giao diện người dùng.
Dave Van den Eynde

@DaveVandenEynde, tôi là một lập trình viên khá mới, vì vậy xin hãy tha thứ cho tôi nếu đây không phải là một câu hỏi hay: ý nghĩa của việc hoán đổi các mục trong danh sách nếu tình huống không liên quan đến khái niệm thứ tự là gì?
Mathieu K.

@ MathieuK.well ý tôi muốn nói là câu trả lời này đề xuất rằng thứ tự bắt nguồn từ một thuộc tính trên các đối tượng mà chuỗi có thể được sắp xếp . Đó thường không phải là trường hợp.
Dave Van den Eynde

Tôi (có lẽ sai) hiểu câu trả lời này là "Đánh số các đối tượng của bạn, lưu trữ các số trong một thuộc tính. Sau đó, để hoán đổi hai mục, hãy hoán đổi số của chúng. Sử dụng danh sách được sắp xếp theo thuộc tính này."
Mathieu K.
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.