Bagaimana cara mengurutkan koleksi yang dapat diamati?

97

Saya memiliki kelas berikut:

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

Yang saya masukkan ke dalam 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"));

T: Bagaimana cara mengurutkan berdasarkan kunci?

Maciek
sumber
Apakah Anda mencari implementasi pengurutan di dalam kelas atau jenis pengurutan apa saja yang bisa digunakan?
okw
Tidak yakin bagaimana memahaminya. Pada dasarnya saya hanya ingin memilahnya, koleksinya tidak akan terlalu besar (maks 20 item) jadi apa pun akan dilakukan (kemungkinan besar)
Maciek
Lihat ini untuk solusi WPF stackoverflow.com/questions/1945461/…
Gayot Fow
Lihatlah jawaban di halaman ini: indikasi yang sangat jelas dari API yang rusak ketika dibutuhkan 22+ jawaban untuk beberapa fungsi kritis dan dasar.
Gerry
Kemungkinan duplikat dari Sort ObservableCollection <string> hingga C #
Tim Pohlmann

Jawaban:

21

Mengurutkan yang dapat diamati dan mengembalikan objek yang sama yang diurutkan dapat dilakukan dengan menggunakan metode ekstensi. Untuk koleksi yang lebih besar hati-hati terhadap jumlah koleksi yang berubah pemberitahuan.

Saya telah memperbarui kode saya untuk meningkatkan kinerja dan menangani duplikat (terima kasih kepada nawfal karena menyoroti kinerja yang buruk dari aslinya meskipun itu bekerja dengan baik pada contoh data asli). Observable dipartisi menjadi setengah yang diurutkan kiri dan setengah kanan yang tidak diurutkan, di mana setiap kali item minimum (seperti yang ditemukan dalam daftar yang diurutkan) digeser ke akhir partisi yang diurutkan dari yang tidak diurutkan. Kasus terburuk O (n). Pada dasarnya semacam seleksi (Lihat di bawah untuk keluaran).

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

        int ptr = 0;
        while (ptr < sorted.Count - 1)
        {
            if (!collection[ptr].Equals(sorted[ptr]))
            {
                int idx = search(collection, ptr+1, sorted[ptr]);
                collection.Move(idx, ptr);
            }
            
            ptr++;
        }
    }

    public static int search<T>(ObservableCollection<T> collection, int startIndex, T other)
            {
                for (int i = startIndex; i < collection.Count; i++)
                {
                    if (other.Equals(collection[i]))
                        return i;
                }
    
                return -1; // decide how to handle error case
            }

penggunaan: Sampel dengan pengamat (menggunakan kelas Person agar tetap sederhana)

    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 = "Alice", Age = 39},
                    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 = 5},
                    new Person {Name = "Kim", Age = 19}
                };
    
                //what do observers see?
            
    
observable.CollectionChanged += (sender, e) =>
        {
            Console.WriteLine(
                e.OldItems[0] + " move from " + e.OldStartingIndex + " to " + e.NewStartingIndex);
            int i = 0;
            foreach (var person in sender as ObservableCollection<Person>)
            {
                if (i == e.NewStartingIndex)
                {
                    Console.Write("(" + (person as Person).Age + "),");
                }
                else
                {
                    Console.Write((person as Person).Age + ",");
                }
                
                i++;
            }

            Console.WriteLine();
        };

Detail kemajuan pengurutan yang menunjukkan bagaimana koleksi diputar:

Sue aged 5 move from 8 to 0
(5),51,12,13,39,14,41,20,39,19,
Jack aged 12 move from 2 to 1
5,(12),51,13,39,14,41,20,39,19,
Bob aged 13 move from 3 to 2
5,12,(13),51,39,14,41,20,39,19,
John aged 14 move from 5 to 3
5,12,13,(14),51,39,41,20,39,19,
Kim aged 19 move from 9 to 4
5,12,13,14,(19),51,39,41,20,39,
Jane aged 20 move from 8 to 5
5,12,13,14,19,(20),51,39,41,39,
Alice aged 39 move from 7 to 6
5,12,13,14,19,20,(39),51,41,39,
Jim aged 39 move from 9 to 7
5,12,13,14,19,20,39,(39),51,41,
Mary aged 41 move from 9 to 8
5,12,13,14,19,20,39,39,(41),51,

Kelas Person mengimplementasikan IComparable dan IEquatable yang terakhir digunakan untuk meminimalkan perubahan pada koleksi sehingga dapat mengurangi jumlah pemberitahuan perubahan yang dimunculkan

  • EDIT Mengurutkan koleksi yang sama tanpa membuat salinan baru *

Untuk mengembalikan ObservableCollection, panggil .ToObservableCollection di * sortOC * menggunakan eg [implementasi ini] [1].

**** orig jawaban - ini membuat koleksi baru **** Anda dapat menggunakan linq seperti yang diilustrasikan oleh metode doSort di bawah ini. Cuplikan kode cepat: menghasilkan

3: xey 6: fty 7: aaa

Alternatifnya, Anda dapat menggunakan metode ekstensi pada koleksi itu sendiri

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;
    }
}
Andrew
sumber
Menemukan ini dan sangat membantu. Apakah LINQ yang menyusun var diurutkan?
Jason94
9
Bukan penggemar jawaban ini karena tidak memberi Anda ObservableCollection yang diurutkan.
xr280xr
63
-1 karena tidak memilah yang ObservableCollection , melainkan menciptakan koleksi baru.
Kos
2
Kode yang diperbarui akan berfungsi, tetapi memiliki kompleksitas waktu O (n ^ 2). Ini dapat ditingkatkan menjadi O (n * log (n)) dengan menggunakan BinarySearchalih-alih IndexOf.
William Morrison
2
Solusi luar biasa! Untuk yang mewarisi dari ObservableCollection <T>, dimungkinkan untuk menggunakan metode MoveItem () yang dilindungi daripada menggunakan metode RemoveAt dan Insert. Lihat juga: referensiource.microsoft.com/#system/compmod/system/…
Herman Cordes
84

Ekstensi sederhana ini bekerja dengan baik untuk saya. Saya hanya harus memastikan MyObjectitu IComparable. Ketika metode pengurutan dipanggil pada koleksi yang dapat diamati MyObjects, CompareTometode pada MyObjectdipanggil, yang memanggil metode Urutkan Logis saya. Meskipun tidak semua lonceng dan peluit dari sisa jawaban yang diposting di sini, itulah yang saya butuhkan.

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();
NielW
sumber
7
ini harus menjadi jawabannya
thumbmunkeys
1
Memperbarui jawaban saya di atas karena itu adalah yang diterima dan membahas peningkatan kinerja atas jawaban ini yang menimbulkan pemberitahuan perubahan untuk semua yang ada di koleksi
Andrew
3
Jawaban yang bagus. Ada alasan mengapa Anda menggunakan, return Utils.LogicalStringCompare(a.Title, b.Title);bukan return string.Compare(a.Title, b.Title);? @NeilW
Joe
2
@ Joe, saya perlu melakukan perbandingan logis dan bukan perbandingan string standar, itulah sebabnya saya perlu menulis ekstensi di tempat pertama. String logis membandingkan nomor yang diurutkan dalam string dengan benar, daripada mengurutkannya seperti string (1, 2, 20, 1000, bukan 1, 1000, 2, 20, dll.)
NielW
4
ini benar-benar cara untuk pergi. Saya menambahkan jawaban saya sendiri yang memperluas ini, memungkinkan Anda untuk meneruskan keySelector daripada menggunakan IComparable, seperti yang biasanya dilakukan LINQ.
Jonesopolis
39

Saya menemukan entri blog relevan yang memberikan jawaban lebih baik daripada yang ada di sini:

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

MEMPERBARUI

The ObservableSortedList yang @romkyns poin di komentar secara otomatis menjaga urutan.

Menerapkan koleksi yang dapat diamati yang mempertahankan itemnya dalam urutan yang diurutkan. Secara khusus, perubahan pada properti item yang menghasilkan perubahan urutan ditangani dengan benar.

Namun perhatikan juga ucapan tersebut

Mungkin bermasalah karena kompleksitas komparatif antarmuka yang terlibat dan dokumentasinya yang relatif buruk (lihat https://stackoverflow.com/a/5883947/33080 ).

Eric J.
sumber
2
Memang blog ini lebih bermanfaat. Namun, saya belum menemukan jawaban yang layak untuk pertanyaan memiliki koleksi yang dapat diamati yang mempertahankan penyortirannya saat item ditambahkan dan dihapus. Saya akan menulis sendiri, saya pikir.
Stephen Drew
@Steve Anda dapat mencoba yang ini .
Roman Starkov
Terima kasih untuk tautannya, saya menggunakan metode ekstensi karena ini tampaknya solusi paling rapi.
Mempesona
bw apakah ada yang memperhatikan bahwa blog memiliki kesalahan ketik pada nama file html (koleksi obversablecollection)? : P
laishiekai
1
@romkyns jawabannya adalah memperluas ObservableCollection <T>. GridView mengenalinya dengan baik. Kemudian sembunyikan saja metodenya, seperti yang Anda lakukan. Saya akan memposting solusi lengkap ketika saya punya waktu.
Weston
25

Anda dapat menggunakan metode sederhana ini:

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

Anda bisa mengurutkan seperti ini:

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

Detail lebih lanjut: http://jaider.net/2011-05-04/sort-a-observablecollection/

Jaider
sumber
4
Ini membersihkan ObservableCollection lalu menambahkan kembali semua objek - jadi perlu diperhatikan bahwa jika UI Anda terikat ke koleksi, Anda tidak akan melihat perubahan animasi, misalnya saat item bergerak
Carlos P
1
Saya tidak yakin mengapa Anda harus menampilkan item bergerak ... misalnya Anda biasanya ObservableCollectionterikat ke ItemSource dari dropdown dan Anda tidak melihat koleksinya sama sekali. Juga operasi pembersihan dan pengisian ini sangat cepat ... yang "lambat" bisa jadi jenis yang sudah dioptimalkan. akhirnya, Anda dapat memodifikasi kode ini untuk mengimplementasikan metode pemindahan Anda, memiliki sortedlistdan sourcesisanya mudah.
Jaider
3
Jika Anda terikat ke drop-down maka Anda tidak akan mendapat manfaat dari melihat item bergerak, itu benar. Jika Anda terikat ke ListBox, kerangka kerja seperti WPF atau Silverlight atau Aplikasi Windows Store akan memberikan umpan balik visual yang berguna karena objek dalam koleksi diindeks ulang.
Carlos P
Meskipun ini lebih cepat daripada pendekatan Pindah, ini menimbulkan sejumlah peristiwa Atur Ulang / Tambah. Jawaban pilihan tertinggi (Pendekatan bergerak) meminimalkan hal ini dan dengan benar memunculkan Moveperistiwa, itu juga hanya untuk yang benar-benar terharu.
nawfal
19

WPF menyediakan penyortiran langsung di luar kotak menggunakan ListCollectionViewkelas ...

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

Setelah inisialisasi ini selesai, tidak ada lagi yang harus dilakukan. Keuntungan dari jenis pasif adalah ListCollectionView melakukan semua pekerjaan berat dengan cara yang transparan bagi pengembang. Item baru secara otomatis ditempatkan dalam urutan yang benar. Setiap kelas yang diturunkan dari IComparerT cocok untuk properti sortir kustom.

Lihat ListCollectionView untuk dokumentasi dan fitur lainnya.

Gayot Fow
sumber
6
yang benar-benar berhasil: D itu adalah solusi yang jauh lebih baik daripada solusi "rekayasa berlebihan" lainnya untuk tugas yang begitu sederhana.
MushyPeas
Kemana perginya blog Anda?
phoog
Masalah dengan hal-hal yang "transparan" adalah Anda tidak dapat melihat ke mana mencarinya jika tidak berfungsi. Dokumentasi Microsoft memiliki contoh transparan 100%, yaitu Anda tidak dapat melihatnya sama sekali.
Paul McCarthy
15

Saya menyukai pendekatan metode ekstensi semacam gelembung pada blog "Richie" di atas, tetapi saya tidak ingin hanya mengurutkan membandingkan seluruh objek. Saya lebih sering ingin menyortir properti tertentu dari objek. Jadi saya memodifikasinya untuk menerima pemilih kunci seperti yang dilakukan OrderBy sehingga Anda dapat memilih properti mana yang akan disortir:

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

Yang akan Anda panggil dengan cara yang sama seperti Anda memanggil OrderBy kecuali itu akan mengurutkan instance yang ada dari ObservableCollection Anda alih-alih mengembalikan koleksi baru:

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

people.Sort(p => p.FirstName);
xr280xr
sumber
1
Terima kasih telah memposting ini - seperti yang ditunjukkan di komentar di blog Richie, ada beberapa peningkatan yang berharga untuk kode ini; khususnya menggunakan metode 'Pindahkan' dari sumbernya. Saya kira ini akan menggantikan baris Hapus / Sisipkan dengan source.Move (j-1, j);
Carlos P
2
Algoritme pengurutan ini tidak dioptimalkan en.wikipedia.org/wiki/Sorting_algorithm
Jaider
@ Jaider Ya, ini dioptimalkan, hanya saja tidak untuk kecepatan mentah secara keseluruhan.
jv42
Hal ini menimbulkan sejumlah peristiwa Hapus / Tambah (untuk setiap N yang saya percaya) .. Jawaban dengan suara terbanyak meminimalkan ini dan dengan tepat memunculkan peristiwa Pindah, itu juga hanya untuk yang benar-benar dipindahkan. Kuncinya di sini adalah tidak langsung melakukan penyortiran di tempat, alih-alih menyortirnya secara eksternal menggunakan OrderBydan kemudian melakukan perbandingan untuk mengetahui perubahan aktual.
nawfal
11

@NielW Jawabannya adalah cara untuk pergi, untuk penyortiran di tempat yang nyata. Saya ingin menambahkan solusi yang sedikit diubah yang memungkinkan Anda melewati keharusan menggunakan 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);
    }
}

sekarang Anda dapat menyebutnya seperti kebanyakan metode LINQ lainnya:

myObservableCollection.Sort(o => o.MyProperty);
Jonesopolis
sumber
2
Untuk cookie cokelat tambahan, Anda dapat menambahkan parameter boolean "Ascending" dan if(!Ascending) sorted.Reverse();tepat sebelum for: D (dan tidak perlu -lebih jauh- khawatir tentang memori, metode Reverse itu tidak membuat objek baru, metode ini sebaliknya)
Sharky
Menurut koleksi pengujian saya. Pindah (0,0) mengarah ke peristiwa CollectionChanged. Oleh karena itu, akan menjadi peningkatan kinerja untuk memeriksa terlebih dahulu apakah suatu gerakan memang diperlukan.
yaitu
10

Saya ingin menambahkan jawaban NeilW . Untuk memasukkan metode yang menyerupai orderby. Tambahkan metode ini sebagai ekstensi:

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

Dan gunakan seperti:

myCollection = new ObservableCollection<MyObject>();

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

Variasi adalah tempat Anda mengurutkan koleksi menggunakan algoritme pengurutan pilihan . Elemen dipindahkan ke tempatnya menggunakan Movemetode ini. Setiap gerakan akan mengaktifkan CollectionChangedacara dengan NotifyCollectionChangedAction.Move(dan juga PropertyChangeddengan nama properti Item[]).

Algoritme ini memiliki beberapa properti bagus:

  • Algoritme dapat diimplementasikan sebagai jenis yang stabil.
  • Jumlah item yang dipindahkan dalam koleksi (misalnya CollectionChangedperistiwa yang diaktifkan) hampir selalu lebih sedikit daripada algoritme serupa lainnya seperti jenis penyisipan dan jenis gelembung.

Algoritmanya cukup sederhana. Koleksi diulang untuk menemukan elemen terkecil yang kemudian dipindahkan ke awal koleksi. Proses ini diulangi mulai dari elemen kedua dan seterusnya sampai semua elemen dipindahkan ke tempatnya. Algoritme ini tidak terlalu efisien tetapi untuk apa pun yang akan Anda tampilkan di antarmuka pengguna, hal itu seharusnya tidak menjadi masalah. Namun, dari segi jumlah operasi pemindahan cukup efisien.

Berikut adalah metode ekstensi yang untuk kesederhanaan memerlukan implementasi elemen IComparable<T>. Pilihan lain menggunakan IComparer<T>atau a 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);
    }
  }

}

Menyortir koleksi hanyalah masalah menjalankan metode ekstensi:

var collection = new ObservableCollection<String>(...);
collection.Sort();
Martin Liversage
sumber
1
Ini adalah cara penyortiran yang saya sukai dari semua yang dijelaskan di sini, sayangnya metode Pindah tidak tersedia di Silverlight 5.
Eduardo Brites
1
Saya mendapatkan kesalahan 'Profiler.Profile.ProfileObject' tidak dapat digunakan sebagai parameter tipe 'T' dalam tipe umum atau metode 'ObservableCollectionExtensions.Sort <T> (ObservableCollection <T>)'. Tidak ada konversi referensi implisit dari 'Profiler.Profile.ProfileObject' ke 'System.IComparable <Profiler.Profile.ProfileObject>
New Bee
1
@NewBee: metode ekstensi ini menentukan kendala generik pada Tuntuk dapat mengurutkan elemen-elemen dalam koleksi. Pengurutan melibatkan konsep lebih besar dan lebih kecil dari dan hanya Anda yang dapat menentukan cara pengurutan ProfileObject. Untuk menggunakan metode ekstensi Anda perlu untuk mengimplementasikan IComparable<ProfileObject>pada ProfileObject. Alternatif lain adalah seperti yang dicatat dengan menentukan IComparer<ProfileObject>atau a Func<ProfileObject, ProfileObject, int>dan mengubah kode pengurutan yang sesuai.
Martin Liversage
4

Untuk meningkatkan sedikit metode ekstensi pada jawaban xr280xr saya menambahkan parameter bool opsional untuk menentukan apakah pengurutan menurun atau tidak. Saya juga memasukkan saran yang dibuat oleh Carlos P dalam komentar untuk jawaban itu. Silahkan lihat di bawah ini.

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);
            }
        }
    }
Jonathan Morales Vélez
sumber
2

Apakah Anda perlu menyortir koleksi Anda setiap saat? Saat mengambil pasangan, apakah Anda membutuhkannya untuk selalu disortir, atau hanya untuk beberapa kali (mungkin hanya untuk presentasi)? Berapa besar koleksi yang Anda harapkan? Ada banyak faktor yang dapat membantu Anda memutuskan metode penyihir yang akan digunakan.

Jika Anda memerlukan koleksi untuk disortir setiap saat, bahkan ketika Anda memasukkan atau menghapus elemen dan kecepatan penyisipan tidak menjadi masalah, mungkin Anda harus menerapkan beberapa jenis SortedObservableCollectionseperti yang disebutkan @Gerrie Schenck atau lihat penerapan ini .

Jika Anda ingin koleksi Anda disortir hanya untuk beberapa kali gunakan:

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

Ini akan membutuhkan waktu untuk menyortir koleksi, tetapi meskipun demikian, ini mungkin solusi terbaik tergantung pada apa yang Anda lakukan dengannya.

bruno conde
sumber
1
Tautan dalam jawaban ini adalah ke kode berlisensi LGPL, jadi jika Anda Silverlight (tidak dapat menautkan secara dinamis) atau tidak open source, berhati-hatilah dengan kode itu.
yzorg
2

Jawaban saya saat ini sudah mendapatkan suara terbanyak, tetapi saya menemukan cara yang lebih baik dan lebih modern untuk melakukan ini.

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)))));
NielW
sumber
bukankah lebih baik memperbarui jawaban asli?
Nathan Hughes
Tidak. Itu sudah mendapat suara positif lebih banyak dari jawaban lainnya. Saya tidak akan berasumsi bahwa orang lebih suka melakukannya dengan cara ini. Hanya berpikir saya akan menawarkan cara lain untuk melakukannya, terutama karena ada karunia untuk jawaban baru.
NielW
1

Buat kelas baru SortedObservableCollection, dapatkan dari ObservableCollectiondan implementasikan IComparable<Pair<ushort, string>>.

Gerrie Schenck
sumber
1

Salah satu caranya adalah dengan mengubahnya menjadi List dan kemudian memanggil Sort (), memberikan delegasi perbandingan. Sesuatu seperti:-

(belum dicoba)

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

Apa-apaan, saya akan memberikan jawaban yang dibuat dengan cepat juga ... sepertinya beberapa implementasi lain di sini, tetapi saya akan menambahkannya siapa saja:

(hampir tidak diuji, semoga saya tidak mempermalukan diri sendiri)

Mari nyatakan beberapa tujuan terlebih dahulu (asumsi saya):

1) Harus menyortir ObservableCollection<T>di tempat, untuk mempertahankan pemberitahuan, dll.

2) Tidak boleh sangat tidak efisien (yaitu, sesuatu yang mendekati efisiensi penyortiran "baik" standar)

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
    }
}
JerKimball
sumber
1

Tak satu pun dari jawaban ini berhasil dalam kasus saya. Entah karena itu mengacaukan pengikatan, atau membutuhkan begitu banyak pengkodean tambahan sehingga itu semacam mimpi buruk, atau jawabannya hanya rusak. Jadi, inilah jawaban sederhana lainnya yang saya pikir. Ini adalah kode yang jauh lebih sedikit dan tetap menjadi koleksi yang dapat diamati yang sama dengan tambahan metode this.sort. Beri tahu saya jika ada alasan mengapa saya tidak boleh melakukannya dengan cara ini (efisiensi, dll.)?

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

... Dimana ScoutItem adalah kelas umum saya. Sepertinya jauh lebih sederhana. Manfaat tambahan: ini benar-benar berfungsi dan tidak mengacaukan binding atau mengembalikan koleksi baru, dll.

maplemale
sumber
1

Baiklah, karena saya mengalami masalah dalam mendapatkan ObservableSortedList untuk bekerja dengan XAML, saya melanjutkan dan membuat SortingObservableCollection . Ini mewarisi dari ObservableCollection, sehingga bekerja dengan XAML dan saya telah mengujinya ke 98% cakupan kode. Saya telah menggunakannya di aplikasi saya sendiri, tetapi saya tidak akan menjanjikan bahwa ini bebas bug. Jangan ragu untuk berkontribusi. Berikut adalah contoh penggunaan kode:

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

Ini adalah PCL, jadi harus bekerja dengan Windows Store, Windows Phone, dan .NET 4.5.1.

Weston
sumber
1
Anda mungkin tidak boleh menggunakan newsemua metode tersebut, jika seseorang memiliki instans yang lebih umum diketik, metode tersebut tidak akan dipanggil. Alih override- alih, setiap metode yang dapat diganti dan ubah sesuai kebutuhan atau lakukan fallback base.Method(...). Anda misalnya bahkan tidak perlu khawatir .Addkarena itu menggunakan internal .InsertItem, jadi jika .InsertItemdiganti dan disesuaikan, .Addtidak akan main-main dengan pemesanan.
HB
1

Inilah yang saya lakukan dengan ekstensi 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);
        }
    }
Xcalibur 37
sumber
1

Ini berhasil untuk saya, sudah lama ditemukan di suatu tempat.

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

Pemakaian:

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

Saya harus dapat mengurutkan berdasarkan banyak hal, bukan hanya satu. Jawaban ini didasarkan pada beberapa jawaban lain tetapi memungkinkan pengurutan yang lebih rumit.

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

Saat Anda menggunakannya, teruskan serangkaian panggilan OrderBy / ThenBy. Seperti ini:

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

Saya belajar banyak dari solusi lain, tetapi saya menemukan beberapa masalah. Pertama, beberapa bergantung pada IndexOf yang cenderung sangat lambat untuk daftar besar. Kedua, ObservableCollection saya memiliki entitas EF dan menggunakan Hapus tampaknya merusak beberapa properti kunci asing. Mungkin aku melakukan sesuatu yang salah.

Terlepas dari itu, A Move dapat digunakan sebagai gantinya Hapus / Sisipkan, tetapi itu menyebabkan beberapa masalah dengan perbaikan kinerja.

Untuk memperbaiki masalah kinerja, saya membuat kamus dengan nilai yang diurutkan IndexOf. Untuk menjaga kamus tetap mutakhir dan untuk mempertahankan properti entitas, gunakan swap yang diimplementasikan dengan dua gerakan, bukan satu seperti yang diterapkan di solusi lain.

Satu gerakan menggeser indeks elemen antara lokasi, yang akan membatalkan kamus IndexOf. Menambahkan langkah kedua untuk mengimplementasikan swap memulihkan lokasi.

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++;
        }
}
jlear
sumber
-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));
Rex
sumber
Oh saya mengerti ... Gayot ingin memberikan bounty untuk jawaban yang paling banyak downvoted lol
NielW
Tidak pernah melihat pemberian hadiah dalam sarkasme :)
nawfal