是否有一个在.NET中自动排序的列表?

时间:2011-04-19 19:54:00

标签: c# .net list sorting

我有一个Layers的集合,里面有名字和颜色。我想要做的是先根据颜色对它们进行排序,然后根据它们的名称进行排序:

class Layer
{
    public string Name {get; set;}
    public LayerColor Color {get; set;}
}

enum LayerColor
{
    Red,
    Blue,
    Green
}

像:

(red) layer2
(red) layer7
(blue) layer0
(blue) layer3
...

我正在查看SortedList,但其行为类似于字典,因此不允许重复项目。

此外,我正在使用API​​,我按创建顺序获取Layers列表,因此我需要获取Layers的完整列表,以便按照我想要的方式对其进行排序。

最终Layers的列表将绑定到WPF UI,用户可以在其中添加新的图层,这就是为什么我希望内部列表始终按照性能不重要的方式排序( Layers的数量不到一千个。

最后,{I}排序的Layers将通过以下方式访问:

class Image
{
    public MySortedList<Layer> Layers {get; set;}
}

最好的方法是什么?

8 个答案:

答案 0 :(得分:19)

派对有点晚了,但出于后人的缘故。

为了优化关注点的分离,我编写了一个包装类,它将列表排序(并允许重复),如下所示:

public class OrderedList<T> : IList<T>, ICollection<T>, IList, ICollection, IReadOnlyList<T>, IReadOnlyCollection<T>, IEnumerable<T>, IEnumerable
{
    #region Fields
    readonly List<T> _list;
    readonly IComparer<T> _comparer;
    #endregion

    #region Constructors
    OrderedList(List<T> list, IComparer<T> comparer)
    {
        _list = list;
        _comparer = comparer;
    }
    public OrderedList() 
        : this(new List<T>(), Comparer<T>.Default)
    {
    }
    public OrderedList(IComparer<T> comparer)
        : this(new List<T>(), comparer)
    {
    }
    public OrderedList(IEnumerable<T> collection)
        : this(collection, Comparer<T>.Default)
    {
    }

    public OrderedList(IEnumerable<T> collection, IComparer<T> comparer)
        : this(new List<T>(collection), comparer)
    {
        _list.Sort(comparer);
    }

    public OrderedList(int capacity)
        : this(new List<T>(capacity), Comparer<T>.Default)
    {
    }
    public OrderedList(int capacity, IComparer<T> comparer)
        : this(new List<T>(capacity), comparer)
    {
    }
    //yet to be implemented
    //public void OrderedList(Comparison<T> comparison);

    #endregion

    #region Properties
    public int Capacity { get { return _list.Capacity; } set { _list.Capacity = value; } }
    public int Count { get { return _list.Count; } }
    object IList.this[int index] { get { return _list[index]; } set { _list[index] = (T)value; } }
    public T this[int index] { get { return _list[index]; } set { _list[index] = value; } }
    //public bool IsSynchronized { get { return false; } }
    bool ICollection.IsSynchronized { get { return false; } }
    //public object SyncRoot { get { return _list; } }
    object ICollection.SyncRoot { get { return _list; } } //? should return this 
    bool IList.IsFixedSize { get { return false; } }
    bool IList.IsReadOnly { get { return false; } }
    bool ICollection<T>.IsReadOnly { get { return false; } }
    #endregion

    #region Methods
    void ICollection<T>.Add(T item)
    {
        Add(item);
    }
    /// <summary>
    /// Adds a new item to the appropriate index of the SortedList
    /// </summary>
    /// <param name="item">The item to be removed</param>
    /// <returns>The index at which the item was inserted</returns>
    public int Add(T item)
    {
        int index = BinarySearch(item);
        if (index < 0)
        {
            index = ~index;
        }
        _list.Insert(index, item);
        return index;
    }
    int IList.Add(object item)
    {
        return Add((T)item);
    }
    //NOT performance tested against other ways algorithms yet
    public void AddRange(IEnumerable<T> collection)
    {
        var insertList = new List<T>(collection);
        if (insertList.Count == 0) 
        {
            return;
        }
        if (_list.Count == 0) 
        { 
            _list.AddRange(collection);
            _list.Sort(_comparer);
            return;
        }
        //if we insert backwards, index we are inserting at does not keep incrementing
        insertList.Sort(_comparer);
        int searchLength = _list.Count;
        for (int i=insertList.Count-1;i>=0;i--)
        {
            T item = insertList[i];
            int insertIndex = BinarySearch(0, searchLength, item);
            if (insertIndex < 0)
            {
                insertIndex = ~insertIndex;
            }
            else
            {
                while (--insertIndex>=0 && _list[insertIndex].Equals(item)) { }
                insertIndex++;
            }
            if (insertIndex<=0)
            {
                _list.InsertRange(0, insertList.GetRange(0, i+1 ));
                break;
            }
            searchLength = insertIndex-1;
            item = _list[searchLength];
            int endInsert = i;
            while (--i>=0 && _comparer.Compare(insertList[i], item) > 0) { }
            i++;
            _list.InsertRange(insertIndex, insertList.GetRange(i, endInsert - i +1));
        }
    }
    public int BinarySearch(T item)
    {
        return _list.BinarySearch(item, _comparer);
    }
    public int BinarySearch(int index, int count, T item)
    {
        return _list.BinarySearch(index,count,item, _comparer);
    }
    public ReadOnlyCollection<T> AsReadOnly()
    {
        return _list.AsReadOnly();
    }
    public void Clear() { _list.Clear(); }
    public bool Contains(T item) { return BinarySearch(item) >= 0; }
    bool IList.Contains(object item)
    {
        return Contains((T)item);
    }
    public List<TOutput> ConvertAll<TOutput>(Converter<T, TOutput> converter) { return _list.ConvertAll(converter); }
    public void CopyTo(T[] array) { _list.CopyTo(array); }
    public void CopyTo(T[] array, int arrayIndex) { _list.CopyTo(array,arrayIndex); }
    void ICollection.CopyTo(Array array, int arrayIndex) { _list.CopyTo((T[])array, arrayIndex); }
    public void CopyTo(int index, T[] array, int arrayIndex, int count) { _list.CopyTo(index, array, arrayIndex, count); }
    public void ForEach(Action<T> action)
    {
        foreach (T item in _list)
        {
            action(item);
        }
    }

    IEnumerator IEnumerable.GetEnumerator() { return _list.GetEnumerator(); }
    public IEnumerator<T> GetEnumerator() { return _list.GetEnumerator(); }
    public List<T> GetRange(int index, int count) { return _list.GetRange(index,count); }

    public bool Remove(T item) 
    {
        int index = BinarySearch(item);
        if (index < 0)
        {
            return false;
        }
        _list.RemoveAt(index);
        return true;
    }
    void IList.Remove(object item)
    {
        Remove((T)item);
    }

    public void RemoveAt(int index) { _list.RemoveAt(index); }
    public void RemoveRange(int index, int count) { _list.RemoveRange(index, count); }
    public T[] ToArray() { return _list.ToArray(); }
    public void TrimExcess() { _list.TrimExcess(); }
    /// <summary>
    /// Find the first index of the given item
    /// </summary>
    /// <param name="item"></param>
    /// <returns></returns>
    public int IndexOf(T item)
    {
        int index = BinarySearch(item);
        if (index < 0) return -1;
        while(--index >= 0 && _list[index].Equals(item)){}
        return index+1;
    }

    int IList.IndexOf(object item)
    {
        return IndexOf((T)item);
    }
    /// <summary>
    /// Find the last index of the given item
    /// </summary>
    /// <param name="item"></param>
    /// <returns></returns>
    public int LastIndexOf(T item)
    {
        int index = BinarySearch(item);
        if (index < 0) return -1;
        while (++index < _list.Count && _list[index].Equals(item)) { }
        return index-1;
    }

    /// <summary>
    /// Return all values within bounds specified
    /// </summary>
    /// <param name="min">Minimum Bound</param>
    /// <param name="max">Maximum Bound</param>
    /// <returns>subset of list with values within or equal to bounds specified</returns>
    public T[] WithinRange(T min, T max)
    {
        if (_comparer.Compare(min,max) > 0)
        {
            throw new ArgumentException("min must be <= max");
        }
        int minSearchLength;
        int maxIndex = _list.BinarySearch(max, _comparer);
        if (maxIndex >= 0)
        {
            minSearchLength = maxIndex + 1;
            while (++maxIndex < _list.Count && _comparer.Compare(max, _list[maxIndex]) == 0) { }
            --maxIndex;
        }
        else
        {
            minSearchLength = ~maxIndex;
            if (minSearchLength <= 0)
            {
                return new T[0];
            }
            maxIndex = minSearchLength - 1;
        }

        int minIndex = _list.BinarySearch(0, minSearchLength, min, _comparer);
        if (minIndex >= 0)
        {
            while (--minIndex >= 0 && _comparer.Compare(max, _list[minIndex]) == 0) { }
            ++minIndex;
        }
        else
        {
            minIndex = ~minIndex;
            if (minIndex > maxIndex)
            {
                return new T[0];
            }
        }
        int length = maxIndex - minIndex + 1;
        var returnVar = new T[length];
        _list.CopyTo(minIndex, returnVar, 0, length);
        return returnVar;

    }
    #endregion

    #region NotImplemented
    const string _insertExceptionMsg = "SortedList detemines position to insert automatically - use add method without an index";
    void IList.Insert(int index, object item)
    {
        throw new NotImplementedException(_insertExceptionMsg);
    }
    void IList<T>.Insert(int index, T item)
    {
        throw new NotImplementedException(_insertExceptionMsg);
    }
    #endregion
}

所写的测试并不广泛(或相当),但如果有人想要扩展它们,则包括在内

[TestClass]
public class TestOrderedList
{
    [TestMethod]
    public void TestIntegerList()
    {
        var startList = new List<int>(new int[] { 5, 2, 1, 4, 5, 5, 2 });
        var olist = new OrderedList<int>(startList);
        startList = startList.OrderBy(l => l).ToList();
        CollectionAssert.AreEqual(startList, olist);
        Assert.AreEqual(0, olist.Add(0));
        int nextInc = olist.Max() + 1;
        Assert.AreEqual(olist.Count, olist.Add(nextInc));
        CollectionAssert.AreEqual(startList.Concat(new int[] { 0, nextInc }).OrderBy(l => l).ToList(), olist);
        Assert.IsTrue(olist.Remove(0));
        Assert.IsFalse(olist.Remove(0));
        Assert.IsTrue(olist.Remove(nextInc));
        CollectionAssert.AreEqual(startList, olist);

        var addList = new List<int>(new int[] { 5, -1, 2, 2, -1, 3, 2 });
        olist.AddRange(addList);
        addList = startList.Concat(addList).OrderBy(l => l).ToList();
        CollectionAssert.AreEqual(addList, olist);
        olist.Remove(-1);
        addList.Remove(-1);
        CollectionAssert.AreEqual(addList, olist);
        olist.Remove(2);
        addList.Remove(2);
        CollectionAssert.AreEqual(addList, olist);

        olist = new OrderedList<int>();
        int[] seed = new int[] { -2, -2 };
        olist.AddRange(seed);
        CollectionAssert.AreEqual(seed, olist);
        olist.AddRange(new int[] { });
        olist.AddRange(new int[] { -2 });
        CollectionAssert.AreEqual(seed.Concat(new int[] { -2 }).ToList(), olist);
        olist.AddRange(new int[] { -3 });
        CollectionAssert.AreEqual((new int[] { -3, -2 }).Concat(seed).ToList(), olist);
    }

    [TestMethod]
    public void TestIndexOf()
    {
        var test = new OrderedList<int>(new[] { 0, -1, -2 });
        Assert.AreEqual(0, test.IndexOf(-2));
        Assert.AreEqual(2, test.IndexOf(0));
        test.Add(-2);
        Assert.AreEqual(0, test.IndexOf(-2));
        Assert.AreEqual(1, test.LastIndexOf(-2));
        test.Add(0);
        Assert.AreEqual(3, test.IndexOf(0));
        Assert.AreEqual(4, test.LastIndexOf(0));
    }

    [TestMethod]
    public void TestRangeFinding()
    {
        var test = new OrderedList<int> { 2 };
        CollectionAssert.AreEqual(new[] { 2 }, test.WithinRange(0, 6));
        CollectionAssert.AreEqual(new[] { 2 }, test.WithinRange(0, 2));
        CollectionAssert.AreEqual(new[] { 2 }, test.WithinRange(2, 4));
        CollectionAssert.AreEqual(new int[0], test.WithinRange(-6, 0));
        CollectionAssert.AreEqual(new int[0], test.WithinRange(6, 8));

        test = new OrderedList<int>();
        CollectionAssert.AreEqual(new int[0], test.WithinRange(6, 8));

        test = new OrderedList<int>{ -4, -2, 0 ,4, 6, 6 };
        CollectionAssert.AreEqual(new[] { 0, 4 }, test.WithinRange(0, 4));
        CollectionAssert.AreEqual(new[] { 0, 4 }, test.WithinRange(-1, 5));
        CollectionAssert.AreEqual(new[] { 6, 6 }, test.WithinRange(6, 8));
        CollectionAssert.AreEqual(new[] { 6, 6 }, test.WithinRange(5, 8));
        CollectionAssert.AreEqual(new[] { -4, -2 }, test.WithinRange(-5, -1));
        CollectionAssert.AreEqual(new[] { -4, }, test.WithinRange(-4, -3));
        CollectionAssert.AreEqual(new int[0], test.WithinRange(-6, -5));

        Assert.ThrowsException<ArgumentException>(() => test.WithinRange(6, 4));

    }
}

答案 1 :(得分:8)

你找到了吗? Generic SortedListSortedList

所以我错过了重复部分,这让我更加努力。但这是我如何解决它:

var sortedList = new SortedList<LayerColor, SortedList<Layer, Layer>>();
var redSortedList = new SortedList<Layer, Layer>();
// Add all layers associated with the color red
sortedList.Add(LayerColor.Red, redSortedList);

这对你有用吗?此外,我更喜欢使用linq,但如果你真的想要一个排序列表,我的解决方案很可能会有效。

上次尝试:):

public class YourClass
{
    private List<Layer> _layers;
    public List<Layer> Layers
    {
        get
        {
            _layers = _layers.OrderBy(y => y.LayerColor).ThenBy(y => y.Name).ToList();
            return _layers;
        }
        set
        {
            _layers = value;
        }
    }
}

请注意,我是直接在浏览器中编写而不是在VS中测试它(坐在OS X上),但你可能会明白这一点。

答案 2 :(得分:1)

你走在正确的轨道上。我将创建一个继承自Collection的自定义集合类。在此自定义集合中,您可以覆盖插入/删除方法,并在添加/删除项目时对集合进行排序。

答案 3 :(得分:1)

您可以使用常规List<T>,但在显示列表之前调用Sort()方法,并在添加新值之后调用{{1}}方法。这应该为您提供所需的功能。该应用程序的性能足够好。

当然,您必须定义自己的比较才能使用,但这不应该太麻烦。

如果您没有任何钩子进入可用于对列表进行排序的add事件,那么您可以将列表包装在@Justin推荐的自定义集合类中。

答案 4 :(得分:1)

使用System.Linq,执行:

from layer in layers
        orderby layer.Color, layer.Name
        select layer

答案 5 :(得分:1)

如果排序仅用于显示目的,请让WPF处理它:

ICollectionView view = CollectionViewSource.GetDefaultView(Layers);
view.SortDescriptions.Add(new SortDescription("Color", ListSortDirection.Ascending);
view.SortDescriptions.Add(new SortDescription("Name", ListSortDirection.Ascending);

然后将Layers绑定到您的用户界面ItemsControl

答案 6 :(得分:-2)

首先在Layer中实现IComparable接口并声明CompareTo方法。然后使用SortedList集合来存储您的对象。

 public class Layer : IComparable {

    public int CompareTo(object obj) {
          //return -1 if this is before obj, 0 is same, 1 is after.
    }

 }

答案 7 :(得分:-2)

你可以使用arraylist并在linq查询下面进行排序

ArrayList myList = new ArrayList();
            Layer obj1 = new Layer();
            obj1.Color = LayerColor.Red;
            obj1.Name = "Layer1";
            myList.Add(obj1);

            Layer obj2 = new Layer();
            obj2.Color = LayerColor.Green;
            obj2.Name = "Layer2";
            myList.Add(obj2);

            Layer obj3 = new Layer();
            obj3.Color = LayerColor.Blue;
            obj3.Name = "Layer3";
            myList.Add(obj3);

            Layer obj4 = new Layer();
            obj4.Color = LayerColor.Green;
            obj4.Name = "Layer4";
            myList.Add(obj4);


            var mySortedList = myList.OfType<Layer>().OrderBy(l => l.Color)
                         .ThenBy(l => l.Name);