38

Why is there only a SortedList<TKey, TValue> which looks more like a dictionary, but no SortedList<T> that is actually just a list that is always sorted?

According to the MSDN documentation on SortedList, it is actually internally implemented as a dynamically-sized array of KeyValuePair<TKey, TValue> that is always sorted by the key. Wouldn’t the same class be more useful as a list of any type T? Wouldn’t that fit the name better, too?

JasonMArcher
  • 14,195
  • 22
  • 56
  • 52
Timwi
  • 65,159
  • 33
  • 165
  • 230
  • 1
    hmm, an interesting thought. But if i had a SortedList how would it perform sorting (wheres the 'key')? Would i have to make Foo implement IComparable? – RPM1984 Sep 08 '10 at 00:07
  • 2
    Have to agree with you - given the name you really wouldn't expect this class to contain key-value pairs. It's not a dictionary, of course, as you can have the same key present in the list multiple times. – Will A Sep 08 '10 at 00:09
  • @RPM1984 - yeah, you could either make Foo IComparable or you could supply a comparer when you construct the list. – Will A Sep 08 '10 at 00:10
  • @RPM1984, how would the sorting of a normal list be any different from the sorting of the keys in `SortedList`? – Timwi Sep 08 '10 at 00:23
  • i could be wrong, but with SortedList you supply the key (TKey) which is normally a native type (int) which is comparable by default. You're explicitly saying "this is the key to sort upon". if i declare var sortedFoo = new SortedList; how does the compiler know how i would like it sorted? That was my question (which was answered by @WillA) – RPM1984 Sep 08 '10 at 00:39
  • @Timwi - you seem to like downvoting every single answer on this question. I'm glad i havent provided an answer. Im my opinion, something should be downvoted if its not useful, not if they went to the effort to supply an answer to question which doesnt really have a "correct answer" – RPM1984 Sep 08 '10 at 00:41
  • 2
    Yuck, ugly downvote fest. The OP's voting record is not inspiring. – Hans Passant Sep 08 '10 at 00:42
  • @RPM: luckily it is quite the exception. – Hans Passant Sep 08 '10 at 00:47
  • @RPM1984: I’m sorry, but I downvoted all the answers (except for Dan Tao’s) because I found them not useful. Most of them didn’t even understand the situation (and nor did you, initially). I would argue that the question does have a correct answer, but I guess only someone from the .NET base-class library team can provide it. – Timwi Sep 08 '10 at 01:03
  • 16
    @Timwi: When you ask the SO community a question, I think the implication is generally that you're saying, "Anyone out there care to share your thoughts and help me out with this?" When you ask a question where the only answer that you would be happy with would have to come straight from the BCL team, it seems like posting on SO is kind of pointless. Why not just send your question to them? – Dan Tao Sep 08 '10 at 01:05
  • A similar question for java which has received more attention. Rationale could be same. [why-there-is-no-sortedlist-in-java](http://stackoverflow.com/questions/8725387/why-there-is-no-sortedlist-in-java) – nawfal May 22 '14 at 05:45
  • For anyone that actually wants a sorted list type, check out `BList`, [part of the AList family of data structures](http://core.loyc.net/collections/alists-part2.html). – Qwertie Feb 26 '16 at 05:29
  • There is.... it's called SortedSet – dynamichael Aug 29 '20 at 13:55

6 Answers6

12

Although nobody can really tell you why there is no SortedList<T>, it is possible to discuss why SortedList takes a key and a value. A dictionary maps keys to values. The typical ways to do this are with a binary tree, a hash table, and a list (array), though hash tables are most common because they are O(1) for most operations.

The primary operation that it doesn't support in O(1) is getting the next key in order. If you want to be able to do that, you typically use a binary tree, giving you a sorted dictionary.

If you decide to implement the map as a list, you would keep the elements sorted by key so that lookup is O(lg n), giving you another sorted dictionary -- in the form of a sorted list. Of course the name SortedDictionary was already taken, but SortedList wasn't. I might have called it SortedListDictionary or SortedDictionaryList, but I didn't get to name it.

Gabe
  • 84,912
  • 12
  • 139
  • 238
8

There is now :)

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;
        }
    }
}
Tal Aloni
  • 1,429
  • 14
  • 14
7

I think the way to go with this problem is to implement an extension method that adds to List<T> in a sorted manner (just 2 lines of code ;), and then List<T> can be used as a sorted list (assuming you avoid using 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);
    }
Alex D.
  • 81
  • 1
  • 4
2

I think the reason is probably just that List<T> already has BinarySearch and Insert, which means implementing your own always-sorted list is trivial.

Not that this means a SortedList<T> class doesn't belong in the framework -- just that it probably wasn't a very high priority since it could easily be written quickly by any developer who needed it.

I think the same was true for HashSet<T>, which didn't originally exist because you could easily use a Dictionary<T, byte> (for example) to simulate one before .NET 3.5.

I know that's what I did in both cases: I had a UniqueSet<T> class and an AlwaysSortedList<T> class, which just wrapped a Dictionary<T, byte> and a List<T> (and used BinarySearch and Insert), respectively.

Dan Tao
  • 125,917
  • 54
  • 300
  • 447
  • 4
    If such a `SortedList` was too low-priority, then certainly `SortedList`, which provides a strict subset of the functionality, would be even lower-priority. – Timwi Sep 08 '10 at 00:33
  • @Timwi: I don't think that's *quite* accurate about `SortedList`. I mean, in terms of its implementation, yeah, it appears to be the case. But that class is clearly meant to be used as a dictionary, hence all the comparisons between `SortedList` and `SortedDictionary` in MSDN's documentation (and the fact that `SortedList` implements `IDictionary`). I don't know *why* they would've prioritized this above a "real" sorted list; the fact remains that implementing one yourself takes practically no effort. – Dan Tao Sep 08 '10 at 00:50
  • 3
    I thought the whole point of a framework was so I didn't have to do trivial things – Simon_Weaver Feb 01 '17 at 01:59
1

It is a list with the sorting being done by the key. I'm just speculating but by providing the ability to specify the key separate from the element, your element doesn't have to be comparable -- only the key need be. I would imagine that in the general case this saves a fair amount of code being developed to implement IComparable since the key is likely a type that is already comparable.

tvanfosson
  • 524,688
  • 99
  • 697
  • 795
  • 3
    This answer doesn’t make any sense. If you want to sort by something you need a comparer, irrespective of whether you happen to call the something a “key” or a “value”. If the key is a type that is already comparable, then obviously I could as well sort a list of just the keys, and if it isn’t, then clearly `SortedList` doesn’t provide the desired functionality either. – Timwi Sep 08 '10 at 00:29
  • My point is that, for most cases, with `SortedList`, you'd end up having to implement IComparable for a class and that IComparable would simply re-implement (or delegate the functionality to) the comparability of a value type. That being the case, making the sort key explicit simplifies the implementation for the user of the class at the expense of some storage overhead -- assuming that the key is drawn from a class property. You can obviously keep a list of sorted keys and a list of associated values in the same order. I believe that's how SortedList is implemented. Not very efficient. – tvanfosson Sep 08 '10 at 12:31
  • 1
    @tvanfosson: Not correct. The class itself doesn't have to be comparable. In fact, the primary reason to have a SortedList is situations where programmer will provide a custom function as the comparer, when constructing the list. For example, if I want to sort 2D points on y and then x, I would supply an IComparer that does so. Such a Comparer is not inherent to the point class, nor should it be. Rather it is a part of my custom usage of those points – ToolmakerSteve Feb 27 '16 at 06:32
0

RPM comments is quite valid. Also, with the Linq extensions, you can do sort by any property of T using the Sort extension method. I think that might be the main reasoning behind it.

Matt Fenwick
  • 48,199
  • 22
  • 128
  • 192
Wagner Silveira
  • 1,576
  • 11
  • 9
  • 3
    There is no `Sort` extension method. If you meant `List.Sort` (which is neither an extension method nor part of LINQ), then using this after every call to `Add` and `Insert` would be much slower than necessary. If you meant `OrderBy`, that’s *even* slower. – Timwi Sep 08 '10 at 00:32
  • 1
    The ability to sort is not an answer to the question. REASON: The point of a `Sorted..` class is that it MAINTAINS the sort as you add/remove elements. This is quite different than calling List.Sort whenever you need it to be sorted; if that is the only solution, then in some use cases programmer would have to call Sort every time they added a new element: extremely slow. A good Sorted.. class would perform much better than calling Sort on every insertion (via appropriate internal structure). – ToolmakerSteve Feb 27 '16 at 06:38