views:

1322

answers:

6

I need to sort some objects according to their contents (in fact according to one of their properties, which is NOT the key and may be duplicated between different objects)

.net provides two classes (SortedDictionnary and SortedList), and both are some kind of Dictionaries. The only difference (AFAIK) is that SortedDictionnary uses a binary tree to maintain its state whereas SortedList does not and is accessible via an index.

I could achieve what I want using a List, and then using its Sort() method with a custom implementation of IComparer, but it wouldn't be time-efficient as I would sort the whole List each time I insert a new object, whereas a good SortedList would just insert the item at the right position.

What I need is a SortedList class with a RefreshPosition(int index) to move only the changed (or inserted) object rather than resorting the whole list each time an object inside changes.

Am I missing something obvious ?

A: 

What I need is a SortedList class with a RefreshPosition(int index) to move only the changed (or inserted) object rather than resorting the whole list each time an object inside changes.

Why would you update using an index when such updates invalidate the index? Really, I would think that updating by object reference would be more convenient. You can do this with the SortedList - just remember that your Key type is the same as the return type of the function that extracts the comparable data form the object.

class UpdateableSortedList<K,V> {
    private SortedList<K,V> list = new SortedList<K,V>();
    public delegate K ExtractKeyFunc(V v);
    private ExtractKeyFunc func;

    public UpdateableSortedList(ExtractKeyFunc f) { func = f; }

    public void Add(V v) {
        list[func(v)] = v;
    }
    public void Update(V v) {
        int i = list.IndexOfValue(v);
        if (i >= 0) {
            list.RemoveAt(i);
        }
        list[func(v)] = v;
    }
    public IEnumerable<T> Values { get { return list.Values; } }
}

Something like that I guess.

Frank Krueger
As I stated into my post, different objects V can have the same "comparable value" K.I don't think SortedList supports duplicate Keys ; or am I mistaking ?
Brann
Ugh. I shouldn't have spent so much time in the SO editor. You know the solution: implement a binary tree yourself (to meet your requirements), or reuse one of the framework classes. If the framework classes don't work, then convert the SortedList into a MultiSortedList.
Frank Krueger
Yes ; that's the way to go. Too bad this functionnality is not built in into the .net framework !
Brann
+1  A: 

Don't forget that inserting an item into a list backed by an array can be an expensive operation - inserting a bunch of items and then sorting may well be quicker unless you really need to sort after every single operation.

Alternatively, you could always wrap a list and make your add operation find the right place and insert it there.

Jon Skeet
Yes, you're definitely right, in cases of mass inserts, sorting only once after the inserts is faster !Still, I think a UpdatePosition(int index, IComparer<T> comparer) which would assume that the list is sorted using comparer and would update the indexes accordingly would be quite useful !
Brann
+2  A: 

I asked a similar question a while back, and there's some good answers there. There a good collection of collections here.

Eclipse
+1  A: 

I've solved this problem in the past by writing an extension method that does a binary search on a IList, and another that does an insert. You can look up the correct implementation in the CLR source because there's a built-in version that works only on arrays, and then just tweak it to be an extension on IList.

One of those "should be in the BCL already" things.

Daniel Earwicker
+3  A: 

I eventually decided to write it :

class RealSortedList<T> : List<T>
    {
        public IComparer<T> comparer;

        public int SortItem(int index)
        {
            T item = this[index];
            this.RemoveAt(index);
            int goodposition=FindLocation(this[index], 0, this.Count);
            this.Insert(goodposition, item);
            return goodposition;
        }

        public int FindLocation(T item, int begin, int end)
        {
            if (begin==end)
                return begin;
            int middle = begin + end / 2;
            int comparisonvalue = comparer.Compare(item, this[middle]);
            if (comparisonvalue < 0)
                return FindLocation(item,begin, middle);
            else if (comparisonvalue > 0)
                return FindLocation(item,middle, end);
            else
                return middle;
        }
    }
Brann
Go on, make it an extension on IList<T>, you know you want to! :)
Daniel Earwicker
+1  A: 

Maybe I'm retarded, but isn't this the easiest implementation ever?

class SortedList<T> : List<T>
{
    public new void Add(T item)
    {
        Insert(~BinarySearch(item), item);
    }
}

http://msdn.microsoft.com/en-us/library/w4e7fxsh.aspx


Unfortunately, Add wasn't overrideable so I had to new it which isn't so nice when you have List<T> list = new SortedList<T>; which I actually needed to do.... so I went ahead and rebuilt the whole thing...

class SortedList<T> : IList<T>
{
    private List<T> list = new List<T>();

    public int IndexOf(T item)
    {
        var index = list.BinarySearch(item);
        return index < 0 ? -1 : index;
    }

    public void Insert(int index, T item)
    {
        throw new NotImplementedException("Cannot insert at index; must preserve order.");
    }

    public void RemoveAt(int index)
    {
        list.RemoveAt(index);
    }

    public T this[int index]
    {
        get
        {
            return list[index];
        }
        set
        {
            list.RemoveAt(index);
            list.Add(value);
        }
    }

    public void Add(T item)
    {
        list.Insert(~list.BinarySearch(item), item);
    }

    public void Clear()
    {
        list.Clear();
    }

    public bool Contains(T item)
    {
        return list.BinarySearch(item) >= 0;
    }

    public void CopyTo(T[] array, int arrayIndex)
    {
        list.CopyTo(array, arrayIndex);
    }

    public int Count
    {
        get { return list.Count; }
    }

    public bool IsReadOnly
    {
        get { return false; }
    }

    public bool Remove(T item)
    {
        var index = list.BinarySearch(item);
        if (index < 0) return false;
        list.RemoveAt(index);
        return true;
    }

    public IEnumerator<T> GetEnumerator()
    {
        return list.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return list.GetEnumerator();
    }
}

Or perhaps something like this is a more appropriate Remove function...

    public bool Remove(T item)
    {
        var index = list.BinarySearch(item);
        if (index < 0) return false;
        while (((IComparable)item).CompareTo((IComparable)list[index]) == 0)
        {
            if (item == list[index])
            {
                list.RemoveAt(index);
                return true;
            }
            index++;
        }
        return false;
    }

Assuming items can compare equal but not be equal...

Mark