Monday, July 28, 2008

C# 2.0 Generic Collection Sort By Value

I’m working on a web application prototype and I’m using some dummy data in xml form because it is simple and I don’t want to care about the database for a prototype ;)

Let say this is my sample data :

<root>
    <member id="1" name="Jeff" />
    <member id="2" name="Fred" />
    <member id="3" name="Greg" />
    <member id="4" name="Andy" />
</root>

I have some drop down lists or combo boxes to fill in with this kind of data in the form, and I want them to be sorted based on the description (name) not based on the key (id).

Since the data come from xml file, it may not be sorted that way, so I need to find a way to sort the data first and then bind the values to the drop down lists.

Firstly I thought it will be very simple, only need to create a class which implements IComparer for this and use it in the available generic collection types. However, it only true at the new IComparer class, but not on the generic collection part, I found out that SortedDictionary<TKey, TValue> and SortedList<TKey, TValue> will use the key to sort, even though we can specify the IComparer in the constructor.

So based on the forum discussion, I found that we can use a generic List<T> to perform sorting and we can use KeyValuePair<TKey, TValue>as the type of T.

Here is the SortByValueComparer class which implements IComparer :

public class SortByValueComparer<TKey, TValue> : IComparer<KeyValuePair<TKey, TValue>>
{
    #region IComparer<KeyValuePair<TKey, TValue>> Members

    public int Compare(KeyValuePair<TKey, TValue> x, KeyValuePair<TKey, TValue> y)
    {
        return Comparer<TValue>.Default.Compare(x.Value, y.Value);
    }

    #endregion
}

Then we can use List<T> such as :

[Fact]
public void Test_ListSort()
{
    List<KeyValuePair<string, string>> l = new List<KeyValuePair<string, string>>();
    l.Add(new KeyValuePair<string, string>("1", "Jeff"));
    l.Add(new KeyValuePair<string, string>("2", "Fred"));
    l.Add(new KeyValuePair<string, string>("3", "Greg"));
    l.Add(new KeyValuePair<string, string>("4", "Andy"));

    Assert.Equal(l[0].Value, "Jeff");
    Assert.Equal(l[1].Value, "Fred");
    Assert.Equal(l[2].Value, "Greg");
    Assert.Equal(l[3].Value, "Andy");

    // Sort the collection based on the value
    l.Sort(new SortByValueComparer<string, string>());

    Assert.Equal(l[0].Value, "Andy");
    Assert.Equal(l[1].Value, "Fred");
    Assert.Equal(l[2].Value, "Greg");
    Assert.Equal(l[3].Value, "Jeff");
}

However, I felt that the code is a bit cluttered, so I add a new Custom List class which implements List<T> :

public class CustomList<TKey, TValue> : List<KeyValuePair<TKey, TValue>>
{
    public CustomList() : base() { }
    public CustomList(int capacity) : base(capacity) { }

    public void AddEntry(TKey key, TValue value)
    {
        this.Add(new KeyValuePair<TKey, TValue>(key, value));
    }

    public void SortByValue()
    {
        this.Sort(new SortByValueComparer<TKey, TValue>());
    }
}

So I can use it like this :

[Fact]
public void Test_CustomList()
{
    CustomList<string, string> l = new CustomList<string, string>();
    l.AddEntry("1", "Jeff");
    l.AddEntry("2", "Fred");
    l.AddEntry("3", "Greg");
    l.AddEntry("4", "Andy");

    Assert.Equal(l[0].Value, "Jeff");
    Assert.Equal(l[1].Value, "Fred");
    Assert.Equal(l[2].Value, "Greg");
    Assert.Equal(l[3].Value, "Andy");

    // Sort the collection based on the value
    l.SortByValue();

    Assert.Equal(l[0].Value, "Andy");
    Assert.Equal(l[1].Value, "Fred");
    Assert.Equal(l[2].Value, "Greg");
    Assert.Equal(l[3].Value, "Jeff");
}

Hope this helps ;)

2 comments:

Fonz said...

Great post...helped me out a lot on a particular problem!

John said...

I must learn to operate this all is C++...Oh its not so easy for me sir!
r4 revolution

Post a Comment