IT박스

목록에서 두 항목 교체

itboxs 2020. 11. 20. 08:43
반응형

목록에서 두 항목 교체


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


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

누군가가이 작업을 수행하는 영리한 방법을 생각할 수도 있지만 그렇게해서는 안됩니다. 목록에서 두 항목을 바꾸는 것은 본질적으로 부작용이 있지만 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;
    }
}

기존의 스왑 방법이 없으므로 직접 만들어야합니다. 물론 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 함수만큼 느리고 메모리를 소모합니다.


List has Reverse method.

your_list.Reverse(i, 2) // will swap elements with indexs i, i + 1. 

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


If order matters, you should keep a property on the "T" objects in your list that denotes sequence. In order to swap them, just swap the value of that property, and then use that in the .Sort(comparison with sequence property)

참고URL : https://stackoverflow.com/questions/2094239/swap-two-items-in-listt

반응형