List <T>에서 두 항목 교체


81

내부에서 두 항목의 위치를 ​​바꾸는 LINQ 방법이 list<T>있습니까?


57
왜 그가 이것을하고 싶어하는지가 왜 중요합니까? "swap list items c #"을 검색하는 사람들은이 특정 질문에 대한 직접적인 대답을 원할 것입니다.
Daniel Macias

10
@DanielMacias 이것은 사실입니다. '그런데 왜 이러는거야?' 너무 짜증나. 이유를 논하기 전에 적어도 실행 가능한 대답을 제공해야한다고 생각합니다.
julealgon

LINQ를 사용하여이 작업을 수행하려는 이유는 무엇입니까? LINQ에 특정한 경우 LINQ를 추가하기 위해 제목을 변경하지 않는 이유
ina

답변:


119

C #의 Marc의 답변 : Good / best implementation of Swap method .

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

다음과 같이 연결될 수 있습니다.

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

10
이 확장 메서드가 목록을 반환해야하는 이유는 무엇입니까? 목록을 제자리에서 수정하고 있습니다.
vargonian

13
그런 다음 메서드를 연결할 수 있습니다.
Jan Jongboom 2012

Unity3D에서이 기능을 사용하려는 경우 확장 메서드를 공개하는 것을 잊지 마십시오! (그렇게하지 않으면 유니티를 찾을 수 없습니다)
col000r

멋지고 쉽습니다. 감사합니다
fnc12 2015 년

5
경고 : "LINQified"버전은 여전히 ​​원래 목록을 변경합니다.
Philipp Michalski

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

부작용? 자세히 설명해 주시겠습니까?
Tony The Lion

12
아무것도 수정하지하는 것과 쿼리 데이터에 반대 - 부작용까지 그는 그들이 목록과 목록에 가능성이있는 항목을 변경 의미
saret

"T temp = list [firstIndex];" 목록에있는 개체의 전체 복사본을 만드시겠습니까?
Paul McCarthy

1
@PaulMcCarthy 아니요, 원본 개체에 대한 새 포인터를 만들고 복사하지 않습니다.
NetMage

12

List<T>갖는다 Reverse()는 두 개 (또는 그 이상)의 순서 역전 단에있어서, 연속적인 품목.

your_list.Reverse(index, 2);

두 번째 매개 변수 2는 주어진에서 항목부터 시작하여 2 개 항목의 순서를 반대로한다는 것을 나타냅니다 index.

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


1
Google이 나를 여기로 데려 왔지만 이것이 내가 찾던 것입니다.
Jnr

1
.Reverse () 함수는 Linq가 아니고 Swap이라고 부르지 않습니다.하지만 질문의 의도는 스왑을 수행하는 "내장"함수를 찾는 것 같습니다. 이것이 유일한 대답입니다.
Americus

그러나 인접한 요소를 교체하는 데만 유용하므로 실제로 대답은 아닙니다.
NetMage

10

기존의 스왑 방법이 없으므로 직접 만들어야합니다. 물론 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-block 안에 복원 코드를 넣도록 해결 될 수 있습니다).

더 나은 (그리고 더 짧은) 해결책이 있습니다. 원본 목록을 복사하기 만하면됩니다. (이렇게하면 IList 대신 IEnumerable을 매개 변수로 사용할 수 있습니다.)

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 ( comparison with sequence property )에서 사용하십시오.


이는 T가 고유 한 "순서"를 가지고 있다는 개념적으로 동의 할 수 있지만 UI와 같이 고유 한 "순서"없이 임의의 방식으로 정렬하려는 경우에는 그렇지 않습니다.
Dave Van den Eynde 2010 년

@DaveVandenEynde, 저는 상당히 초보 프로그래머이므로 이것이 좋은 질문이 아니라면 저를 용서 해주세요 : 상황이 순서의 개념을 포함하지 않는 경우 목록에서 항목을 바꾸는 의미는 무엇입니까?
Mathieu K.

@ MathieuK.well 내가 말하고자하는 것은이 대답은 순서가 순서가 정렬 될 수있는 객체의 속성에서 파생된다는 것을 제안한다는 것 입니다. 일반적으로 그렇지 않습니다.
Dave Van den Eynde

이 대답은 "개체에 번호를 매기고 속성에 번호를 저장합니다. 그런 다음 두 항목을 바꾸려면 해당 번호를 바꾸십시오.이 속성별로 정렬 된 목록을 사용하십시오."라는 대답을 잘못 이해하고 있습니다.
Mathieu K.
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.