일반 목록-목록 내에서 항목 이동


155

그래서 나는 일반적인 목록 oldIndexnewIndex값을 가지고 있습니다.

나는에있는 항목을 이동할 oldIndex에, newIndex... 단순히 가능한 한.

어떤 제안?

노트

항목은 항목 사이에 종료되어야 (newIndex - 1)하고 newIndex 이전 이 제거되었습니다.


1
선택한 답변을 변경해야합니다. 가진 사람은 newIndex--당신이 당신이 원하는 말했다 동작이 발생하지 않습니다.
Miral

1
@Miral-어떤 대답을 받아 들여야한다고 생각하십니까?
Richard Ev

4
jpierson 's. 이동 이전의 오브젝트는 이동 이전의 newIndex가되기 전에 oldIndex에있었습니다. 이것은 가장 놀라운 행동입니다 (그리고 드래그 앤 드롭 재정렬 코드를 작성할 때 필요한 것입니다). 그가 ObservableCollectiongeneric List<T>이 아니라 이야기 하고 있지만, 동일한 결과를 얻기 위해 단순히 메소드 호출을 바꾸는 것은 쉽지 않습니다 .
Miral

요청 (그리고 올바르게 대답에서 구현 ) 동작에서 항목 사이에서 항목을 이동 [newIndex - 1]하고 [newIndex]반전되지 않습니다. Move(1, 3); Move(3, 1);목록을 초기 상태로 되 돌리지 않습니다. 한편 이 답변에서 제공 ObservableCollection되고 언급 된 다른 행동 있으며, 이는 불가피 합니다.
Lightman

답변:


138

"일반 목록"이라고 말했지만 List (T) 클래스 를 사용해야한다고 지정하지 않았 으므로 여기에 다른 내용이 있습니다.

ObservableCollection에 (T) 클래스는이 이동 방법을 정확하게 당신이 원하는 않습니다.

public void Move(int oldIndex, int newIndex)

그 아래에는 기본적으로 다음 과 같이 구현됩니다.

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

보시 다시피 다른 사람들이 제안한 스왑 방법은 본질적으로 ObservableCollection 이 자체 Move 메서드에서 수행하는 것입니다.

업데이트 2015-12-30 : .NET이 오픈 소스이므로 Reflector / ILSpy를 사용하지 않고도 corefx에서 MoveMoveItem 메서드 의 소스 코드를 직접 확인할 수 있습니다 .


28
왜 이것이 List <T>에서도 구현되지 않았는지 궁금합니다.
Andreas

일반 목록과 List (T) 클래스의 차이점은 무엇입니까? 나는 그들이 :( 같은 줄 알았는데
BKSpurgeon

A "일반적인 목록"는 구현할 수있는 ObservableCollection에 포함될 수 .NET에서 목록 또는 데이터 구조와 같은 모음의 유형 (T) 또는 다른 클래스를 의미 할 수 listy을 등은 IList / ICollection은 /는 IEnumerable 인터페이스를.
jpierson

6
누군가 대상 인덱스 이동 (소스 인덱스보다 큰 경우)이 실제로없는 이유를 설명해 주시겠습니까?
블라 디우스

@vladius 아이디어는 지정된 newIndex 값이 항목이 이동 후 있어야 할 원하는 색인을 지정해야하며 삽입을 사용하기 때문에 조정할 이유가 없다고 생각합니다. newIndex가 원래 인덱스와 관련된 위치라면 다른 이야기 일 것입니다. 그러나 그것이 작동하는 방식은 아닙니다.
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
목록에 두 개의 항목 사본이 있고 하나는 oldIndex 이전에 발생하면 솔루션이 분해됩니다. RemoveAt를 사용하여 올바른 것을 확보해야합니다.
Aaron Maenpaa

6
실제로, 비열한 가장자리 케이스
게리 Shutler

1
@GarryShutler 단일 항목을 제거한 다음 삽입하면 인덱스가 어떻게 변할 수 있는지 알 수 없습니다. 을 감소시키는 것은 newIndex실제로 내 시험 (내 대답은 아래 참조) 나누기.
벤 포스터

1
참고 : 스레드 안전성이 중요한 경우, 이는 모두 lock명령문 안에 있어야합니다 .
rory.ap

1
여러 가지 이유로 혼란 스럽기 때문에 이것을 사용하지 않을 것입니다. 목록에서 Move (oldIndex, newIndex) 메소드를 정의하고 Move (15,25)를 호출 한 다음 Move (25,15)를 호출하는 것은 ID가 아니라 스왑입니다. 또한 Move (15,25)를 사용하면 항목이 25가 아닌 인덱스 24로 이동합니다. 또한 스왑은 temp = item [oldindex]; item [oldindex] = 항목 [newindex]; item [newindex] = 임시; 큰 배열에서 더 효율적으로 보입니다. 또한 Move (0,0) 및 Move (0,1)도 같으며 홀수입니다. 또한 Move (0, Count -1)은 항목을 끝으로 이동하지 않습니다.
Wouter

12

나는이 질문이 오래 되었다는 것을 알고 있지만 자바 스크립트 코드 의이 응답을 C #에 적용했습니다. 그것이 도움이되기를 바랍니다.

 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

List <T> .Remove () 및 List <T> .RemoveAt ()는 제거중인 항목을 반환하지 않습니다.

따라서 이것을 사용해야합니다 :

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

5

항목 삽입 현재에 oldIndex에있을 newIndex다음 원래 인스턴스를 제거합니다.

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

삭제하려는 항목의 색인이 삽입으로 인해 변경 될 수 있다는 점을 고려해야합니다.


1
삽입하기 전에 제거해야합니다 ... 주문으로 인해 목록이 할당 될 수 있습니다.
Jim Balter

4

목록에서 항목을 이동하기위한 확장 방법을 만들었습니다.

항목을 목록 의 기존 색인 위치로 이동하기 때문에 기존 항목을 이동하는 경우 색인이 이동하지 않아야 합니다.

@Oliver가 아래 (항목의 끝으로 항목을 이동)를 참조하는 경우는 실제로 테스트에 실패하지만 의도적으로 설계된 것입니다. 목록 끝에 항목 을 삽입하려면을 호출하면 List<T>.Add됩니다. 이 인덱스 위치는 이동 전에 존재하지 않으므로 실패 list.Move(predicate, list.Count) 해야합니다 .

어떤 경우에, 나는 두 개의 추가 확장 방법, 만든 MoveToEndMoveToBeginning찾을 수있는 소스있는, 여기를 .

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

어디에 Ensure.Argument정의되어 있습니까?
Oliver

1
일반적으로 목록 끝에 무언가를 배치하기 위해 List<T>전화 Insert(list.Count, element)를 걸 수 있습니다 . 따라서 실제로 실패한 When_new_index_is_higher전화 list.Move(x => x == 3, 5)를 해야 합니다.
Oliver

3
@Oliver는 보통 목록 끝에 항목을 삽입하기 위해 List<T>호출 합니다. 개별 항목을 이동할 때 단일 항목 만 제거하고 다시 삽입하기 때문에 인덱스의 원래 크기를 늘리지 않습니다. 내 답변에서 링크를 클릭하면에 대한 코드를 찾을 수 있습니다. .AddEnsure.Argument
벤 포스터

솔루션은 대상 색인이 두 요소 사이가 아닌 위치 일 것으로 예상합니다. 이것은 일부 사용 사례에서는 잘 작동하지만 다른 경우에는 작동하지 않습니다. 또한 이동은 끝으로 이동하는 것을 지원하지 않지만 (Oliver가 지적한 것처럼) 코드 에서이 제약 조건을 나타내는 곳은 없습니다. 또한 20 개의 요소가있는 목록이 있고 요소 10을 끝으로 이동하려는 경우 Move 메서드가이를 처리하기보다는 객체 참조를 저장하고 목록에서 객체를 제거해야합니다. 개체를 추가하십시오.
21:44에

1
@ 실제로 내 대답을 읽으면 항목이 목록의 끝 / 시작으로 이동하는 것이 지원됩니다. 여기 에서 사양을 볼 수 있습니다 . 예, 내 코드는 색인이 목록 내에서 유효한 (존재하는) 위치가 될 것으로 기대합니다. 우리는 항목을 삽입하지 않고 움직 입니다.
Ben Foster

1

나는 다음 중 하나를 기대할 것이다.

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

... 또는 :

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

... 트릭을 할 것이지만, 나는이 기계에 VS를 가지고 있지 않습니다.


1
@GarryShutler 상황에 따라 다릅니다. 인터페이스에서 사용자가 색인으로 목록에서 위치를 지정할 수 있으면 항목 15에 20으로 이동하라고 지시 할 때 혼동되지만 대신 19로 이동합니다. 인터페이스에서 사용자가 다른 항목으로 항목을 끌 수있는 경우 목록에서 다음에 newIndex있다면 감소 하는 것이 합리적 oldIndex입니다.
21:47에

-1

가장 간단한 방법 :

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

편집하다

질문은 명확하지 않습니다 ... 우리는 list[newIndex]항목이 어디로 가는지 신경 쓰지 않기 때문에 이것을 수행하는 가장 간단한 방법은 다음과 같습니다 (확장 방법의 유무에 관계없이).

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

이 솔루션은 목록 삽입 / 제거를 포함하지 않기 때문에 가장 빠릅니다.


4
삽입하지 않고 newIndex의 항목을 덮어 씁니다.
Garry Shutler

@Garry 최종 결과가 동일하지 않습니까?
Ozgur Ozcitak

4
아니요, 삽입하면 발생하지 않는 newIndex의 값이 손실됩니다.
Garry Shutler

-2

더 단순한 사람들이이 작업을 수행합니까?

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

MoveDown과 동일하게 수행하지만 "if (ind! = Concepts.Count ())"및 Concepts.Insert (ind + 1, item.ToString ())의 if를 변경하십시오.


-3

이것이 내가 요소 확장 방법을 구현 한 방법입니다. 요소의 앞뒤로 그리고 극단으로 이동하는 것을 잘 처리합니다.

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
이것은 Francisco의 답변과 중복됩니다.
nivs1978
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.