代码之家  ›  专栏  ›  技术社区  ›  Maciek

如何对可观察的集合进行排序?

  •  90
  • Maciek  · 技术社区  · 15 年前

    我有以下课程:

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

    问:我如何按键排序?

    22 回复  |  直到 7 年前
        1
  •  20
  •   Andrew    10 年前

    op edit:正如很多人正确指出的那样,原来的答案不会返回相同的集合(原来更注重对字典中q部分的排序)。请参阅底部的编辑,我在这里处理可观察集合的排序。原来留在这里,因为仍在接受投票

    您可以使用linq作为下面说明的dosort方法。快速代码段:生成

    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,请调用.ToObservableCollection 索尔多克 使用例如 this implementation .

    操作编辑 可以使用扩展方法对可观察对象进行排序并返回排序后的相同对象。对于较大的集合,请注意集合更改通知的数量,例如

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

    从上面输出:
    移除了51岁的凯蒂,指数0
    在索引8中添加了51岁的凯蒂
    在索引3处删除41岁的玛丽
    增加了41岁的玛丽在索引7
    在索引3删除了20岁的简
    在索引5中添加了20岁的简。
    在索引3删除了39岁的吉姆
    增加了索引6中39岁的吉姆。
    在索引4删除了20岁的简
    在索引5中添加了20岁的简。

    Person类实现IComparable和IEquatable,后者用于最小化对集合的更改,以减少引发的更改通知数。

        2
  •  77
  •   Noctis    10 年前

    这个简单的扩展对我很有用。我只是要确保 MyObject IComparable . 当对 MyObjects ,的 CompareTo 方法对 我的对象 调用,这将调用我的逻辑排序方法。虽然它没有所有的钟声和哨声其余的答案张贴在这里,这正是我需要的。

    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();
    
        3
  •  38
  •   Community CDub    8 年前

    我知道这个问题很古老,但在谷歌搜索时偶然发现了这个问题,并找到了一个相关的博客条目,比这里的答案更好:

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

    更新

    这个 ObservableSortedList @romkyns在评论中指出,这会自动保持排序顺序。

    实现可观察集合,该集合按排序顺序维护其项。尤其是,正确处理导致订单更改的项目属性更改。

    但是也要注意这句话

    由于所涉及的接口的相对复杂性及其相对较差的文档(请参见 https://stackoverflow.com/a/5883947/33080 )

        4
  •  23
  •   Jaider    12 年前

    您可以使用以下简单方法:

    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/

        5
  •  15
  •   xr280xr    13 年前

    我喜欢上面“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);
                    }
                }
            }
        }
    

    您将以调用orderby的相同方式调用它,但它将对ObservableCollection的现有实例进行排序,而不是返回新的集合:

    ObservableCollection<Person> people = new ObservableCollection<Person>();
    ...
    
    people.Sort(p => p.FirstName);
    
        6
  •  13
  •   Gayot Fow    8 年前

    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的优势在于它以一种对开发人员透明的方式完成了所有繁重的工作。新项目将自动按正确的排序顺序放置。派生自的任何类 IComparer 适用于自定义排序属性。

    ListCollectionView 用于文档和其他功能。

        7
  •  9
  •   Jonesopolis    9 年前

    @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);
    
        8
  •  8
  •   DR.    10 年前

    我想 加上尼尔的答案 . 合并一个类似于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);
    
        9
  •  8
  •   Martin Liversage    7 年前

    一个变体是使用 selection sort 算法。元素使用 Move 方法。每一步都会触发 CollectionChanged 事件与 NotifyCollectionChangedAction.Move (还有) PropertyChanged 属性名为的 Item[] )

    这个算法有一些很好的特性:

    • 该算法可以实现为一个稳定的排序。
    • 在集合中移动的项目数(例如 集合已更改 事件触发)几乎总是比其他类似的算法(如插入排序和冒泡排序)少。

    算法很简单。迭代集合以查找最小的元素,然后将其移动到集合的开头。从第二个元素开始重复这个过程,以此类推,直到所有元素都移动到位。该算法并不是非常有效,但是对于您要在用户界面中显示的任何内容来说,它都不重要。但是,就移动操作的数量而言,它是相当有效的。

    这里有一个扩展方法,为了简单起见,需要元素实现 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();
    
        10
  •  4
  •   Jonathan Morales Vélez WilQu    12 年前

    为了稍微改进一下xr280xr answer上的扩展方法,我添加了一个可选的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);
                }
            }
        }
    
        11
  •  2
  •   bruno conde    15 年前

    您是否需要随时对收藏进行分类?在检索配对时,您需要始终对它们进行排序,还是只进行几次排序(可能只是为了演示)?你希望你的收藏品有多大?有很多因素可以帮助你决定使用巫术。

    如果需要在任何时候对集合进行排序,即使插入或删除元素和插入速度都不是问题,也可以实现某种类型的 SortedObservableCollection 就像@gerrie schenck提到的或者退房 this implementation .

    如果您只需要对收藏进行几次分类,请使用:

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

    这将需要一些时间来对集合进行排序,但即使如此,它可能是最佳的解决方案,具体取决于您如何处理它。

        12
  •  2
  •   NielW    8 年前

    我目前的答案已经获得了最多的选票,但我找到了一种更好、更现代的方法。

    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)))));
    
        13
  •  1
  •   Gerrie Schenck    15 年前

    创建新类 SortedObservableCollection ,从 ObservableCollection 实施 IComparable<Pair<ushort, string>> .

        14
  •  1
  •   Adam Ralph    15 年前

    一种方法是将其转换为列表,然后调用sort(),提供比较委托。比如:

    (未经测试)

    my_collection.ToList().Sort((left, right) => left == right ? 0 : (left > right ? -1 : 1));
    
        16
  •  1
  •   JerKimball    12 年前

    见鬼,我也会给出一个很快拼凑起来的答案……它看起来有点像这里的其他实现,但我会添加到任何人:

    (几乎没有测试过,希望我没有把自己弄脏)

    让我们先陈述一些目标(我的假设):

    1)必须排序 ObservableCollection<T> 到位,保持通知等。

    2)一定不能效率低下(即 关闭 达到标准的“良好”分拣效率)

    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
        }
    }
    
        17
  •  1
  •   maplemale    11 年前

    这些答案对我来说都不起作用。要么是因为它搞砸了绑定,要么是因为它需要太多额外的编码,以至于有点像一场噩梦,要么就是因为答案被破坏了。所以,我想还有一个更简单的答案。它的代码要少得多,它仍然是同一个可观察的集合,还有一个额外的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);
                }
          }
    }
    

    …球探是我的公共课。只是看起来简单多了。附加的好处:它实际上是有效的,并且不会干扰绑定或返回新的集合等。

        18
  •  1
  •   Weston    11 年前

    好吧,因为我在让观察员与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 Store、Windows Phone和.NET 4.5.1一起工作。

        19
  •  1
  •   Xcalibur37    9 年前

    这就是我对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);
            }
        }
    
        20
  •  1
  •   Stacked Emily M    8 年前

    这对我很有效,很久以前就在某个地方发现了。

    // 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);
    
        21
  •  0
  •   J.H.    8 年前

    我需要能够按多个东西排序,而不仅仅是一个。这个答案是基于其他一些答案,但它允许更复杂的排序。

    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));
    
        22
  •  -3
  •   Rex    12 年前
    var collection = new ObservableCollection<int>();
    
    collection.Add(7);
    collection.Add(4);
    collection.Add(12);
    collection.Add(1);
    collection.Add(20);
    
    // ascending
    collection = new ObservableCollection<int>(collection.OrderBy(a => a));
    
    // descending
    collection = new ObservableCollection<int>(collection.OrderByDescending(a => a));