web-dev-qa-db-ja.com

監視可能なコレクションをソートするにはどうすればよいですか?

私は次のクラスがあります:

[DataContract]
public class Pair<TKey, TValue> : INotifyPropertyChanged, IDisposable
{
    public Pair(TKey key, TValue value)
    {
        Key = key;
        Value = value;
    }

    #region Properties
    [DataMember]
    public TKey Key
    {
        get
        { return m_key; }
        set
        {
            m_key = value;
            OnPropertyChanged("Key");
        }
    }
    [DataMember]
    public TValue Value
    {
        get { return m_value; }
        set
        {
            m_value = value;
            OnPropertyChanged("Value");
        }
    }
    #endregion

    #region Fields
    private TKey m_key;
    private TValue m_value;
    #endregion

    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged(string name)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(name));
        }
    }

    #endregion

    #region IDisposable Members

    public void Dispose()
    { }

    #endregion
}

ObservableCollectionに入れました:

ObservableCollection<Pair<ushort, string>> my_collection = 
    new ObservableCollection<Pair<ushort, string>>();

my_collection.Add(new Pair(7, "aaa"));
my_collection.Add(new Pair(3, "xey"));
my_collection.Add(new Pair(6, "fty"));

Q:キーでソートするにはどうすればよいですか?

93
Maciek

OP編集:多くの人が元の回答が同じコレクションを返さないことを正しく指摘しているので(元はQの辞書部分のソートに重点を置いていました)。観察可能なコレクションの並べ替えを扱う下部の編集を参照してください。オリジナルはまだ投票数が残っているためここに残っています

以下のdoSortメソッドが示すように、linqを使用できます。簡単なコードスニペット:を生成します

3:xey 6:fty 7:aaa

または、コレクション自体で拡張メソッドを使用できます

var sortedOC = _collection.OrderBy(i => i.Key);

private void doSort()
{
    ObservableCollection<Pair<ushort, string>> _collection = 
        new ObservableCollection<Pair<ushort, string>>();

    _collection.Add(new Pair<ushort,string>(7,"aaa"));
    _collection.Add(new Pair<ushort, string>(3, "xey"));
    _collection.Add(new Pair<ushort, string>(6, "fty"));

    var sortedOC = from item in _collection
                   orderby item.Key
                   select item;

    foreach (var i in sortedOC)
    {
        Debug.WriteLine(i);
    }

}

public class Pair<TKey, TValue>
{
    private TKey _key;

    public TKey Key
    {
        get { return _key; }
        set { _key = value; }
    }
    private TValue _value;

    public TValue Value
    {
        get { return _value; }
        set { _value = value; }
    }

    public Pair(TKey key, TValue value)
    {
        _key = key;
        _value = value;

    }

    public override string ToString()
    {
        return this.Key + ":" + this.Value;
    }
}

編集

ObservableCollectionを返すには、sortedOCなどで.ToObservableCollectionを呼び出します。 この実装

OP EDITオブザーバブルをソートし、ソートされた同じオブジェクトを返すには、拡張メソッドを使用します。大規模なコレクションの場合、コレクション変更通知の数に注意してください

public static void Sort<T>(this ObservableCollection<T> observable) where T : IComparable<T>, IEquatable<T>
    {
        List<T> sorted = observable.OrderBy(x => x).ToList();

        int ptr = 0;
        while (ptr < sorted.Count)
        {
            if (!observable[ptr].Equals(sorted[ptr]))
            {
                T t = observable[ptr];
                observable.RemoveAt(ptr);
                observable.Insert(sorted.IndexOf(t), t);
            }
            else
            {
                ptr++;
            }
        }
    }

使用法:オブザーバーを使用したサンプル(Personクラスを使用してシンプルにする)

public class Person:IComparable<Person>,IEquatable<Person>
    { 
        public string Name { get; set; }
        public int Age { get; set; }

        public int CompareTo(Person other)
        {
            if (this.Age == other.Age) return 0;
            return this.Age.CompareTo(other.Age);
        }

        public override string ToString()
        {
            return Name + " aged " + Age;
        }

        public bool Equals(Person other)
        {
            if (this.Name.Equals(other.Name) && this.Age.Equals(other.Age)) return true;
            return false;
        }
    }

  static void Main(string[] args)
    {
        Console.WriteLine("adding items...");
        var observable = new ObservableCollection<Person>()
        {
            new Person { Name = "Katy", Age = 51 },
            new Person { Name = "Jack", Age = 12 },
            new Person { Name = "Bob",  Age = 13 },
            new Person { Name = "John", Age = 14 },
            new Person { Name = "Mary", Age = 41 },
            new Person { Name = "Jane", Age = 20 },
            new Person { Name = "Jim",  Age = 39 },
            new Person { Name = "Sue",  Age = 15 },
            new Person { Name = "Kim",  Age = 19 }
        };

        //what do observers see?
        observable.CollectionChanged += (o, e) => {

            if (e.OldItems != null)
            {
                foreach (var item in e.OldItems)
                {
                    Console.WriteLine("removed {0} at index {1}", item, e.OldStartingIndex);
                }
            }

            if (e.NewItems != null)
            {
                foreach (var item in e.NewItems)
                {
                    Console.WriteLine("added {0} at index {1}", item, e.NewStartingIndex);
                }
            }};            

        Console.WriteLine("\nsorting items...");
        observable.Sort();
    };

上記からの出力:
インデックス0で51歳のKatyを削除しました
インデックス8で51歳のケイティを追加
インデックス3で41歳のメアリーを削除
インデックス7に41歳のメアリーを追加
インデックス3で20歳のジェーンを削除しました
インデックス5で20歳のジェーンを追加しました
インデックス3で39歳のジムを削除しました
インデックス6に39歳のジムを追加
インデックス4で20歳のジェーンを削除
インデックス5で20歳のジェーンを追加しました

PersonクラスはIComparableとIEquatableの両方を実装します。後者は、発生する変更通知の数を減らすためにコレクションへの変更を最小限に抑えるために使用されます

20
Andrew

この単純な拡張機能は、私にとって見事に機能しました。 MyObjectIComparableであることを確認する必要がありました。 MyObjectsのオブザーバブルコレクションでsortメソッドが呼び出されると、CompareToMyObjectメソッドが呼び出され、Logical Sortメソッドが呼び出されます。ここに掲載されている残りの回答のすべての機能を備えているわけではありませんが、まさに必要なものです。

static class Extensions
{
    public static void Sort<T>(this ObservableCollection<T> collection) where T : IComparable
    {
        List<T> sorted = collection.OrderBy(x => x).ToList();
        for (int i = 0; i < sorted.Count(); i++)
            collection.Move(collection.IndexOf(sorted[i]), i);
    }
}

public class MyObject: IComparable
{
    public int CompareTo(object o)
    {
        MyObject a = this;
        MyObject b = (MyObject)o;
        return Utils.LogicalStringCompare(a.Title, b.Title);
    }

    public string Title;

}
  .
  .
  .
myCollection = new ObservableCollection<MyObject>();
//add stuff to collection
myCollection.Sort();
77
NielW

私はこの質問が古いことを知っていますが、グーグル検索中に偶然に起こり、ここにあるものよりも良い答えを提供する関連するブログエントリを見つけました:

http://kiwigis.blogspot.com/2010/03/how-to-sort-obversablecollection.html

UPDATE

@romkynsがコメントで指摘している ObservableSortedList は、ソート順を自動的に維持します。

ソートされた順序で項目を維持する監視可能なコレクションを実装します。特に、順序の変更につながるアイテムプロパティの変更は正しく処理されます。

ただし、発言にも注意してください

関連するインターフェイスが比較的複雑で、ドキュメントが比較的質が悪いため、バグがある場合があります( https://stackoverflow.com/a/5883947/3308 を参照)。

38
Eric J.

次の簡単な方法を使用できます。

public static void Sort<TSource, TKey>(this Collection<TSource> source, Func<TSource, TKey> keySelector)
{
    List<TSource> sortedList = source.OrderBy(keySelector).ToList();
    source.Clear();
    foreach (var sortedItem in sortedList)
        source.Add(sortedItem);
}

次のようにソートできます。

_collection.Sort(i => i.Key);

詳細: http://jaider.net/2011-05-04/sort-a-observablecollection/

24
Jaider

WPFは、ListCollectionViewクラスを使用して、すぐにライブソートを提供します...

public ObservableCollection<string> MyStrings { get; set; }
private ListCollectionView _listCollectionView;
private void InitializeCollection()
{
    MyStrings = new ObservableCollection<string>();
    _listCollectionView = CollectionViewSource.GetDefaultView(MyStrings) 
              as ListCollectionView;
    if (_listCollectionView != null)
    {
        _listCollectionView.IsLiveSorting = true;
        _listCollectionView.CustomSort = new 
                CaseInsensitiveComparer(CultureInfo.InvariantCulture);
    }
}

この初期化が完了すると、それ以上のことはありません。パッシブソートよりも優れている点は、ListCollectionViewが開発者に透過的な方法ですべての面倒な作業を行うことです。新しいアイテムは、正しい並べ替え順序で自動的に配置されます。 TのIComparerから派生するクラスはすべて、カスタムソートプロパティに適しています。

ドキュメントおよびその他の機能については、 ListCollectionView を参照してください。

16
Gayot Fow

上記の「Richie」のブログでのバブルソート拡張メソッドアプローチが好きでしたが、オブジェクト全体を比較するだけでソートする必要はありません。私はより頻繁にオブジェクトの特定のプロパティでソートしたいです。そこで、OrderByのようにキーセレクターを受け入れるように変更して、ソートするプロパティを選択できるようにしました。

    public static void Sort<TSource, TKey>(this ObservableCollection<TSource> source, Func<TSource, TKey> keySelector)
    {
        if (source == null) return;

        Comparer<TKey> comparer = Comparer<TKey>.Default;

        for (int i = source.Count - 1; i >= 0; i--)
        {
            for (int j = 1; j <= i; j++)
            {
                TSource o1 = source[j - 1];
                TSource o2 = source[j];
                if (comparer.Compare(keySelector(o1), keySelector(o2)) > 0)
                {
                    source.Remove(o1);
                    source.Insert(j, o1);
                }
            }
        }
    }

新しいコレクションを返す代わりに、ObservableCollectionの既存のインスタンスをソートすることを除いて、OrderByを呼び出すのと同じ方法で呼び出します。

ObservableCollection<Person> people = new ObservableCollection<Person>();
...

people.Sort(p => p.FirstName);
15
xr280xr

@NielWの答えは、実際のインプレースソートのための方法です。 IComparableを使用しなくても済むように、少し変更したソリューションを追加したかったのです。

static class Extensions
{
    public static void Sort<TSource, TKey>(this ObservableCollection<TSource> collection, Func<TSource, TKey> keySelector)
    {
        List<TSource> sorted = collection.OrderBy(keySelector).ToList();
        for (int i = 0; i < sorted.Count(); i++)
            collection.Move(collection.IndexOf(sorted[i]), i);
    }
}

これで、ほとんどのLINQメソッドと同様に呼び出すことができます。

myObservableCollection.Sort(o => o.MyProperty);
9
Jonesopolis

NeilWの回答に追加にしたいと思います。 orderbyに似たメソッドを組み込むため。このメソッドを拡張機能として追加します。

public static void Sort<T>(this ObservableCollection<T> collection, Func<T,T> keySelector) where T : IComparable
{
    List<T> sorted = collection.OrderBy(keySelector).ToList();
    for (int i = 0; i < sorted.Count(); i++)
        collection.Move(collection.IndexOf(sorted[i]), i);
}

そして次のように使用します:

myCollection = new ObservableCollection<MyObject>();

//Sorts in place, on a specific Func<T,T>
myCollection.Sort(x => x.ID);
8
DR.

バリエーションは、 selection sort アルゴリズムを使用して、コレクションを所定の場所に並べ替えます。要素は Move メソッドを使用して所定の位置に移動されます。各移動は、NotifyCollectionChangedAction.MoveCollectionChanged イベントを発生させます(また、プロパティ名Item[]PropertyChanged を発生させます)。

このアルゴリズムにはいくつかの素晴らしい特性があります:

  • このアルゴリズムは、安定したソートとして実装できます。
  • コレクション内で移動されるアイテムの数(たとえば、起動されるCollectionChangedイベント)は、ほとんどの場合、挿入ソートやバブルソートなどの他の同様のアルゴリズムよりも少なくなります。

アルゴリズムは非常に簡単です。コレクションは、最小の要素を見つけるために反復され、コレクションの先頭に移動されます。すべての要素が所定の位置に移動するまで、2番目の要素から順にプロセスが繰り返されます。このアルゴリズムはそれほど効率的ではありませんが、ユーザーインターフェイスに表示するものは何でもかまいません。ただし、移動操作の数の点では、非常に効率的です。

簡単にするために、要素がIComparable<T>を実装する必要がある拡張メソッドを次に示します。他のオプションは、IComparer<T>またはFunc<T, T, Int32>を使用しています。

public static class ObservableCollectionExtensions {

  public static void Sort<T>(this ObservableCollection<T> collection) where T : IComparable<T> {
    if (collection == null)
      throw new ArgumentNullException("collection");

    for (var startIndex = 0; startIndex < collection.Count - 1; startIndex += 1) {
      var indexOfSmallestItem = startIndex;
      for (var i = startIndex + 1; i < collection.Count; i += 1)
        if (collection[i].CompareTo(collection[indexOfSmallestItem]) < 0)
          indexOfSmallestItem = i;
      if (indexOfSmallestItem != startIndex)
        collection.Move(indexOfSmallestItem, startIndex);
    }
  }

}

コレクションのソートは、単に拡張メソッドを呼び出すだけです。

var collection = new ObservableCollection<String>(...);
collection.Sort();
8

Xr280xr回答の拡張メソッドを少し改善するために、オプションのboolパラメーターを追加して、ソートが降順かどうかを判断します。また、カルロスPの提案をその回答へのコメントに含めました。下記を参照してください。

public static void Sort<TSource, TKey>(this ObservableCollection<TSource> source, Func<TSource, TKey> keySelector, bool desc = false)
    {
        if (source == null) return;

        Comparer<TKey> comparer = Comparer<TKey>.Default;

        for (int i = source.Count - 1; i >= 0; i--)
        {
            for (int j = 1; j <= i; j++)
            {
                TSource o1 = source[j - 1];
                TSource o2 = source[j];
                int comparison = comparer.Compare(keySelector(o1), keySelector(o2));
                if (desc && comparison < 0)
                    source.Move(j, j - 1);
                else if (!desc && comparison > 0)
                    source.Move(j - 1, j);
            }
        }
    }

コレクションを常にソートしたままにする必要がありますか?ペアを取得するとき、それらを常にソートする必要がありますか、それとも数回だけです(おそらくプレゼンテーションのためだけですか)。あなたのコレクションはどれくらい大きいと思いますか?使用する魔女の方法を決定するのに役立つ多くの要因があります。

コレクションを常にソートする必要がある場合、要素を挿入または削除し、挿入速度が問題にならない場合でも、@ Gerrie Schenckが言及またはチェックアウトしたような何らかのSortedObservableCollectionを実装する必要があります この実装

コレクションを数回だけソートする必要がある場合は、次を使用します。

my_collection.OrderBy(p => p.Key);

これはコレクションをソートするのに時間がかかりますが、それでも、それを使って何をするかに応じて最善のソリューションになるかもしれません。

2
bruno conde

私の現在の答えはすでに最も票を集めていますが、これを行うより良い、より現代的な方法を見つけました。

class MyObject 
{
      public int id { get; set; }
      public string title { get; set; }
}

ObservableCollection<MyObject> myCollection = new ObservableCollection<MyObject>();

//add stuff to collection
// .
// .
// .

myCollection = new ObservableCollection<MyObject>(
    myCollection.OrderBy(n => n.title, Comparer<string>.Create(
    (x, y) => (Utils.Utils.LogicalStringCompare(x, y)))));
2
NielW

私の場合、これらの答えはどれもうまくいきませんでした。それはバインディングを台無しにするか、それは一種の悪夢のような非常に多くの追加のコーディングを必要とするか、答えがちょうど壊れているためです。だから、ここに私が考えたもう一つの簡単な答えがあります。コードがはるかに少なく、this.sortタイプのメソッドが追加された同じ監視可能なコレクションのままです。この方法で行うべきではない理由(効率など)があれば教えてください。

public class ScoutItems : ObservableCollection<ScoutItem>
{
    public void Sort(SortDirection _sDir, string _sItem)
    {
             //TODO: Add logic to look at _sItem and decide what property to sort on
            IEnumerable<ScoutItem> si_enum = this.AsEnumerable();

            if (_sDir == SortDirection.Ascending)
            {
                si_enum = si_enum.OrderBy(p => p.UPC).AsEnumerable();
            } else
            {
                si_enum = si_enum.OrderByDescending(p => p.UPC).AsEnumerable();
            }

            foreach (ScoutItem si in si_enum)
            {
                int _OldIndex = this.IndexOf(si);
                int _NewIndex = si_enum.ToList().IndexOf(si);
                this.MoveItem(_OldIndex, _NewIndex);
            }
      }
}

... ScoutItemは私のパブリッククラスです。ずっとシンプルに見えました。追加の利点:実際に機能し、バインディングを台無しにしたり、新しいコレクションを返したりしません。

1
maplemale

一体何なのか、早速まとめて答えを投げます...ここでは他の実装と少し似ていますが、誰にでも追加します:

(かろうじてテストされた、うまくいけば私は恥ずかしくない)

最初にいくつかの目標を述べましょう(私の仮定):

1)通知などを維持するために、ObservableCollection<T>を適切にソートする必要があります。

2)ひどく非効率であってはなりません(つまり、何かcloseが標準の「良い」ソート効率になります)

public static class Ext
{
    public static void Sort<T>(this ObservableCollection<T> src)
        where T : IComparable<T>
    {
        // Some preliminary safety checks
        if(src == null) throw new ArgumentNullException("src");
        if(!src.Any()) return;

        // N for the select,
        // + ~ N log N, assuming "smart" sort implementation on the OrderBy
        // Total: N log N + N (est)
        var indexedPairs = src
            .Select((item,i) => Tuple.Create(i, item))
            .OrderBy(tup => tup.Item2);
        // N for another select
        var postIndexedPairs = indexedPairs
            .Select((item,i) => Tuple.Create(i, item.Item1, item.Item2));
        // N for a loop over every element
        var pairEnum = postIndexedPairs.GetEnumerator();
        pairEnum.MoveNext();
        for(int idx = 0; idx < src.Count; idx++, pairEnum.MoveNext())
        {
            src.RemoveAt(pairEnum.Current.Item1);
            src.Insert(idx, pairEnum.Current.Item3);            
        }
        // (very roughly) Estimated Complexity: 
        // N log N + N + N + N
        // == N log N + 3N
    }
}
1
JerKimball

新しいクラスSortedObservableCollectionを作成し、ObservableCollectionから派生させて、IComparable<Pair<ushort, string>>を実装します。

1
Gerrie Schenck

1つの方法は、リストに変換してからSort()を呼び出し、比較デリゲートを提供することです。何かのようなもの:-

(未テスト)

my_collection.ToList().Sort((left, right) => left == right ? 0 : (left > right ? -1 : 1));
1
Adam Ralph

これは私のために働いた、どこか昔にそれを見つけた。

// SortableObservableCollection
public class SortableObservableCollection<T> : ObservableCollection<T>
    {
        public SortableObservableCollection(List<T> list)
            : base(list)
        {
        }

        public SortableObservableCollection()
        {
        }

        public void Sort<TKey>(Func<T, TKey> keySelector, System.ComponentModel.ListSortDirection direction)
        {
            switch (direction)
            {
                case System.ComponentModel.ListSortDirection.Ascending:
                    {
                        ApplySort(Items.OrderBy(keySelector));
                        break;
                    }
                case System.ComponentModel.ListSortDirection.Descending:
                    {
                        ApplySort(Items.OrderByDescending(keySelector));
                        break;
                    }
            }
        }

        public void Sort<TKey>(Func<T, TKey> keySelector, IComparer<TKey> comparer)
        {
            ApplySort(Items.OrderBy(keySelector, comparer));
        }

        private void ApplySort(IEnumerable<T> sortedItems)
        {
            var sortedItemsList = sortedItems.ToList();

            foreach (var item in sortedItemsList)
            {
                Move(IndexOf(item), sortedItemsList.IndexOf(item));
            }
        }
    }

使用法:

MySortableCollection.Sort(x => x, System.ComponentModel.ListSortDirection.Ascending);
1
Stacked

これは私がOC拡張機能で行うことです:

    /// <summary>
    /// Synches the collection items to the target collection items.
    /// This does not observe sort order.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="source">The items.</param>
    /// <param name="updatedCollection">The updated collection.</param>
    public static void SynchCollection<T>(this IList<T> source, IEnumerable<T> updatedCollection)
    {
        // Evaluate
        if (updatedCollection == null) return;

        // Make a list
        var collectionArray = updatedCollection.ToArray();

        // Remove items from FilteredViewItems not in list
        source.RemoveRange(source.Except(collectionArray));

        // Add items not in FilteredViewItems that are in list
        source.AddRange(collectionArray.Except(source));
    }

    /// <summary>
    /// Synches the collection items to the target collection items.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="source">The source.</param>
    /// <param name="updatedCollection">The updated collection.</param>
    /// <param name="canSort">if set to <c>true</c> [can sort].</param>
    public static void SynchCollection<T>(this ObservableCollection<T> source,
        IList<T> updatedCollection, bool canSort = false)
    {
        // Synch collection
        SynchCollection(source, updatedCollection.AsEnumerable());

        // Sort collection
        if (!canSort) return;

        // Update indexes as needed
        for (var i = 0; i < updatedCollection.Count; i++)
        {
            // Index of new location
            var index = source.IndexOf(updatedCollection[i]);
            if (index == i) continue;

            // Move item to new index if it has changed.
            source.Move(index, i);
        }
    }
1
Xcalibur37

これが最もエレガントなソリューションだと思います。

http://www.xamlplayground.org/post/2009/07/18/Use-CollectionViewSource-effectively-in-MVVM-applications.aspx

1
jaccso

さて、ObservableSortedListをXAMLで動作させるのに問題があったので、先に進んで SortingObservableCollection を作成しました。 ObservableCollectionを継承しているため、XAMLで動作し、98%のコードカバレッジで単体テストを行いました。私は自分のアプリでそれを使用しましたが、バグがないとは約束しません。気軽に貢献してください。コードの使用例は次のとおりです。

var collection = new SortingObservableCollection<MyViewModel, int>(Comparer<int>.Default, model => model.IntPropertyToSortOn);

collection.Add(new MyViewModel(3));
collection.Add(new MyViewModel(1));
collection.Add(new MyViewModel(2));
// At this point, the order is 1, 2, 3
collection[0].IntPropertyToSortOn = 4; // As long as IntPropertyToSortOn uses INotifyPropertyChanged, this will cause the collection to resort correctly

これはPCLなので、Windowsストア、Windows Phone、および.NET 4.5.1で動作するはずです。

1
Weston

1つだけでなく複数の項目でソートできるようにする必要がありました。この回答は他の回答のいくつかに基づいていますが、より複雑なソートが可能です。

static class Extensions
{
    public static void Sort<T, TKey>(this ObservableCollection<T> collection, Func<ObservableCollection<T>, TKey> sort)
    {
        var sorted = (sort.Invoke(collection) as IOrderedEnumerable<T>).ToArray();
        for (int i = 0; i < sorted.Count(); i++)
            collection.Move(collection.IndexOf(sorted[i]), i);
    }
}

使用するときは、一連のOrderBy/ThenBy呼び出しを渡します。このような:

Children.Sort(col => col.OrderByDescending(xx => xx.ItemType == "drive")
                    .ThenByDescending(xx => xx.ItemType == "folder")
                    .ThenBy(xx => xx.Path));
0
J.H.