【问题标题】:Is there a list that is sorted automatically in .NET?.NET 中是否有自动排序的列表?
【发布时间】:2011-08-08 23:27:22
【问题描述】:

我有一个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 不到一千)。

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

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

最好的方法是什么?

【问题讨论】:

  • 如您所见,您的问题有很多答案,但您应该问自己的问题是为什么要对它进行排序。当然我能理解为什么,但是你需要性能还是只是一种好看的方式来排序?抱歉,错过了您所说的性能不重要。
  • 是的,它仅用于 WPF UI 的显示目的。

标签: c# .net list sorting


【解决方案1】:

你搜索了吗? 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 上)进行测试,但您可能明白了。

【讨论】:

  • 我不认为 GenericSortedList 允许欺骗是吗?
  • “重复项”的要求使得这比平时更棘手。
  • 他当然可以使用它们,你只需要有点创意。更新即将到来。
  • 谢谢,但如果我这样做,那么我必须处理 WPF 的正确绑定,对吗?目前我的层次结构是扁平的,所以很容易绑定。
  • 错过了关于性能的部分,如果这不是问题,我会更新我将如何解决它。
【解决方案2】:

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

【讨论】:

  • 继承Collection还是List还是IList更好?
  • @Joan:我会实现ICollection&lt;T&gt;IList&lt;T&gt;(如果您需要索引访问,则使用后者)
  • 感谢 digEmAll。如果可以的话,我可能会这样做或使用 Julien 的 WPF 排序方法。
  • @digEmAll,忍者一天。这取决于您需要什么样的访问权限。
【解决方案3】:

您可以使用常规的List&lt;T&gt;,但在显示列表之前和添加新值之后调用Sort() 方法。这应该为您提供所需的功能。性能对于这个应用程序来说已经足够好了。

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

如果您没有任何可用于对列表进行排序的添加事件挂钩,那么您可以按照@Justin 的建议将列表包装在自定义集合类中。

【讨论】:

    【解决方案4】:

    使用 System.Linq,执行:

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

    【讨论】:

    • 我希望他要求排序列表,因为他希望在要求对象时获得最佳性能,在这种情况下,我不确定 linq 是否会有所帮助,因为您必须在战后对它们进行排序......但否则这就是我的建议:)
    • 谢谢,orderby 是如何知道如何根据 Color 枚举进行排序的?我必须在某个地方定义它吗?
    • 不需要你定义,它会使用颜色的int值;红色 = 0 和绿色 = 2
    • 谢谢,这也很有趣。
    【解决方案5】:

    如果排序只是为了显示,让 WPF 处理它:

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

    然后只需将 Layers 绑定到您的 UI ItemsControl

    【讨论】:

    • 谢谢,这也适用于 TreeViews 吗?我有一个 GridViewColumn 但不知道该放在哪里。
    • 另外你能用 xaml 给我看一下吗?我正在尝试查找它,但到目前为止运气不佳。
    【解决方案6】:

    首先在 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】:

    您可以使用 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);
    

    【讨论】:

      【解决方案8】:

      晚会有点晚了,但为了子孙后代。

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

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

      【讨论】:

      • 该课程有任何更新或补充吗?我喜欢它并想采用它,但是,有一些评论表明您还没有完成和/或打算优化它?
      • 看起来你可以用 orderedList[5] = 3; 在列表中设置值然后排序将不再有效。
      • C# 语言现在应该已经实现了。我很惊讶库中没有允许重复的排序列表或有序列表的选项。您可以使用排序字典,但在不需要键值对的情况下呢?
      猜你喜欢
      • 2010-11-29
      • 1970-01-01
      • 1970-01-01
      • 2019-12-28
      • 2011-05-14
      • 2011-08-27
      • 2019-05-18
      • 2012-03-14
      • 1970-01-01
      相关资源
      最近更新 更多