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


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. 정렬 방법의 관찰 컬렉션 호출 될 때 MyObjectsCompareTo의 방법은 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.