为啥 .NET 中没有 SortedList<T>? [关闭]

Posted

技术标签:

【中文标题】为啥 .NET 中没有 SortedList<T>? [关闭]【英文标题】:Why is there no SortedList<T> in .NET? [closed]为什么 .NET 中没有 SortedList<T>? [关闭] 【发布时间】:2011-04-09 11:33:54 【问题描述】:

为什么只有一个看起来更像字典的SortedList&lt;TKey, TValue&gt;,而没有一个实际上只是一个总是排序的列表的SortedList&lt;T&gt;

根据the MSDN documentation on SortedList,它实际上在内部实现为一个动态大小的KeyValuePair&lt;TKey, TValue&gt;数组,始终按key排序。作为任何类型的列表T,同一个类不是更有用吗?那不是更适合这个名字吗?

【问题讨论】:

嗯,一个有趣的想法。但是,如果我有一个 SortedList 它将如何执行排序(“键”在哪里)?我必须让 Foo 实现 IComparable 吗? 必须同意你的观点——考虑到你真的不希望这个类包含键值对的名称。当然,它不是字典,因为您可以在列表中多次出现相同的键。 @RPM1984 - 是的,你可以让 Foo IComparable 或者你可以在构建列表时提供一个比较器。 呸,丑陋的投反对票。 OP 的投票记录并不鼓舞人心。 @Timwi:当您向 SO 社区提出问题时,我认为这通常意味着您在说:“有人愿意分享您的想法并帮助我解决这个问题吗?”当您问一个问题,您唯一满意的答案必须直接来自 BCL 团队时,在 SO 上发帖似乎毫无意义。为什么不直接将您的问题发送给他们? 【参考方案1】:

这是一个通过键进行排序的列表。我只是在推测,但通过提供与元素分开指定键的能力,您的元素不必具有可比性——只需要键即可。我可以想象,在一般情况下,这会节省大量为实现 IComparable 而开发的代码,因为键很可能是已经可比较的类型。

【讨论】:

这个答案没有任何意义。如果您想按某事进行排序,则需要一个比较器,无论您是否碰巧将某事称为“键”或“值”。如果键是已经可比较的类型,那么显然我也可以对仅包含键的列表进行排序,如果不是,那么显然SortedList&lt;TK,TV&gt; 也没有提供所需的功能。 我的观点是,在大多数情况下,使用SortedList&lt;T&gt;,您最终必须为一个类实现 IComparable,而 IComparable 只需重新实现(或将功能委托给)值类型的可比性。在这种情况下,使排序键显式简化了类用户的实现,但代价是一些存储开销——假设键是从类属性中提取的。您显然可以以相同的顺序保留排序键列表和关联值列表。我相信这就是 SortedList 的实现方式。效率不高。 @tvanfosson:不正确。类本身不必具有可比性。事实上,拥有 SortedList 的主要原因是程序员在构造列表时将提供自定义函数作为比较器的情况。例如,如果我想对 y 和 x 上的 2D 点进行排序,我将提供一个 IComparer 来执行此操作。这样的比较器不是点类所固有的,也不应该是。相反,这是我对这些点的自定义用法的一部分【参考方案2】:

RPM cmets 非常有效。此外,通过 Linq 扩展,您可以使用 Sort 扩展方法按 T 的任何属性进行排序。我认为这可能是其背后的主要原因。

【讨论】:

没有Sort扩展方法。如果您的意思是 List&lt;T&gt;.Sort(它既不是扩展方法也不是 LINQ 的一部分),那么在每次调用 AddInsert 之后使用它会比必要的慢得多。如果你的意思是OrderBy,那就甚至慢了。 排序能力不是问题的答案。原因:Sorted.. 类的意义在于它在您添加/删除元素时保持排序。这与在需要排序时调用 List.Sort 完全不同;如果这是唯一的解决方案,那么在某些用例中,程序员每次添加新元素时都必须调用 Sort:非常慢。一个好的 Sorted.. 类的性能要比在每次插入时调用 Sort(通过适当的内部结构)要好得多。【参考方案3】:

我认为原因可能只是List&lt;T&gt; 已经有BinarySearchInsert,这意味着实现自己的始终排序列表是微不足道的。

这并不意味着SortedList&lt;T&gt; 类不属于框架——只是它可能不是一个非常高的优先级,因为任何需要它的开发人员都可以轻松快速地编写它。

我认为 HashSet&lt;T&gt; 也是如此,它最初并不存在,因为您可以轻松地使用 Dictionary&lt;T, byte&gt;(例如)在 .NET 3.5 之前模拟一个。

我知道这就是我在这两种情况下所做的:我有一个 UniqueSet&lt;T&gt; 类和一个 AlwaysSortedList&lt;T&gt; 类,它只是包装了一个 Dictionary&lt;T, byte&gt; 和一个 List&lt;T&gt;(并使用了 BinarySearchInsert) ,分别。

【讨论】:

如果这样的SortedList&lt;T&gt; 的优先级太低,那么提供严格功能子集的SortedList&lt;TKey, TValue&gt; 的优先级肯定会更​​低。 @Timwi:我认为SortedList&lt;TKey, TValue&gt; 的说法相当准确。我的意思是,就其实施而言,是的,情况似乎如此。但是该类显然是用作字典,因此在 MSDN 的文档中 SortedList&lt;TKey, TValue&gt;SortedDictionary&lt;TKey, TValue&gt; 之间的所有比较(以及 SortedList&lt;TKey, TValue&gt; 实现 IDictionary&lt;TKey, TValue&gt; 的事实)。我不知道为什么他们会将其优先于“真实”排序列表;事实上,自己实施几乎不费吹灰之力。 我认为框架的重点是让我不必做琐碎的事情【参考方案4】:

虽然没有人能真正告诉你为什么没有SortedList&lt;T&gt;,但可以讨论为什么SortedList 需要一个键和一个值。字典将键映射到值。执行此操作的典型方法是使用二叉树、哈希表和列表(数组),尽管哈希表是最常见的,因为它们对于大多数操作来说都是 O(1)。

它在 O(1) 中不支持的主要操作是按顺序获取下一个键。如果您希望能够做到这一点,您通常使用二叉树,为您提供一个排序字典。

如果您决定将地图实现为列表,您将保持元素按键排序,以便查找是 O(lg n),从而为您提供另一个排序字典 - 以排序列表的形式。当然SortedDictionary 这个名字已经被占用了,但是SortedList 没有被占用。我可能称它为SortedListDictionarySortedDictionaryList,但我没有给它命名。

【讨论】:

【参考方案5】:

现在有:)

public class SortedList<T> : ICollection<T>

    private List<T> m_innerList;
    private Comparer<T> m_comparer;

    public SortedList() : this(Comparer<T>.Default)
    
    

    public SortedList(Comparer<T> comparer)
    
        m_innerList = new List<T>();
        m_comparer = comparer;
    

    public void Add(T item)
    
        int insertIndex = FindIndexForSortedInsert(m_innerList, m_comparer, item);
        m_innerList.Insert(insertIndex, item);
    

    public bool Contains(T item)
    
        return IndexOf(item) != -1;
    

    /// <summary>
    /// Searches for the specified object and returns the zero-based index of the first occurrence within the entire SortedList<T>
    /// </summary>
    public int IndexOf(T item)
    
        int insertIndex = FindIndexForSortedInsert(m_innerList, m_comparer, item);
        if (insertIndex == m_innerList.Count)
        
            return -1;
        
        if (m_comparer.Compare(item, m_innerList[insertIndex]) == 0)
        
            int index = insertIndex;
            while (index > 0 && m_comparer.Compare(item, m_innerList[index - 1]) == 0)
            
                index--;
            
            return index;
        
        return -1;
    

    public bool Remove(T item)
    
        int index = IndexOf(item);
        if (index >= 0)
        
            m_innerList.RemoveAt(index);
            return true;
        
        return false;
    

    public void RemoveAt(int index)
    
        m_innerList.RemoveAt(index);
    

    public void CopyTo(T[] array)
    
        m_innerList.CopyTo(array);
    

    public void CopyTo(T[] array, int arrayIndex)
    
        m_innerList.CopyTo(array, arrayIndex);
    

    public void Clear()
    
        m_innerList.Clear();
    

    public T this[int index]
    
        get
        
            return m_innerList[index];
        
    

    public IEnumerator<T> GetEnumerator()
    
        return m_innerList.GetEnumerator();
    

    IEnumerator IEnumerable.GetEnumerator()
    
        return m_innerList.GetEnumerator();
    

    public int Count
    
        get
        
            return m_innerList.Count;
        
    

    public bool IsReadOnly
    
        get
        
            return false;
        
    

    public static int FindIndexForSortedInsert(List<T> list, Comparer<T> comparer, T item)
    
        if (list.Count == 0)
        
            return 0;
        

        int lowerIndex = 0;
        int upperIndex = list.Count - 1;
        int comparisonResult;
        while (lowerIndex < upperIndex)
        
            int middleIndex = (lowerIndex + upperIndex) / 2;
            T middle = list[middleIndex];
            comparisonResult = comparer.Compare(middle, item);
            if (comparisonResult == 0)
            
                return middleIndex;
            
            else if (comparisonResult > 0) // middle > item
            
                upperIndex = middleIndex - 1;
            
            else // middle < item
            
                lowerIndex = middleIndex + 1;
            
        

        // At this point any entry following 'middle' is greater than 'item',
        // and any entry preceding 'middle' is lesser than 'item'.
        // So we either put 'item' before or after 'middle'.
        comparisonResult = comparer.Compare(list[lowerIndex], item);
        if (comparisonResult < 0) // middle < item
        
            return lowerIndex + 1;
        
        else
        
            return lowerIndex;
        
    

【讨论】:

未实现 IList 的列表。太棒了:) @MariuszJamro,实际上SortedList&lt;T&gt; 无法实现IList&lt;T&gt;,因为某些操作在排序集合的上下文中没有意义——比如索引设置器,InsertAtRemoveAt @Tal 你可以使用 BinarySearch 代替 FindIndexForSortedInsert @ironstone13 我认为实现RemoveAt(int index)没有问题,列表仍然会排序。 您可以实现IList&lt;T&gt; 并在调用InsertAt() 时抛出异常。虽然并不理想,但这就是数组实现IList&lt;T&gt; 的方式,我认为这比根本不实现接口要好。【参考方案6】:

我认为解决这个问题的方法是实现一个扩展方法,以排序方式添加到List&lt;T&gt;(只需2行代码;),然后List&lt;T&gt;可以用作排序列表(假设你避免使用List.Add(...)):

    public static void AddSorted<T>(this List<T> list, T value)
    
        int x = list.BinarySearch(value);
        list.Insert((x >= 0) ? x : ~x, value);
    

【讨论】:

以上是关于为啥 .NET 中没有 SortedList<T>? [关闭]的主要内容,如果未能解决你的问题,请参考以下文章

.Net 数据结构:ArrayList、List、HashTable、Dictionary、SortedList、SortedDictionary——速度、内存以及何时使用它们? [关闭]

为啥 .NET 中没有 Tree<T> 类?

SortedList<K, V> 键的二分查找

SortedList 和 SortedDictionary 有啥区别?

Android你可能不知道的Support 0步自动定向刷新:SortedList

LINQ 计算 SortedList<dateTime,double> 的移动平均值