Làm cách nào để sắp xếp một bộ sưu tập có thể quan sát được?


97

Tôi có một lớp sau:

[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
}

Cái mà tôi đã đưa vào 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: Làm cách nào để sắp xếp nó theo khóa?


Bạn đang tìm kiếm một triển khai sắp xếp trong lớp hay chỉ bất kỳ kiểu sắp xếp nào sẽ thực hiện?
okw

Không chắc chắn làm thế nào để hiểu điều đó. Về cơ bản tôi chỉ muốn có nó được sắp xếp, bộ sưu tập sẽ không thể rất lớn (20 bản ghi tối đa) để bất cứ điều gì sẽ làm (rất có thể)
Maciek

Xem phần này để biết giải pháp WPF stackoverflow.com/questions/1945461/…
Gayot Fow

Xem các câu trả lời trên trang này: dấu hiệu rất rõ ràng về một API bị hỏng khi nó có hơn 22 câu trả lời cho một số chức năng quan trọng và cơ bản.
Gerry

Câu trả lời:


19

Việc sắp xếp một đối tượng có thể quan sát và trả về cùng một đối tượng đã được sắp xếp có thể được thực hiện bằng phương pháp mở rộng. Đối với các bộ sưu tập lớn hơn, hãy để ý số lượng các thông báo thay đổi bộ sưu tập, ví dụ:

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++;
            }
        }
    }

cách sử dụng: Lấy mẫu với một người quan sát (sử dụng một lớp Người để giữ cho nó đơn giản)

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

Đầu ra từ phía trên:
loại bỏ Katy tuổi 51 ở chỉ số 0
thêm Katy tuổi 51 ở chỉ số 8
loại bỏ Mary tuổi 41 ở chỉ mục 3
đã thêm Mary tuổi 41 ở chỉ mục 7
đã xóa Jane tuổi 20 ở chỉ mục 3
đã thêm Jane tuổi 20 ở chỉ mục 5
đã loại bỏ Jim 39 tuổi ở chỉ số 3
đã thêm Jim tuổi 39 ở chỉ số 6
đã loại bỏ Jane tuổi 20 ở chỉ số 4
đã thêm Jane tuổi 20 ở chỉ số 5

Lớp Person triển khai cả ICompABLE và IEquatable, lớp sau này được sử dụng để giảm thiểu các thay đổi đối với bộ sưu tập để giảm số lượng thông báo thay đổi được đưa ra

  • CHỈNH SỬA Sắp xếp cùng một bộ sưu tập mà không cần tạo một bản sao mới *

Để trả về một ObservableCollection, hãy gọi .ToObservableCollection trên * sortedOC * bằng cách sử dụng ví dụ: [triển khai này] [1].

**** Câu trả lời orig - điều này tạo ra một bộ sưu tập mới **** Bạn có thể sử dụng linq như phương thức doSort bên dưới minh họa. Đoạn mã nhanh: sản xuất

3: xey 6: fty 7: aaa

Ngoài ra, bạn có thể sử dụng một phương thức mở rộng trên chính bộ sưu tập

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

Tìm thấy điều này và kết luận nó hữu ích nhất. Có phải LINQ tạo nên varOC được sắp xếp không?
Jason94

9
Không phải là người yêu thích câu trả lời này vì nó không cung cấp cho bạn một bộ sưu tập ObservableCollection được sắp xếp.
xr280xr

63
-1 vì nó không sắp xếp các ObservableCollection , nhưng thay vì tạo ra một bộ sưu tập mới.
Kos,

2
Mã được cập nhật sẽ hoạt động, nhưng có độ phức tạp về thời gian là O (n ^ 2). Điều này có thể được cải thiện thành O (n * log (n)) bằng cách sử dụng BinarySearchthay vì IndexOf.
William Morrison

2
Giải pháp tuyệt vời! Đối với những cái kế thừa từ ObservableCollection <T>, có thể sử dụng phương thức MoveItem () được bảo vệ thay vì sử dụng phương thức RemoveAt và Insert. Xem thêm: referencesource.microsoft.com/#system/compmod/system/...
Herman Cordes

84

Phần mở rộng đơn giản này đã hoạt động tốt đối với tôi. Tôi chỉ cần chắc chắn rằng đó MyObjectIComparable. Khi phương pháp sắp xếp được gọi trên tập hợp có thể quan sát đượcMyObjects , CompareTophương thức trên MyObjectđược gọi, phương thức này gọi phương thức Sắp xếp logic của tôi. Mặc dù nó không có tất cả chuông và còi của phần còn lại của câu trả lời được đăng ở đây, nhưng đó chính xác là những gì tôi cần.

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

7
đây sẽ là câu trả lời
thumbmunkeys

1
Cập nhật câu trả lời của tôi ở trên vì nó là một trong những địa chỉ và cải thiện hiệu suất chấp nhận qua câu trả lời này mà đặt ra thông báo thay đổi cho tất cả mọi thứ trong bộ sưu tập
Andrew

3
Câu trả lời chính xác. Bất kỳ lý do tại sao bạn sử dụng return Utils.LogicalStringCompare(a.Title, b.Title);thay vì return string.Compare(a.Title, b.Title);? @NeilW
Joe

2
@Joe, tôi cần so sánh logic thay vì so sánh chuỗi tiêu chuẩn, đó là lý do tại sao tôi cần viết phần mở rộng ngay từ đầu. Chuỗi logic so sánh số loại trong chuỗi đúng cách, chứ không phải là ra lệnh cho họ như dây (1, 2, 20, 1000 thay vì 1, 1000, 2, 20, vv)
NielW

4
đây hoàn toàn là con đường để đi. Tôi đã thêm một câu trả lời của riêng mình để mở rộng điều này, cho phép bạn chuyển vào KeySelector thay vì sử dụng ICompABLE, như LINQ thường làm.
Jonesopolis

39

Tôi đã tìm thấy một mục blog có liên quan cung cấp câu trả lời tốt hơn những bài viết ở đây:

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

CẬP NHẬT

Các ObservableSortedList rằng @romkyns điểm ra trong các ý kiến tự động duy trì thứ tự sắp xếp.

Triển khai một tập hợp có thể quan sát được để duy trì các mục của nó theo thứ tự được sắp xếp. Đặc biệt, các thay đổi đối với thuộc tính mặt hàng dẫn đến thay đổi thứ tự được xử lý chính xác.

Tuy nhiên cũng lưu ý nhận xét

Có thể gặp lỗi do sự phức tạp so sánh của giao diện liên quan và tài liệu tương đối nghèo nàn của nó (xem https://stackoverflow.com/a/5883947/33080 ).


2
Thật vậy, blog này hữu ích hơn. Tuy nhiên, tôi vẫn chưa tìm ra câu trả lời phù hợp cho câu hỏi về việc có một bộ sưu tập có thể quan sát được duy trì việc phân loại của nó khi các mục được thêm vào và loại bỏ. Tôi sẽ viết của riêng tôi mà tôi nghĩ.
Stephen Drew

@Steve Bạn có thể thử cái này .
Roman Starkov

Cảm ơn liên kết, tôi đã sử dụng phương pháp mở rộng vì đây có vẻ là giải pháp gọn gàng nhất. Làm việc một nét duyên dáng: D
pengibot

bw có ai nhận thấy rằng blog có lỗi đánh máy trong tên tệp html (obversablecollection) không? : P
laishiekai

1
@romkyns câu trả lời là mở rộng ObservableCollection <T>. GridView nhận ra nó rất tốt sau đó. Sau đó, chỉ cần ẩn các phương thức của nó, giống như bạn làm. Tôi sẽ đăng một giải pháp đầy đủ khi tôi có thời gian.
Weston

25

Bạn có thể sử dụng phương pháp đơn giản này:

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

Bạn có thể sắp xếp như thế này:

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

Chi tiết hơn: http://jaider.net/2011-05-04/sort-a-observablecollection/


4
Điều này sẽ xóa ObservableCollection sau đó lại thêm tất cả các đối tượng - vì vậy nó đáng chú ý là nếu UI của bạn bị ràng buộc vào bộ sưu tập, bạn sẽ không thấy các thay đổi hoạt hình, ví dụ như khi mục di chuyển
Carlos P

1
Tôi không chắc tại sao bạn phải hiển thị các mục di chuyển xung quanh ... ví dụ: bạn thường ObservableCollectionbị ràng buộc vào ItemSource của danh sách thả xuống và bạn không thấy bộ sưu tập nào cả. Ngoài ra, hoạt động xóa và lấp đầy này là cực nhanh ... "chậm" có thể là loại đã được tối ưu hóa. cuối cùng, bạn có thể sửa đổi mã này để triển khai phương thức di chuyển của mình, việc có sortedlistsourcephần còn lại thật dễ dàng.
Jaider 12/10/12

3
Nếu bạn bị ràng buộc vào một menu thả xuống thì bạn sẽ không được lợi khi thấy các mục di chuyển xung quanh, điều đó đúng. Tuy nhiên, nếu bạn bị ràng buộc với ListBox, thì các khung như WPF hoặc Silverlight hoặc Ứng dụng Windows Store sẽ cung cấp phản hồi trực quan hữu ích khi các đối tượng trong bộ sưu tập được lập chỉ mục lại.
Carlos P

Mặc dù cách này nhanh hơn so với phương pháp Di chuyển, nhưng điều này làm tăng một số sự kiện Đặt lại / Thêm. Câu trả lời được bình chọn cao nhất (Cách tiếp cận di chuyển) giảm thiểu điều này và tăng đúng Movecác sự kiện, điều đó cũng chỉ dành cho những câu trả lời thực sự được di chuyển.
nawfal

18

WPF cung cấp phân loại trực tiếp ngay lập tức bằng cách sử dụng ListCollectionViewlớp ...

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

Khi quá trình khởi tạo này hoàn tất, bạn không cần làm gì nữa. Ưu điểm so với sắp xếp thụ động là ListCollectionView thực hiện tất cả các công việc nặng nhọc theo cách minh bạch đối với nhà phát triển. Các mặt hàng mới sẽ tự động được đặt theo đúng thứ tự sắp xếp của chúng. Bất kỳ lớp nào bắt nguồn từ IComparerT đều phù hợp với thuộc tính sắp xếp tùy chỉnh.

Xem ListCollectionView để biết tài liệu và các tính năng khác.


6
mà thực sự đã hoạt động: D đó là một cách giải pháp tốt hơn so với giải pháp "sử dụng quá mức" khác cho một nhiệm vụ đơn giản như vậy.
MushyPeas

Blog của bạn đã đi đâu?
phoog

Vấn đề với những thứ "trong suốt" là bạn không thể nhìn thấy nơi để tìm khi nó không hoạt động. Tài liệu của Microsoft có một ví dụ minh bạch 100%, tức là bạn hoàn toàn không thấy nó.
Paul McCarthy

15

Tôi thích cách tiếp cận phương pháp mở rộng sắp xếp bong bóng trên blog của "Richie" ở trên, nhưng tôi không nhất thiết chỉ muốn sắp xếp so sánh toàn bộ đối tượng. Tôi thường muốn sắp xếp trên một thuộc tính cụ thể của đối tượng. Vì vậy, tôi đã sửa đổi nó để chấp nhận một bộ chọn khóa theo cách OrderBy thực hiện để bạn có thể chọn thuộc tính nào để sắp xếp:

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

Cái mà bạn sẽ gọi giống như cách bạn gọi OrderBy ngoại trừ nó sẽ sắp xếp phiên bản hiện có của ObservableCollection của bạn thay vì trả về một bộ sưu tập mới:

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

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

1
Cảm ơn bạn đã đăng bài này - như đã chỉ ra trong các nhận xét trên blog của Richie, có một số cải tiến đáng giá đối với mã này; đặc biệt là sử dụng phương pháp 'Di chuyển' của nguồn. Tôi đoán điều này sẽ thay thế các dòng Xóa / Chèn bằng source.Move (j-1, j);
Carlos P

2
Thuật toán sắp xếp này không được tối ưu hóa en.wikipedia.org/wiki/Sorting_algorithm
Jaider

@Jaider Có, nó được tối ưu hóa, không chỉ cho tốc độ thô tổng thể.
jv42

Điều này làm tăng một số sự kiện Xóa / Thêm (đối với mọi N tôi tin) .. Câu trả lời được bình chọn cao nhất sẽ giảm thiểu điều này và tăng đúng sự kiện Di chuyển, điều đó cũng chỉ dành cho những sự kiện thực sự được di chuyển. Chìa khóa ở đây là không thực hiện sắp xếp tại chỗ ngay lập tức, thay vào đó hãy sắp xếp nó từ bên ngoài bằng cách sử dụng OrderByvà sau đó thực hiện so sánh để tìm ra thay đổi thực tế.
nawfal

11

Câu trả lời của @ NielW là cách để thực hiện, để phân loại tại chỗ thực sự. Tôi muốn thêm một giải pháp được thay đổi một chút cho phép bạn bỏ qua việc phải sử dụng 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);
    }
}

bây giờ bạn có thể gọi nó giống như hầu hết các phương thức LINQ:

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

2
Đối với một cookie sô cô la bổ sung, bạn có thể thêm tham số boolean "Tăng dần" và if(!Ascending) sorted.Reverse();ngay trước for: D (và không cần phải lo lắng về bộ nhớ, phương thức Đảo ngược đó không tạo bất kỳ đối tượng mới nào, nó đảo ngược tại chỗ)
Sharky

Theo bộ sưu tập thử nghiệm của tôi, Move (0,0) dẫn đến sự kiện CollectionChanged. Do đó, trước tiên sẽ là một ứng biến hiệu suất để kiểm tra xem có cần phải di chuyển hay không.
sa. Ngày

10

Tôi muốn Thêm vào câu trả lời của NeilW . Để kết hợp một phương pháp giống với orderby. Thêm phương thức này làm tiện ích mở rộng:

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

Và sử dụng như:

myCollection = new ObservableCollection<MyObject>();

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

8

Một biến thể là nơi bạn sắp xếp bộ sưu tập tại chỗ bằng thuật toán sắp xếp lựa chọn . Các phần tử được chuyển vào vị trí bằng Movephương pháp này. Mỗi bước di chuyển sẽ kích hoạt CollectionChangedsự kiện với NotifyCollectionChangedAction.Move(và cả PropertyChangedvới tên thuộc tính Item[]).

Thuật toán này có một số thuộc tính tốt:

  • Thuật toán có thể được thực hiện như một loại ổn định.
  • Số lượng các mục được di chuyển trong bộ sưu tập (ví dụ: CollectionChangedcác sự kiện được kích hoạt) hầu như luôn ít hơn các thuật toán tương tự khác như sắp xếp chèn và sắp xếp bong bóng.

Thuật toán khá đơn giản. Tập hợp được lặp lại để tìm phần tử nhỏ nhất, sau đó được chuyển đến đầu tập hợp. Quá trình được lặp lại bắt đầu từ phần tử thứ hai và cứ tiếp tục như vậy cho đến khi tất cả các phần tử đã được chuyển vào vị trí. Thuật toán không quá hiệu quả nhưng đối với bất kỳ thứ gì bạn sẽ hiển thị trong giao diện người dùng thì điều đó không thành vấn đề. Tuy nhiên, về số lượng hoạt động di chuyển thì nó khá hiệu quả.

Đây là một phương thức mở rộng để đơn giản hóa yêu cầu các phần tử phải triển khai IComparable<T>. Các tùy chọn khác đang sử dụng một IComparer<T>hoặc mộ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);
    }
  }

}

Việc sắp xếp một bộ sưu tập chỉ đơn giản là gọi phương thức mở rộng:

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

1
Đây là cách sắp xếp ưa thích của tôi so với tất cả những gì được mô tả ở đây, rất tiếc là phương pháp Move không khả dụng trong Silverlight 5.
Eduardo Brites

1
Tôi nhận được lỗi 'Profiler.Profile.ProfileObject' không thể được sử dụng làm tham số loại 'T' trong kiểu hoặc phương thức chung 'ObservableCollectionExtensions.Sort <T> (ObservableCollection <T>)'. Không có chuyển đổi tham chiếu ngầm nào từ 'Profiler.Profile.ProfileObject' thành 'System.ICompABLE <Profiler.Profile.ProfileObject>
New Bee

1
@NewBee: Phương pháp Phần mở rộng này chỉ định một hạn chế chung trên Tđể có thể sắp xếp các yếu tố trong bộ sưu tập. Sắp xếp liên quan đến khái niệm lớn hơn và nhỏ hơn và chỉ bạn mới có thể xác định cách ProfileObjectsắp xếp. Để sử dụng phương pháp mở rộng, bạn cần triển khai IComparable<ProfileObject>trên ProfileObject. Các lựa chọn thay thế khác như đã lưu ý chỉ định một IComparer<ProfileObject>hoặc a Func<ProfileObject, ProfileObject, int>và thay đổi mã phân loại cho phù hợp.
Martin Liversage

4

Để cải thiện một chút phương thức mở rộng trên câu trả lời xr280xr, tôi đã thêm một tham số bool tùy chọn để xác định xem việc sắp xếp có giảm dần hay không. Tôi cũng đưa đề xuất của Carlos P vào bình luận cho câu trả lời đó. Vui lòng xem bên dưới.

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

2

Bạn có cần sắp xếp bộ sưu tập của mình mọi lúc không? Khi truy xuất các cặp, bạn có cần chúng luôn được sắp xếp hay chỉ trong một vài lần (có thể chỉ để trình bày)? Bạn mong đợi bộ sưu tập của mình sẽ lớn đến mức nào? Có rất nhiều yếu tố có thể giúp bạn quyết định sử dụng phương pháp phù thủy.

Nếu bạn cần bộ sưu tập được sắp xếp mọi lúc, ngay cả khi bạn chèn hoặc xóa các phần tử và tốc độ chèn không phải là vấn đề, có thể bạn nên triển khai một số loại SortedObservableCollectionnhư @Gerrie Schenck đã đề cập hoặc kiểm tra triển khai này .

Nếu bạn cần sắp xếp bộ sưu tập của mình chỉ để sử dụng một vài lần:

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

Điều này sẽ mất một chút thời gian để sắp xếp bộ sưu tập, nhưng ngay cả như vậy, nó có thể là giải pháp tốt nhất tùy thuộc vào những gì bạn đang làm với nó.


1
Liên kết trong câu trả lời này là đến mã được cấp phép của LGPL, vì vậy nếu bạn là Silverlight (không thể liên kết động) hoặc không phải là mã nguồn mở, hãy cẩn thận với mã đó.
yzorg

2

Câu trả lời hiện tại của tôi đã có nhiều phiếu bầu nhất, nhưng tôi đã tìm thấy một cách tốt hơn và hiện đại hơn để làm điều này.

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

sẽ tốt hơn nếu cập nhật câu trả lời ban đầu?
Nathan Hughes

Không. Nó đã được ủng hộ nhiều hơn bất kỳ câu trả lời nào khác. Tôi sẽ không cho rằng mọi người muốn làm theo cách này. Tôi chỉ nghĩ rằng tôi sẽ đưa ra một cách khác để làm điều đó, đặc biệt là vì có rất nhiều tiền thưởng cho các câu trả lời mới.
NielW

1

Tạo một lớp mới SortedObservableCollection, bắt nguồn từ đó ObservableCollectionvà triển khai IComparable<Pair<ushort, string>>.


1

Một cách sẽ là chuyển nó thành Danh sách và sau đó gọi Sort (), cung cấp một đại diện so sánh. Cái gì đó như:-

(chưa được kiểm tra)

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


1

Cái quái gì vậy, tôi cũng sẽ đưa ra một câu trả lời được tập hợp nhanh ... nó trông hơi giống một số triển khai khác ở đây, nhưng tôi sẽ thêm nó bất kỳ ai:

(hầu như không được kiểm tra, hy vọng tôi không tự làm phiền mình)

Trước tiên, hãy nêu một số mục tiêu (giả định của tôi):

1) Phải sắp xếp ObservableCollection<T> đúng vị trí, để duy trì thông báo, v.v.

2) Không được kém hiệu quả kinh khủng (tức là thứ gì đó gần với hiệu quả sắp xếp "tốt" tiêu chuẩn)

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

Không có câu trả lời nào trong số này hoạt động trong trường hợp của tôi. Hoặc bởi vì nó vặn chặt ràng buộc, hoặc đòi hỏi quá nhiều mã hóa bổ sung đến mức nó giống như một cơn ác mộng, hoặc câu trả lời chỉ bị hỏng. Vì vậy, đây là một câu trả lời đơn giản hơn mà tôi nghĩ. Nó ít mã hơn rất nhiều và nó vẫn là một bộ sưu tập có thể quan sát được với một loại phương thức this.sort bổ sung. Hãy cho tôi biết nếu có lý do nào đó tôi không nên làm theo cách này (hiệu quả, v.v.)?

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

... Nơi mà ScoutItem là lớp công khai của tôi. Chỉ có vẻ đơn giản hơn rất nhiều. Lợi ích bổ sung: nó thực sự hoạt động và không gây rối với các ràng buộc hoặc trả lại một bộ sưu tập mới, v.v.


1

Được rồi, vì tôi đang gặp sự cố khi đưa ObservableSortedList hoạt động với XAML, tôi đã tiếp tục và tạo SortingObservableCollection . Nó kế thừa từ ObservableCollection, vì vậy nó hoạt động với XAML và đơn vị tôi đã thử nghiệm nó với độ phủ mã 98%. Tôi đã sử dụng nó trong các ứng dụng của riêng mình, nhưng tôi sẽ không hứa rằng nó không có lỗi. Hãy tự do đóng góp. Đây là cách sử dụng mã mẫu:

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

Đó là một PCL, vì vậy nó sẽ hoạt động với Windows Store, Windows Phone và .NET 4.5.1.


1
Bạn có thể không nên sử dụng newtất cả các phương thức đó, nếu ai đó có một phiên bản được nhập chung chung hơn thì các phương thức đó sẽ không được gọi. Thay vào đó, overridemọi phương thức có thể ghi đè và thay đổi chúng khi cần thiết hoặc dự phòng base.Method(...). Ví dụ, bạn thậm chí không cần phải lo lắng về .Addviệc sử dụng nội bộ .InsertItem, vì vậy nếu .InsertItemđược ghi đè và điều chỉnh, .Addsẽ không gây rối với việc đặt hàng.
HB

1

Đây là những gì tôi làm với phần mở rộng 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

Điều này đã làm việc cho tôi, đã tìm thấy nó từ lâu ở đâu đó.

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

Sử dụng:

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

0

Tôi cần có khả năng sắp xếp theo nhiều thứ chứ không chỉ một. Câu trả lời này dựa trên một số câu trả lời khác nhưng nó cho phép phân loại phức tạp hơn.

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

Khi bạn sử dụng nó, hãy chuyển một loạt lệnh gọi OrderBy / ThenBy. Như thế này:

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

0

Tôi đã học được rất nhiều từ các giải pháp khác, nhưng tôi nhận thấy một vài vấn đề. Đầu tiên, một số phụ thuộc vào IndexOf có xu hướng khá chậm đối với các danh sách lớn. Thứ hai, ObservableCollection của tôi có các thực thể EF và việc sử dụng Remove dường như làm hỏng một số thuộc tính khóa ngoại. Có lẽ tôi đang làm gì đó sai.

Bất kể, A Move có thể được sử dụng để thay thế Xóa / Chèn, nhưng điều đó gây ra một số vấn đề với việc sửa hiệu suất.

Để khắc phục sự cố hiệu suất, tôi tạo một từ điển với các giá trị được sắp xếp IndexOf. Để cập nhật từ điển và duy trì các thuộc tính thực thể, hãy sử dụng hoán đổi được thực hiện với hai lần di chuyển thay vì một lần như được triển khai trong các giải pháp khác.

Một động tác duy nhất sẽ thay đổi chỉ mục của các phần tử giữa các vị trí, điều này sẽ làm mất hiệu lực của từ điển IndexOf. Việc thêm bước thứ hai để thực hiện hoán đổi sẽ khôi phục các vị trí.

public static void Sort<TSource, TKey>(this ObservableCollection<TSource> collection, Func<TSource, TKey> keySelector)
{
    List<TSource> sorted = collection.OrderBy(keySelector).ToList();
    Dictionary<TSource, int> indexOf = new Dictionary<TSource, int>();

    for (int i = 0; i < sorted.Count; i++)
        indexOf[sorted[i]] = i;

    int idx = 0;
    while (idx < sorted.Count)
        if (!collection[idx].Equals(sorted[idx])) {
            int newIdx = indexOf[collection[idx]]; // where should current item go?
            collection.Move(newIdx, idx); // move whatever's there to current location
            collection.Move(idx + 1, newIdx); // move current item to proper location
        }
        else {
            idx++;
        }
}

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

Oh tôi nhận được nó ... Gayot muốn tặng tiền thưởng cho câu trả lời downvoted nhất lol
NielW

Chưa bao giờ thấy việc trao tiền thưởng trong sự mỉa mai :)
nawfal
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.