관찰 가능한 컬렉션을 어떻게 정렬합니까?


97

다음 수업이 있습니다.

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

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

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

    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;

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

    #endregion

    #region IDisposable Members

    public void Dispose()
    { }

    #endregion
}

ObservableCollection에 넣었습니다.

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

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

Q : 키별로 어떻게 정렬합니까?


클래스 내에서 정렬 구현을 찾고 있습니까? 아니면 모든 유형의 정렬이 수행됩니까?
— okw

그것을 이해하는 방법을 모르겠습니다. 기본적으로 나는 그것을 분류하고 싶습니다. 컬렉션은 그다지 크지 않을 것입니다 (최대 20 개 항목). 그래서 무엇이든 할 것입니다.
— Maciek

WPF 솔루션을 보려면 this를 참조하십시오. stackoverflow.com/questions/1945461/…
— Gayot Fow

이 페이지의 답변을보십시오. 일부 중요하고 기본적인 기능에 대해 22 개 이상의 답변이 필요한 경우 고장난 API를 매우 명확하게 나타냅니다.
— Gerry

답변:


19

관찰 가능 항목을 정렬하고 정렬 된 동일한 객체를 반환하는 작업은 확장 메서드를 사용하여 수행 할 수 있습니다. 더 큰 컬렉션의 경우 컬렉션 변경 알림 수를주의하세요.

성능을 향상시키고 중복을 처리하기 위해 코드를 업데이트했습니다 (원래 데이터 예제에서는 잘 작동했지만 원본의 성능 저하를 강조한 nawfal에게 감사드립니다). 관찰 가능 항목은 왼쪽으로 정렬 된 절반과 오른쪽으로 정렬되지 않은 절반으로 분할되며, 여기서 최소 항목 (정렬 된 목록에있는)이 정렬되지 않은 파티션에서 정렬 된 파티션의 끝으로 이동 될 때마다 이동합니다. 최악의 경우 O (n). 기본적으로 선택 정렬입니다 (출력은 아래 참조).

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
            }

사용법 : 관찰자가있는 샘플 (단순하게 유지하기 위해 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 = "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();
        };

컬렉션이 피벗되는 방식을 보여주는 정렬 진행률 세부 정보 :

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,

Person 클래스는 IComparable과 IEquatable을 모두 구현합니다. 후자는 발생하는 변경 알림의 수를 줄이기 위해 컬렉션의 변경을 최소화하는 데 사용됩니다.

  • 편집 새 복사본을 만들지 않고 동일한 컬렉션을 정렬합니다. *

ObservableCollection을 반환하려면 [this implementation] [1]과 같이 * sortedOC *에서 .ToObservableCollection을 호출합니다.

**** orig answer-이것은 새로운 컬렉션을 생성합니다 **** 아래의 doSort 메소드가 보여주는 것처럼 linq를 사용할 수 있습니다. 빠른 코드 스 니펫 : 생성

3 : xey 6 : fty 7 : aaa

또는 컬렉션 자체에서 확장 메서드를 사용할 수 있습니다.

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

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

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

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

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

}

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

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

    public TValue Value
    {
        get { return _value; }
        set { _value = value; }
    }
    
    public Pair(TKey key, TValue value)
    {
        _key = key;
        _value = value;

    }

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

이것을 발견하고 가장 유용합니다. sortedOC var를 구성하는 것이 LINQ입니까?
— Jason94

9
정렬 된 ObservableCollection을 제공하지 않기 때문에이 답변의 팬이 아닙니다.
— xr280xr 2012-06-11

63
-1 정렬하지 않기 때문에 을 ObservableCollection , 대신 새 컬렉션을 만듭니다.
— Kos

2
업데이트 된 코드는 작동하지만 O (n ^ 2) 시간 복잡성이 있습니다. BinarySearch대신을 사용하여 O (n * log (n))로 개선 할 수 있습니다 IndexOf.
— William Morrison

2
훌륭한 솔루션! ObservableCollection <T>에서 상속 된 항목의 경우 RemoveAt 및 Insert 메서드를 사용하는 대신 보호 된 MoveItem () 메서드를 사용할 수 있습니다. 참고 항목 : referencesource.microsoft.com/#system/compmod/system/…
— Herman Cordes

84

이 간단한 확장은 저에게 아름답게 작동했습니다. 난 그냥 그 확인했다 MyObject이었다 IComparable. 정렬 방법의 관찰 컬렉션 호출 될 때 MyObjects의 CompareTo의 방법은 MyObject내 논리 정렬 메소드를 호출하는 호출됩니다. 여기에 게시 된 나머지 답변의 모든 종소리와 휘파람이 없지만 정확히 필요한 것입니다.

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
이것이 답이어야합니다
— thumbmunkeys

1
위의 답변이 수락
— Andrew

3
좋은 대답입니다. return Utils.LogicalStringCompare(a.Title, b.Title);대신 사용 하는 이유 는 return string.Compare(a.Title, b.Title);무엇입니까? @NeilW
— 조

2
@Joe, 표준 문자열 비교 대신 논리적 비교를 수행해야했기 때문에 처음에 확장을 작성해야했습니다. 논리 문자열 비교는 문자열처럼 순서를 지정하지 않고 문자열의 숫자를 올바르게 정렬합니다 (1, 1000, 2, 20 등 대신 1, 2, 20, 1000)
— NielW

4
이것이 절대적으로 갈 길이다. LINQ가 일반적으로하는 것처럼 IComparable을 사용하는 대신 keySelector를 전달할 수 있도록이를 확장하는 내 자신의 답변을 추가했습니다.
— Jonesopolis 2015

39

여기에있는 것보다 더 나은 답변을 제공하는 관련 블로그 항목을 찾았습니다.

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

최신 정보

ObservableSortedList 코멘트 아웃 @romkyns 포인트가 자동으로 정렬 순서를 유지하고있다.

항목을 정렬 된 순서로 유지하는 관찰 가능한 컬렉션을 구현합니다. 특히, 주문 변경을 초래하는 항목 속성 변경은 올바르게 처리됩니다.

그러나 비고도 참고

관련된 인터페이스의 상대적 복잡성과 상대적으로 열악한 문서로 인해 버그가있을 수 있습니다 ( https://stackoverflow.com/a/5883947/33080 참조 ).


2
실제로이 블로그가 더 유용합니다. 그러나 항목이 추가되고 제거 될 때 정렬을 유지하는 관찰 가능한 컬렉션이 있다는 질문에 대한 적절한 대답을 아직 찾지 못했습니다. 나는 내 생각에 내 자신을 쓸 것입니다.
— Stephen Drew

@Steve 당신은 이것을 시도 할 수 있습니다 .
— Roman Starkov 2012

링크 주셔서 감사합니다. 이것이 가장 깔끔한 솔루션 인 것처럼 확장 방법을 사용했습니다. 매력 작품 : D
— pengibot

bw 블로그의 html 파일 이름 (obversablecollection)에 오타가 있음을 알아 차린 사람이 있습니까? : P
— laishiekai 2013-09-12

1
@romkyns 대답은 ObservableCollection <T>를 확장하는 것이 었습니다. GridView는 잘 인식합니다. 그런 다음 당신과 마찬가지로 방법을 숨기십시오. 시간이있을 때 전체 솔루션을 게시하겠습니다.
— Weston

25

이 간단한 방법을 사용할 수 있습니다.

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/


4
이렇게하면 ObservableCollection이 지워지고 모든 개체가 다시 추가됩니다. 따라서 UI가 컬렉션에 바인딩 된 경우 항목이 이동할 때 애니메이션 변경 사항이 표시되지 않는다는 점에 유의할 가치가 있습니다.
— Carlos P

1
나는 당신이 왜 움직이는 항목을 표시해야하는지 잘 모르겠습니다. 예를 들어 일반적으로 ObservableCollection드롭 다운의 ItemSource에 바인딩되어 있고 컬렉션을 전혀 볼 수 없습니다. 또한 이러한 정리 및 채우기 작업은 매우 빠릅니다. "느린"작업은 이미 최적화 된 종류 일 수 있습니다. 마지막으로, 당신은 당신의 이동 방법을 구현하기 위해이 코드를 수정을 가지고 있습니다 sortedlist그리고 source나머지는 간단합니다.
— Jaider

3
드롭 다운에 묶여 있다면 항목이 움직이는 것을 보는 것으로부터 이익을 얻지 못할 것입니다. 하지만 ListBox에 바인딩 된 경우 WPF, Silverlight 또는 Windows Store Apps와 같은 프레임 워크는 컬렉션의 개체가 다시 인덱싱 될 때 유용한 시각적 피드백을 제공합니다.
— Carlos P

이것은 이동 접근 방식보다 빠르지 만 여러 재설정 / 추가 이벤트를 발생시킵니다. 가장 많이 득표 한 답변 (이동 접근 방식)은이를 최소화하고 Move이벤트를 올바로 발생시킵니다.
— nawfal

19

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가 개발자에게 투명한 방식으로 모든 무거운 작업을 수행한다는 것입니다. 새 항목은 올바른 정렬 순서로 자동 배치됩니다. 파생되는 모든 클래스IComparerT 는 사용자 지정 정렬 속성에 적합합니다.

문서 및 기타 기능 은 ListCollectionView 를 참조하십시오 .


6
실제로 작동했습니다. D는 그러한 간단한 작업에 대해 다른 "과도하게 엔지니어링 된"솔루션보다 훨씬 나은 솔루션입니다.
— MushyPeas

블로그는 어디로 갔습니까?
— phoog

"투명한"사물의 문제는 작동하지 않을 때 어디를 볼 수 있는지 볼 수 없다는 것입니다. Microsoft의 문서에는 100 % 투명한 예가 있습니다. 즉, 전혀 볼 수 없습니다.
— Paul McCarthy

15

위의 "Richie"블로그에서 버블 정렬 확장 방법 접근 방식이 마음에 들었지만 전체 개체를 비교하는 것만으로 정렬하고 싶지는 않습니다. 나는 더 자주 개체의 특정 속성을 정렬하고 싶습니다. 그래서 OrderBy와 같은 방식으로 키 선택기를 허용하도록 수정하여 정렬 할 속성을 선택할 수 있습니다.

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

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

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

새 컬렉션을 반환하는 대신 ObservableCollection의 기존 인스턴스를 정렬한다는 점을 제외하면 OrderBy를 호출하는 것과 동일한 방식으로 호출합니다.

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

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

1
이를 게시 해 주셔서 감사합니다. Richie의 블로그에 대한 의견에서 지적했듯이이 코드에 가치있는 개선 사항이 있습니다. 특히 소스의 '이동'방법을 사용합니다. 나는 이것이 Remove / Insert 줄을 source.Move (j-1, j);
— Carlos P

2
이런 종류의 알고리즘은 최적화되지 en.wikipedia.org/wiki/Sorting_algorithm
— Jaider

@Jaider 예, 전체 원시 속도가 아니라 최적화되어 있습니다.
— jv42

이로 인해 많은 Remove / Add 이벤트가 발생합니다 (모든 N 믿고 있음). 가장 많이 투표 된 답변은이를 최소화하고 Move 이벤트를 올바로 올립니다. 실제로 이동 한 이벤트에 대해서만 마찬가지입니다. 여기서 핵심은 바로 내부 정렬 OrderBy을 수행하지 않고 대신 외부에서 사용하여 정렬 한 다음 비교를 수행하여 실제 변경 사항을 파악하는 것입니다.
— nawfal

11

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

2
여분의 초콜릿 쿠키의 경우 부울 매개 변수 "Ascending"과 : D if(!Ascending) sorted.Reverse();바로 앞에 추가 할 수 있습니다 for(그리고 메모리에 대해 더 이상 걱정할 필요가 없습니다. Reverse 메서드는 새로운 객체를 생성하지 않으며 제자리에서 역방향입니다)
— Sharky

내 테스트 collection.Move (0,0)에 따르면 CollectionChanged 이벤트가 발생합니다. 따라서 이동이 필요한지 여부를 먼저 확인하는 것은 성능 향상입니다.
— sa.he

10

NeilW의 답변 에 추가하고 싶습니다 . orderby와 유사한 방법을 통합합니다. 이 메서드를 확장으로 추가합니다.

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

그리고 다음과 같이 사용하십시오.

myCollection = new ObservableCollection<MyObject>();

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

8

변형은 선택 정렬 알고리즘을 사용하여 컬렉션을 정렬하는 곳 입니다. Move메서드를 사용하여 요소를 제자리로 이동합니다 . 각 이동은 (및 속성 이름 과 CollectionChanged함께) 이벤트를 시작 합니다.NotifyCollectionChangedAction.MovePropertyChangedItem[]

이 알고리즘에는 몇 가지 좋은 속성이 있습니다.

  • 알고리즘은 안정적인 정렬로 구현 될 수 있습니다.
  • 컬렉션에서 이동 된 항목 수 (예 : CollectionChanged발생한 이벤트)는 거의 항상 삽입 정렬 및 버블 정렬과 같은 다른 유사한 알고리즘보다 적습니다.

알고리즘은 아주 간단합니다. 컬렉션은 컬렉션의 시작 부분으로 이동되는 가장 작은 요소를 찾기 위해 반복됩니다. 이 프로세스는 모든 요소가 제자리로 이동할 때까지 두 번째 요소부터 반복됩니다. 알고리즘은 그다지 효율적이지는 않지만 사용자 인터페이스에 표시하려는 모든 것에 대해서는 중요하지 않습니다. 그러나 이동 작업의 수 측면에서 상당히 효율적입니다.

다음은 단순화를 위해 요소가 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();

1
이것은 여기에 설명 된 모든 것 중에서 선호하는 정렬 방법입니다. 불행히도 Silverlight 5에서는 Move 방법을 사용할 수 없습니다.
— Eduardo Brites 2014

1
제네릭 형식 또는 메서드 'ObservableCollectionExtensions.Sort <T> (ObservableCollection <T>)'에서 'Profiler.Profile.ProfileObject'오류를 형식 매개 변수 'T'로 사용할 수 없습니다. 'Profiler.Profile.ProfileObject'에서 'System.IComparable <Profiler.Profile.ProfileObject> 로의 암시 적 참조 변환이 없습니다
— New Bee

1
@NewBee :이 확장 메서드 는 컬렉션의 요소를 정렬 할 수 있는 일반 제약 조건 을 지정합니다 T. 정렬은보다 크거나 작다는 개념을 포함하며 ProfileObject순서 지정 방법을 정의 할 수 있습니다 . 당신이 구현하는 데 필요한 확장 메서드 사용 IComparable<ProfileObject>에를 ProfileObject. 다른 대안은 IComparer<ProfileObject>또는 a를 지정하고 Func<ProfileObject, ProfileObject, int>그에 따라 정렬 코드를 변경합니다.
— Martin Liversage 2011

4

xr280xr 응답의 확장 방법을 약간 개선하기 위해 정렬이 내림차순인지 여부를 결정하는 선택적 bool 매개 변수를 추가했습니다. 나는 또한 그 대답에 대한 의견에 Carlos 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);
            }
        }
    }

2

컬렉션을 항상 정렬해야합니까? 쌍을 검색 할 때 항상 정렬해야합니까? 아니면 몇 번만 수행해야합니까 (아마도 발표 용일 수도 있음)? 컬렉션이 얼마나 클 것으로 예상하십니까? 사용할 마녀 방법을 결정하는 데 도움이 될 수있는 많은 요소가 있습니다.

컬렉션을 항상 정렬해야한다면 요소를 삽입하거나 삭제하고 삽입 속도가 문제가되지 않는 경우 SortedObservableCollection에도 @Gerrie Schenck가 언급 한 것과 같은 종류를 구현 하거나이 구현을 확인 해야 합니다.

컬렉션을 몇 번만 정렬해야하는 경우 다음을 사용하세요.

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

컬렉션을 정렬하는 데 시간이 좀 걸리지 만 그래도 작업에 따라 가장 좋은 솔루션이 될 수 있습니다.


1
이 답변의 링크는 LGPL 라이센스 코드에 대한 것이므로 Silverlight (동적으로 링크 할 수 없음)이거나 오픈 소스가 아닌 경우 해당 코드에주의하십시오.
— yzorg

2

내 현재 답변은 이미 가장 많은 표를 얻었지만 더 좋고 더 현대적인 방법을 찾았습니다.

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

원래 답변을 업데이트하는 것이 더 낫지 않을까요?
— Nathan Hughes

아니요. 이미 다른 답변보다 더 많이 찬성되었습니다. 나는 사람들이 이런 식으로 그렇게 할 것이라고 가정하지 않을 것입니다. 특히 새로운 답변에 대한 현상금이 있었기 때문에 다른 방법을 제안 할 것이라고 생각했습니다.
— NielW

1

새 클래스를 만들고 SortedObservableCollection파생 ObservableCollection하고 구현 IComparable<Pair<ushort, string>>합니다.


1

한 가지 방법은 목록으로 변환 한 다음 Sort ()를 호출하여 비교 대리자를 제공하는 것입니다. 같은 것 :-

(미정)

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


1

도대체, 나는 신속하게 함께 대답을 던질 것입니다 ... 여기에 다른 구현과 약간 비슷해 보이지만 누구에게나 추가 할 것입니다.

(간신히 테스트 됨, 바라건대 나는 내 자신을 부끄럽지 않게)

먼저 몇 가지 목표를 설명하겠습니다 (내 가정).

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

1

이 답변 중 어느 것도 제 경우에는 효과가 없었습니다. 바인딩을 망쳐 놓거나 너무 많은 추가 코딩이 필요하기 때문에 일종의 악몽이거나 대답이 깨졌습니다. 그래서 여기에 내가 생각한 또 다른 간단한 대답이 있습니다. 코드가 훨씬 적고 this.sort 유형의 추가 메서드를 사용하여 동일한 관찰 가능한 컬렉션으로 유지됩니다. 이런 식으로하면 안되는 이유 (효율성 등)가 있는지 알려주세요.

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

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

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

... ScoutItem은 내 공개 클래스입니다. 훨씬 간단 해 보였습니다. 추가 된 이점 : 실제로 작동하며 바인딩을 엉망으로 만들거나 새 컬렉션 등을 반환하지 않습니다.


1

좋습니다 . ObservableSortedList를 XAML과 함께 사용하는 데 문제가 있었기 때문에 계속해서 SortingObservableCollection을 만들었 습니다 . ObservableCollection에서 상속되므로 XAML과 함께 작동하며 98 % 코드 적용 범위에서 단위 테스트를 거쳤습니다. 내 앱에서 사용했지만 버그가 없다고 약속하지는 않겠습니다. 자유롭게 기여하십시오. 다음은 샘플 코드 사용입니다.

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

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

PCL이므로 Windows Store, Windows Phone 및 .NET 4.5.1에서 작동합니다.


1
new누군가가 좀 더 일반적인 형식의 인스턴스를 가지고 있다면 해당 메서드는 호출되지 않을 것이므로 이러한 모든 메서드에서 사용해서는 안됩니다 . 대신 override모든 재정의 가능한 메서드를 필요에 따라 변경하거나 base.Method(...). 당신은, 예를 들어,도에 대해 걱정할 필요가 없습니다 .Add즉 내부적으로 사용하기 때문에 .InsertItem그래서 만약, .InsertItem대체 및 조정, .Add순서와 혼란하지 않습니다.
— HB

1

이것이 내가 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

이것은 나를 위해 일했으며 오래 전에 어딘가에서 발견했습니다.

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

0

하나가 아닌 여러 가지로 정렬 할 수 있어야했습니다. 이 답변은 다른 답변 중 일부를 기반으로하지만 더 복잡한 정렬이 가능합니다.

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

사용할 때 일련의 OrderBy / ThenBy 호출을 전달하십시오. 이렇게 :

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

0

다른 솔루션에서 많은 것을 배웠지 만 몇 가지 문제를 발견했습니다. 첫째, 일부는 큰 목록의 경우 매우 느린 경향이있는 IndexOf에 의존합니다. 둘째, 내 ObservableCollection에는 EF 엔터티가 있고 Remove를 사용하면 일부 외래 키 속성이 손상되는 것 같습니다. 내가 뭔가 잘못하고 있을지도 몰라.

어쨌든 A Move는 Remove / Insert 대신 사용할 수 있지만 이로 인해 성능 수정에 문제가 발생합니다.

성능 문제를 해결하기 위해 IndexOf 정렬 된 값으로 사전을 만듭니다. 사전을 최신 상태로 유지하고 엔터티 속성을 유지하려면 다른 솔루션에서 구현 된 것처럼 하나가 아닌 두 이동으로 구현 된 스왑을 사용합니다.

한 번의 이동으로 위치간에 요소의 인덱스가 이동하여 IndexOf 사전이 무효화됩니다. 스왑을 구현하기 위해 두 번째 이동을 추가하면 위치가 복원됩니다.

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

오, 나는 그것을 얻습니다 ... Gayot는 가장 downvoted 답변 롤에 현상금을주고 싶었습니다
— NielW

풍자에서 현상금 수여를 본 적이 없음 :)
— nawfal
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.