93

内の 2 つのアイテムの位置を交換する LINQ の方法はありList<T>ますか?

4

6 に答える 6

32

誰かがこれを行う賢い方法を考えるかもしれませんが、そうすべきではありません。リスト内の 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;
    }
}
于 2010-01-19T14:48:28.403 に答える
10

既存の Swap メソッドがないため、自分で作成する必要があります。もちろん、それを linqify することはできますが、それは 1 つの (書かれていない?) ルールを念頭に置いて行う必要があります: 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 ブロック内に復元コードを配置することで解決できます)。

より良い (そしてより短い) 解決策があります: 元のリストのコピーを作成するだけです。(これにより、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;
}

このソリューションの欠点の 1 つは、メモリを消費するリスト全体をコピーし、ソリューションがかなり遅くなることです。

次の解決策を検討してください。

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 と同じくらい遅く、メモリを消費します。

于 2013-04-21T21:38:01.833 に答える