PG日誌

受託系 PG が C# の事を書いています

C#でリストの特定の要素の位置を変更する

あるリストに入ってる1つの要素の順序(位置)を変更します。

例えば、5番目の要素を3番目に移動したいなどの状況想定します。Listクラスを使いますがそういったメソッドは存在しないため追加で実装します。

実装例

Listクラスの拡張メソッドとして実装したいと思います。Listは何も指定しないと後入れ先出のLIFOっぽい動きになり、順序が追加した順に並ぶのでちょうどいいと思います。

リスト内のオブジェクトの一致判断をするラムダのmatchとオブジェクトを複製するためのラムダのcloneを指定し、整列したい要素のあたらしい順番を指定して呼び出します。

public static class ListExtension
{
    /// <summary>
    /// リスト内の1つの要素を指定した位置へ移動します。
    /// </summary>
    public static void NewOrder<T>(this List<T> list, Predicate<T> match, int newOrder, Func<T, T> clone)
    {
        if (newOrder > list.Count)
        {
            // リストより大きい位置を指定した
            throw new ArgumentOutOfRangeException(nameof(newOrder));
        }

        T _item = list.Find(c => match(c));
        if (_item == null)
        {
            // リスト内にmatchに一致する要素が見つからなかった
            throw new KeyNotFoundException("There are no elements.");
        }

        int _index = list.IndexOf(_item, 0);
        if (newOrder == _index + 1)
        {
            return; // 同じ位置へ並び替えはのしていは何もしない
        }

        int newIndex = newOrder > _index ? newOrder : newOrder - 1;
        T _newItem = clone(_item);
        list.Insert(newIndex, _newItem);

        list.Remove(_item);
    }
}

使い方

例えば以下のようなコンテナがあったとしてそれがリストに格納された場合の使い方です。

まずはデータを入れるコンテナ。

// リストに入れるコンテナ
public class Item
{
    public string Key { get; set; }

    public Item DeepCopy()
    {
        return new Item()
        {
            Key = this.Key,
        };
    }
}

次に使い方です。

static void Main(string[] args)
{
    // 並びかえるリスト
    var list = new List<Item>()
    {
        new Item() { Key = "1" },
        new Item() { Key = "2" },
        new Item() { Key = "3" },
        new Item() { Key = "4" },
        new Item() { Key = "5" },
    };

    // 全部別々に宣言したらこんな感じ
    Predicate<Item> pred = p => p.Key == "4";
    Func<Item, Item> copy = c => c.DeepCopy();

    list.NewOrder(pred, 2, copy);

    // インラインでラムダを使うとこんな感じ
    // list.NewOrder(item => item.Key == "4", 2, item => item.DeepCopy());
    // list.NewOrder(item => item.Key == "2", 3, item => item.Clone());

    for (int i = 0; i < list.Count; i++)
    {
        Console.WriteLine("[" + i + "] " + list[i].Key);
    }
    // 実行結果
    // [0] 1
    // [1] 4
    // [2] 2
    // [3] 3
    // [4] 5
}

この実装、FindとIndexOfがそれぞれO(n)な関数なので一番末尾のデータを末尾-1に入れ替えようとするとO(2n)な計算が発生するのでちょっと遅いかもしれません。