UberUtils - Part 4 : Collections

 ÜberUtils Series posts so far :

I figured it was about time I did another post in the UberUtils series seeing that I haven't written a post in over a month. So here is the next installment. This time it's for collections. I hear you asking "but the framework has every collection I will ever need", and this is true for 98% of the time. But every once in a while there comes a time where I find myself subtly changing the way an existing collection works, or wishing it could do something just slightly differently. As with any software development, there are many different ways to solve the same problem, so you probably find yourself getting around limitations with the built-in collections without even knowing it. (I know I have when I go back and look at code I've written).

The number one question I have found myself asking over the years with regards to the collection namespace is:

Is there a dictionary whereby I can access the values from both the key and an index?

The answer to this is NO. And if I'm wrong about this then please shoot me now for wasting so much time over the years on this code. You can overcome this limitation by using the other collections at your disposal, and with not so much code either. The thing is, I hate writing the same code over and over again (I actually refuse to do it), so why not write a generic dictionary that can access the values by index? And then just use that dictionary instead of the normal dictionary in the future.

Introducing the IndexedDictionary, an indexable dictionary. Here is a unit test to show the indexing feature :

        [TestMethod()]
public void SomeTest()
{
IndexedDictionary<string, int> col = new IndexedDictionary<string,int>();
col.Add("1", 1);
col.Add("2", 2);
col.AddAt(0, "0", 0); //add by index
col.Add("3", 3);
col.RemoveAt(2); //remove by index
string strList = string.Empty;
for (int i = 0; i < col.Count; i++)
{
strList += col[i]; //get by index
}
Assert.AreEqual(strList, "013");
}
As you can see, you can access the dictionary now by an index (aswell as your key). This is accomplished simply by inheriting from the Dictionary class and holding a List inside the class too :
    public class IndexedDictionary<TKey, TValue> : Dictionary<TKey, TValue>
{
protected List<TKey> m_col = new List<TKey>();

I have also added some extra features which can be toggled :

  • Replace Duplicate Keys - obviously you cannot have multiple entries in a dictionary with the same key, so what this does instead is replaces a value with the same key when adding to the collection
  • Throw Error On Invalid Remove - if disabled, when you try to remove an item that does not exist in the collection it just does nothing and does not throw an exception as it normally does

Immediately after this new collection came the NamedIndexedDictionary. This is a sub class of the IndexedDictionary class that takes a string as the key.

public class NamedIndexedDictionary<TValue> : IndexedDictionary<string, TValue>

The string key can also be set to be case insensitive or not, so calling col["abc"] is the same as col["ABC"].

Both have been very useful to me in the past and I hope someone else will find them useful too. Send me any other helpful collections you have created and I will add them too. 

Download here : collections.zip

 

4 Comments

  • [undeleted]
    Richard wrote :&nbsp;

    I think it's usually called a bi-directional map. Here's Google's implementation of it, in Java.

    google-collections.googlecode.com/.../BiMap.java

  • @BEM

    lol - ok u got me & thanks for pointing that class out. BUT although the NameObjectCollectionBase does exist, it's abstract and not generic. It's also set at using a string as the key, which is almost identical to my NamedIndexedDictionary, so maybe it could replace mine, but not without writing quite a bit of code first.

  • Take a look at System.Collection.ObjectModel.KeyedCollection. It is close to what you want. It is addressable by index or key of object. Yes the class is abstract but it only requires that you implement 1 method to get it working, GetKeyForItem(TItem). The draw back that I've seen is that it uses it's indexer (collection[i]) to address both the keyed and the indexed value so that means your key can't be an int.

    It uses a hybrid method of looking up keys in the collection. If the set is suffeciently small ( < dictionaryCreationThreshold ) it will iterate over the collection checking the value being queried for to GetKeyForItem() of the current item in the iteration. However if it is exceeds that threshold it stores the keys in a dictionary. The threshold is definable when you create the collection.

    John

  • I wrote one of these for the .Net 1.1 framework and used it quite a bit. I'm very happy to see one that uses Generics!

    Thanks,
    Johnny

Comments have been disabled for this content.