41

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?

13
  • 1
    hmm, an interesting thought. But if i had a SortedList<Foo> how would it perform sorting (wheres the 'key')? Would i have to make Foo implement IComparable?
    – RPM1984
    Commented Sep 8, 2010 at 0: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
    Commented Sep 8, 2010 at 0:09
  • @RPM1984 - yeah, you could either make Foo IComparable or you could supply a comparer when you construct the list.
    – Will A
    Commented Sep 8, 2010 at 0:10
  • 2
    Yuck, ugly downvote fest. The OP's voting record is not inspiring. Commented Sep 8, 2010 at 0:42
  • 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
    Commented Sep 8, 2010 at 1:05

6 Answers 6

13

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.

10

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

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;
        }
    }
}
6
  • 2
    List which doesn't implement IList. Awesome :) Commented Feb 1, 2017 at 13:49
  • 9
    @MariuszJamro, actually SortedList<T> can't implement IList<T> because some operations have no meaning in the context of a sorted collection - like index setter, InsertAt and RemoveAt Commented Aug 2, 2017 at 19:57
  • 3
    @Tal you could use BinarySearch instead of FindIndexForSortedInsert
    – yacc
    Commented Aug 13, 2017 at 21:22
  • 6
    @ironstone13 I see no problem with implementing RemoveAt(int index), the list will still be sorted.
    – Anlo
    Commented Oct 9, 2017 at 10:03
  • 5
    You could implement IList<T> and throw an exception when InsertAt() is called. While not ideal, this is how arrays implement IList<T>, and I think is preferable to not implementing the interface at all. Commented Apr 5, 2018 at 20:00
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.

3
  • 4
    If such a SortedList<T> was too low-priority, then certainly SortedList<TKey, TValue>, which provides a strict subset of the functionality, would be even lower-priority.
    – Timwi
    Commented Sep 8, 2010 at 0:33
  • @Timwi: I don't think that's quite accurate about SortedList<TKey, TValue>. 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<TKey, TValue> and SortedDictionary<TKey, TValue> in MSDN's documentation (and the fact that SortedList<TKey, TValue> implements IDictionary<TKey, TValue>). 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
    Commented Sep 8, 2010 at 0:50
  • 3
    I thought the whole point of a framework was so I didn't have to do trivial things Commented Feb 1, 2017 at 1: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.

3
  • 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<TK,TV> doesn’t provide the desired functionality either.
    – Timwi
    Commented Sep 8, 2010 at 0:29
  • My point is that, for most cases, with SortedList<T>, 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
    Commented Sep 8, 2010 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<T> 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<T> 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 Commented Feb 27, 2016 at 6: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.

2
  • 3
    There is no Sort extension method. If you meant List<T>.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
    Commented Sep 8, 2010 at 0: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). Commented Feb 27, 2016 at 6:38

Not the answer you're looking for? Browse other questions tagged or ask your own question.