Danh sách chung - di chuyển một mục trong danh sách


155

Vì vậy, tôi có một danh sách chung, và một oldIndexvà một newIndexgiá trị.

Tôi muốn chuyển món đồ đó vào oldIndex, newIndex... đơn giản nhất có thể.

Bất kỳ đề xuất?

Ghi chú

Các mục nên được kết thúc giữa các mục tại (newIndex - 1)newIndex trước khi nó được gỡ bỏ.


1
Bạn nên thay đổi câu trả lời bạn đánh dấu. Người newIndex--không có kết quả trong hành vi bạn nói bạn muốn.
Miral

1
@Mirus - bạn nghĩ câu trả lời nào nên được chấp nhận?
Richard Ev

4
của jpierson. Nó dẫn đến đối tượng đã từng ở old Index trước khi di chuyển đến new Index sau khi di chuyển. Đây là hành vi ít gây ngạc nhiên nhất (và đó là những gì tôi cần khi tôi viết một số mã sắp xếp lại kéo theo). Được cho là anh ta đang nói về ObservableCollectionvà không phải là một người chung chung List<T>, nhưng thật đơn giản để trao đổi các cuộc gọi phương thức để có được kết quả tương tự.
Miral

Hành vi được yêu cầu (và được thực hiện chính xác trong câu trả lời này ) để di chuyển mục ở giữa các mục tại [newIndex - 1][newIndex]không thể đảo ngược. Move(1, 3); Move(3, 1);không trả lại danh sách về trạng thái ban đầu. Trong khi đó, có những hành vi khác nhau được cung cấp ObservableCollectionđề cập trong câu trả lời này , điều này là không thể đảo ngược .
Lightman

Câu trả lời:


138

Tôi biết bạn đã nói "danh sách chung" nhưng bạn không xác định rằng bạn cần sử dụng Danh sách (T) vì vậy đây là một cách khác biệt.

Lớp ObservableCollection (T) có một phương thức Move thực hiện chính xác những gì bạn muốn.

public void Move(int oldIndex, int newIndex)

Bên dưới nó về cơ bản được thực hiện như thế này.

T item = base[oldIndex];
base.RemoveItem(oldIndex);
base.InsertItem(newIndex, item);

Vì vậy, như bạn có thể thấy phương thức hoán đổi mà những người khác đã đề xuất về cơ bản là những gì mà ObservableCollection thực hiện trong phương thức Move của chính nó.

CẬP NHẬT 2015-12-30: Bạn có thể xem mã nguồn cho các phương thức MoveMoveItem trong corefx ngay bây giờ mà không cần sử dụng Reflector / ILSpy vì .NET là nguồn mở.


28
Tôi tự hỏi tại sao điều này không được thực hiện trong Danh sách <T>, bất kỳ ai cũng sẽ làm sáng tỏ điều này?
Andreas

Sự khác biệt giữa danh sách chung và lớp Danh sách (T) là gì? Tôi nghĩ rằng họ giống nhau :(
BKSpurgeon

"Danh sách chung" có thể có nghĩa là bất kỳ loại danh sách hoặc bộ sưu tập nào như cấu trúc dữ liệu trong .NET có thể bao gồm ObservableCollection (T) hoặc các lớp khác có thể thực hiện giao diện liệt kê như IList / ICollection / IEnumerable.
jpierson

6
Ai đó có thể giải thích, tại sao không có sự thay đổi chỉ số đích (trong trường hợp nó lớn hơn chỉ số nguồn) thực sự?
Vladius

@vladius Tôi tin rằng ý tưởng là giá trị new Index được chỉ định chỉ nên chỉ định chỉ mục mong muốn mà mặt hàng sẽ có sau khi di chuyển và vì một chèn đang được sử dụng nên không có lý do để điều chỉnh. Nếu new Index là một vị trí liên quan đến chỉ mục ban đầu thì đó sẽ là một câu chuyện khác tôi đoán nhưng đó không phải là cách nó hoạt động.
jpierson

129
var item = list[oldIndex];

list.RemoveAt(oldIndex);

if (newIndex > oldIndex) newIndex--; 
// the actual index could have shifted due to the removal

list.Insert(newIndex, item);

9
Giải pháp của bạn bị hỏng nếu có hai bản sao của danh sách, với một bản xuất hiện trước old Index. Bạn nên sử dụng RemoveAt để đảm bảo bạn lấy đúng.
Aaron Maenpaa

6
Thật vậy, trường hợp cạnh lén lút
Garry Shutler

1
@GarryShutler Tôi không thể thấy chỉ mục có thể thay đổi như thế nào nếu chúng ta xóa và sau đó chèn một mục duy nhất. Giảm newIndexthực sự phá vỡ bài kiểm tra của tôi (xem câu trả lời của tôi dưới đây).
Ben Foster

1
Lưu ý: nếu an toàn luồng là quan trọng, thì tất cả phải nằm trong một lockcâu lệnh.
rory.ap

1
Tôi sẽ không sử dụng điều này vì nó gây nhầm lẫn vì nhiều lý do. Xác định phương thức Di chuyển (old Index, new Index) trong danh sách và gọi Move (15,25) và sau đó Move (25,15) không phải là danh tính mà là trao đổi. Ngoài ra Move (15,25) làm cho vật phẩm chuyển sang chỉ số 24 chứ không phải ở mức 25 mà tôi mong đợi. Bên cạnh việc hoán đổi có thể được thực hiện bởi temp = item [oldindex]; mục [oldindex] = mục [newindex]; mục [newindex] = temp; có vẻ hiệu quả hơn trên mảng lớn. Ngoài ra Di chuyển (0,0) và Di chuyển (0,1) sẽ giống nhau cũng là số lẻ. Và cũng Di chuyển (0, Đếm -1) không di chuyển mục đến cuối.
Wouter

12

Tôi biết câu hỏi này đã cũ nhưng tôi đã điều chỉnh câu trả lời NÀY của mã javascript thành C #. Hy vọng nó giúp

 public static void Move<T>(this List<T> list, int oldIndex, int newIndex)
{

    // exit if possitions are equal or outside array
    if ((oldIndex == newIndex) || (0 > oldIndex) || (oldIndex >= list.Count) || (0 > newIndex) ||
        (newIndex >= list.Count)) return;
    // local variables
    var i = 0;
    T tmp = list[oldIndex];
    // move element down and shift other elements up
    if (oldIndex < newIndex)
    {
        for (i = oldIndex; i < newIndex; i++)
        {
            list[i] = list[i + 1];
        }
    }
        // move element up and shift other elements down
    else
    {
        for (i = oldIndex; i > newIndex; i--)
        {
            list[i] = list[i - 1];
        }
    }
    // put element from position 1 to destination
    list[newIndex] = tmp;
}

9

Danh sách <T> .Remove () và Danh sách <T> .RemoveAt () không trả về mục đang bị xóa.

Vì vậy, bạn phải sử dụng điều này:

var item = list[oldIndex];
list.RemoveAt(oldIndex);
list.Insert(newIndex, item);

5

Chèn mục hiện tại oldIndexvào lúc newIndexđó và sau đó loại bỏ thể hiện ban đầu.

list.Insert(newIndex, list[oldIndex]);
if (newIndex <= oldIndex) ++oldIndex;
list.RemoveAt(oldIndex);

Bạn phải tính đến việc chỉ mục của mục bạn muốn xóa có thể thay đổi do chèn.


1
Bạn nên xóa trước khi chèn ... đơn đặt hàng của bạn có thể khiến danh sách thực hiện phân bổ.
Jim Balter

4

Tôi đã tạo một phương thức mở rộng để di chuyển các mục trong danh sách.

Một chỉ mục không nên thay đổi nếu chúng ta đang di chuyển một mục hiện có vì chúng ta đang di chuyển một mục sang một mục hiện có vị trí chỉ mục trong danh sách.

Trường hợp cạnh mà @Oliver đề cập dưới đây (di chuyển một mục đến cuối danh sách) thực sự sẽ khiến các bài kiểm tra thất bại, nhưng đây là do thiết kế. Để chèn một mục mới vào cuối danh sách, chúng tôi sẽ chỉ cần gọi List<T>.Add. list.Move(predicate, list.Count) Nên thất bại vì vị trí chỉ số này không tồn tại trước khi di chuyển.

Trong mọi trường hợp, tôi đã tạo hai phương thức mở rộng bổ sung MoveToEndMoveToBeginningnguồn có thể được tìm thấy ở đây .

/// <summary>
/// Extension methods for <see cref="System.Collections.Generic.List{T}"/>
/// </summary>
public static class ListExtensions
{
    /// <summary>
    /// Moves the item matching the <paramref name="itemSelector"/> to the <paramref name="newIndex"/> in a list.
    /// </summary>
    public static void Move<T>(this List<T> list, Predicate<T> itemSelector, int newIndex)
    {
        Ensure.Argument.NotNull(list, "list");
        Ensure.Argument.NotNull(itemSelector, "itemSelector");
        Ensure.Argument.Is(newIndex >= 0, "New index must be greater than or equal to zero.");

        var currentIndex = list.FindIndex(itemSelector);
        Ensure.That<ArgumentException>(currentIndex >= 0, "No item was found that matches the specified selector.");

        // Copy the current item
        var item = list[currentIndex];

        // Remove the item
        list.RemoveAt(currentIndex);

        // Finally add the item at the new index
        list.Insert(newIndex, item);
    }
}

[Subject(typeof(ListExtensions), "Move")]
public class List_Move
{
    static List<int> list;

    public class When_no_matching_item_is_found
    {
        static Exception exception;

        Establish ctx = () => {
            list = new List<int>();
        };

        Because of = ()
            => exception = Catch.Exception(() => list.Move(x => x == 10, 10));

        It Should_throw_an_exception = ()
            => exception.ShouldBeOfType<ArgumentException>();
    }

    public class When_new_index_is_higher
    {
        Establish ctx = () => {
            list = new List<int> { 1, 2, 3, 4, 5 };
        };

        Because of = ()
            => list.Move(x => x == 3, 4); // move 3 to end of list (index 4)

        It Should_be_moved_to_the_specified_index = () =>
            {
                list[0].ShouldEqual(1);
                list[1].ShouldEqual(2);
                list[2].ShouldEqual(4);
                list[3].ShouldEqual(5);
                list[4].ShouldEqual(3);
            };
    }

    public class When_new_index_is_lower
    {
        Establish ctx = () => {
            list = new List<int> { 1, 2, 3, 4, 5 };
        };

        Because of = ()
            => list.Move(x => x == 4, 0); // move 4 to beginning of list (index 0)

        It Should_be_moved_to_the_specified_index = () =>
        {
            list[0].ShouldEqual(4);
            list[1].ShouldEqual(1);
            list[2].ShouldEqual(2);
            list[3].ShouldEqual(3);
            list[4].ShouldEqual(5);
        };
    }
}

Trường hợp được Ensure.Argumentxác định?
Oliver

1
Trong một bình thường, List<T>bạn có thể gọi Insert(list.Count, element)để đặt một cái gì đó ở cuối danh sách. Vì vậy, bạn When_new_index_is_highernên gọi list.Move(x => x == 3, 5)mà thực sự thất bại.
Oliver

3
@Oliver trong một bình thường List<T>tôi sẽ chỉ gọi .Addđể chèn một mục mới vào cuối danh sách. Khi di chuyển các mục riêng lẻ, chúng tôi không bao giờ tăng kích thước ban đầu của chỉ mục vì chúng tôi chỉ xóa một mục duy nhất và xác nhận lại. Nếu bạn nhấp vào liên kết trong câu trả lời của tôi, bạn sẽ tìm thấy mã cho Ensure.Argument.
Ben Foster

Giải pháp của bạn hy vọng rằng chỉ số đích là một vị trí, không phải giữa hai yếu tố. Trong khi điều này hoạt động tốt cho một số trường hợp sử dụng, nó không hoạt động cho những người khác. Ngoài ra, di chuyển của bạn không hỗ trợ di chuyển đến cuối (như Oliver đã lưu ý) nhưng không có nơi nào trong mã của bạn làm bạn chỉ ra ràng buộc này. Nó cũng phản trực giác, nếu tôi có một danh sách có 20 phần tử và muốn di chuyển phần tử 10 đến hết, tôi sẽ mong phương thức Move xử lý việc này, thay vào đó phải tìm cách lưu tham chiếu đối tượng, xóa đối tượng khỏi danh sách và thêm đối tượng.
Đã xem

1
@Trisped thực sự nếu bạn đọc câu trả lời của tôi, di chuyển một mục đến cuối / đầu danh sách được hỗ trợ. Bạn có thể xem thông số kỹ thuật ở đây . Có, mã của tôi hy vọng chỉ mục là một vị trí hợp lệ (hiện có) trong danh sách. Chúng tôi đang di chuyển các mặt hàng, không chèn chúng.
Ben Foster

1

Tôi cũng mong đợi:

// Makes sure item is at newIndex after the operation
T item = list[oldIndex];
list.RemoveAt(oldIndex);
list.Insert(newIndex, item);

... hoặc là:

// Makes sure relative ordering of newIndex is preserved after the operation, 
// meaning that the item may actually be inserted at newIndex - 1 
T item = list[oldIndex];
list.RemoveAt(oldIndex);
newIndex = (newIndex > oldIndex ? newIndex - 1, newIndex)
list.Insert(newIndex, item);

... sẽ thực hiện thủ thuật, nhưng tôi không có VS trên máy này để kiểm tra.


1
@GarryShutler Nó phụ thuộc vào tình hình. Nếu giao diện của bạn cho phép người dùng chỉ định vị trí trong danh sách theo chỉ mục, họ sẽ bối rối khi họ bảo mục 15 chuyển sang 20, nhưng thay vào đó, nó chuyển sang 19. Nếu giao diện của bạn cho phép người dùng kéo một mục giữa cho người khác trong danh sách sau đó sẽ có ý nghĩa để giảm newIndexnếu nó là sau oldIndex.
Đã xem

-1

Cách đơn giản nhất:

list[newIndex] = list[oldIndex];
list.RemoveAt(oldIndex);

BIÊN TẬP

Câu hỏi không rõ ràng lắm ... Vì chúng tôi không quan tâm list[newIndex]mục đó đi đâu nên tôi nghĩ cách đơn giản nhất để thực hiện việc này là như sau (có hoặc không có phương thức mở rộng):

    public static void Move<T>(this List<T> list, int oldIndex, int newIndex)
    {
        T aux = list[newIndex];
        list[newIndex] = list[oldIndex];
        list[oldIndex] = aux;
    }

Giải pháp này là nhanh nhất vì nó không liên quan đến việc chèn / xóa danh sách.


4
Điều đó sẽ ghi đè lên mục tại new Index, không chèn.
Máy đóng cửa Garry

@Garry Không phải kết quả cuối cùng sẽ giống nhau sao?
Ozgur Ozcitak

4
Không, cuối cùng bạn sẽ mất giá trị tại new Index, điều này sẽ không xảy ra nếu bạn chèn.
Máy đóng cửa Garry

-2

Là những người đơn giản hơn chỉ cần làm điều này

    public void MoveUp(object item,List Concepts){

        int ind = Concepts.IndexOf(item.ToString());

        if (ind != 0)
        {
            Concepts.RemoveAt(ind);
            Concepts.Insert(ind-1,item.ToString());
            obtenernombres();
            NotifyPropertyChanged("Concepts");
        }}

Thực hiện tương tự với MoveDown nhưng thay đổi if cho "if (ind! = Conception.Count ())" và Conception.Insert (ind + 1, item.ToString ());


-3

Đây là cách tôi thực hiện một phương thức mở rộng phần tử di chuyển. Nó xử lý di chuyển trước / sau và cực đoan cho các yếu tố khá tốt.

public static void MoveElement<T>(this IList<T> list, int fromIndex, int toIndex)
{
  if (!fromIndex.InRange(0, list.Count - 1))
  {
    throw new ArgumentException("From index is invalid");
  }
  if (!toIndex.InRange(0, list.Count - 1))
  {
    throw new ArgumentException("To index is invalid");
  }

  if (fromIndex == toIndex) return;

  var element = list[fromIndex];

  if (fromIndex > toIndex)
  {
    list.RemoveAt(fromIndex);
    list.Insert(toIndex, element);
  }
  else
  {
    list.Insert(toIndex + 1, element);
    list.RemoveAt(fromIndex);
  }
}

2
Đây là một bản sao của câu trả lời từ Francisco.
nivs1978
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.