views:

5422

answers:

15

Lets say I have a Dictionary object:

Dictionary myDictionary<int, SomeObject> = new Dictionary<string, SomeObject>();

Now I want to iterate through the dictionary in reverse order. I can't use a simple for loop because I don't know the keys of the dictionary. A foreach is easy:

foreach (SomeObject object in myDictionary.Values)
{
    // Do stuff to object
}

But how can I perform this in reverse?

+21  A: 

A dictionary or any other form of hashtable has no ordering. So what you are trying to do is pointless :)

leppie
Oh yeah. Duh. I'm completely stupid. Thanks :-)
Pandincus
Use SortedList if you want hashtable type lookups as well as arraylist style sequence ordering. Someone has already posted a code snippet.
Gishu
A: 

That would be a Dictionary myDictionary, and you would do it by foreach(SomeObject _object in myDictionary.Values.Reverse()) { }

A: 

If the ordering is most important, you could you a Stack and create a simple struct to store your int, Object pair.

Timothy Carter
+7  A: 

If you have .Net 3.5 you can use the .Reverse() extension method on IEnumerables. EG:

foeach (SomeObject o in myDictionary.Values.Reverse())
{
     // Do stuff to object
}
C. Lawrence Wenham
+1  A: 

The only way I can come up with in .net 2.0 is to first copy all the values to an List, then reverse the list and then run the foreach on that list.


    Dictionary d;
    List tmplist;
    foreach (object o in d.Values) tmplist.Add(s);
    tmplist.Reverse();
    foreach (object o in tmplist) {
    //dostuff
    }
Stormenet
+3  A: 

I agree with @leppie, but think you deserve an answer to the question in general. It could be that you meant for the question to be in general, but accidentally picked a bad data structure. The order of the values in a dictionary should be considered implementation-specific; according to the documentation it is always the same order as the keys, but this order is unspecified as well.

Anyway, there's not a straightforward way to make foreach work in reverse. It's syntactic sugar to using the class's enumerator, and enumerators can only travel in one direction. Technically the answer could be "reverse the collection, then enumerate", but I think this is a case where you'll just have to use a "backwards" for loop:

for (int i = myCollection.Length - 1; i >= 0; i--)
{
    // do something
}
OwenP
That won't work since the collection that "Values" property of Dictionary class returns, does not have an indexer.
Serhat Özgel
A: 

If you want a dictionary type collection but you need to maintain the insertion order you can look into the KeyedCollection here

It is a merger between a dictionary and a list. That way you can access elements in the collection via the key or the insertion index.

The only gotcha is if your element being stored in the collection has to have an int key. If you could change that to a string or another type (Guid Mabye). Since collection[1] will be searching for the key of 1 rather than the index of 1.

Gord
A: 

A standard for loop would be best, you don't have to worry about the processing overhead of reversing the collection.

Slace
+10  A: 

I'd use a SortedList instead of a dictionary. You can still access it by Key, but you can access it by index as well.

SortedList sCol = new SortedList();

sCol.Add("bee", "Some extended string matching bee");
sCol.Add("ay", "value matching ay");
sCol.Add("cee", "Just a standard cee");

// Go through it backwards.
for (int i = sCol.Count - 1; i >=0 ; i--)
    Console.WriteLine("sCol[" + i.ToString() + "] = " + sCol.GetByIndex(i));

// Reference By Key
foreach (string i in sCol.Keys)
    Console.WriteLine("sCol[" + i + "] = " + sCol[i]);

// Enumerate all values
foreach (string i in sCol.Values)
    Console.WriteLine(i);

It's worth noting that a sorted list stores key/value pairs sorted by key only.

Jonathan
+3  A: 

Actually, in C# 2.0 you can create your own iterator that traverses a container in reverse. Then, you can use that iterator in your foreach statement. But your iterator would have to have a way of navigating the container in the first place. If it's a simple array, it could go backwards like this:

static IEnumerable<T> CreateReverseIterator<T>(IList<T> list)
{
    int count = list.Count;
    for (int i = count - 1; i >= 0; --i)
    {
        yield return list[i];
    }
}

But of course you can't do that with a Dictionary as it doesn't implement IList or provides an indexer. Saying that a Dictionary does not have order is not true: of course it has order. That order can even be useful if you know what it is.

For a solution to your problem: I'd say copy the elements to an array, and use the above method to traverse it in reverse. Like this:

static void Main(string[] args)
{
    Dictionary<int, string> dict = new Dictionary<int, string>();

    dict[1] = "value1";
    dict[2] = "value2";
    dict[3] = "value3";

    foreach (KeyValuePair<int, string> item in dict)
    {
        Console.WriteLine("Key : {0}, Value: {1}", new object[] { item.Key, item.Value });
    }

    string[] values = new string[dict.Values.Count];
    dict.Values.CopyTo(values, 0);

    foreach (string value in CreateReverseIterator(values))
    {
        Console.WriteLine("Value: {0}", value);
    }

}

Copying your values to an array may seem like a bad idea, but depending on the type of value it's not really that bad. You might just be copying references!

Dave

Dave Van den Eynde
A: 

You can use the Linq to objects Enumerable.Reverse() function in .net 2.0 using LinqBridge.

Nathan Baulch
A: 

Literal Answer:

Dictionary<int, SomeObject>  myDictionary = new Dictionary<int, SomeObject>();

foreach (var pair in myDictionary.OrderByDescending(i => i.Key))
{
  //Observe pair.Key
  //Do stuff to pair.Value
}
David B
+3  A: 

If you don't have .NET 3.5 and therefore the Reverse extension method you can implement your own. I'd guess it probably generates an intermediate list (when necessary) and iterates it in reverse, something like the following:

public static IEnumerable<T> Reverse<T>(IEnumerable<T> items)
{
    IList<T> list = items as IList<T>;
    if (list == null) list = new List<T>(items);
    for (int i = list.Count - 1; i >= 0; i-- )
    {
     yield return list[i];
    }
}
Joe
A: 

'System.Collections.Generic.Dictionary' does not contain a definition for 'OrderByDescending', when trying David B's solution.

Dave Van den Eynde's solution might work if it weren't for the fact my dictionary has DataRows, which I'd have to convert to string and back to use it. Not even sure that's possible (converting the string back to a DataRow....).

I'm trying to make it so that a DataTable that has values in a specific column, if I've seen that value before, it will not store that row in my dictionary. But I have to check the DataTable in reverse order because the latest events are at the bottom, which I'm already doing a foreach on. During that foreach of that DataTable, I check the row against my dictionary.

If someone can post the syntax for how you would check a DataTable, row by row, in reverse, on a specific column, from the bottom to top, that would be greatly appreciated.

Thanks, Tom

A: 

Never mind - I figured it out myself -

    public static IEnumerable<DataRow> AllowFirstEventOnly(DataTable t, int maxcount)
    {
        // Dictionary will contain DataRow objects, keyed by message ID
        Dictionary<string, DataRow> toDo = new Dictionary<string, DataRow>();
        string messageId;

        // Loop through rows           
        for (int i = maxcount - 1; i >= 0; i--)
        {
            bool exists = false;
            // Find the messageId for current row. 
            messageId = t.Rows[i]["messageId"].ToString().Trim();

            // If we've seen this message ID before, we already processed it
            if (toDo.ContainsKey(messageId))
            {
                exists = true;
            }
            // Add only the rows we have not yet seen
            if (exists == false)
            {
                toDo.Add(messageId, t.Rows[i]);
            }
        }

        // Now we "yield" the results
        foreach (DataRow r in toDo.Values) yield return r;
    }

And the code that calls that function --

            foreach (DataRow r in AllowFirstEventOnly(eventTable, maxcount))
            {
                // Establish what this is and the action to do
                messageId = r["messageId"].ToString().Trim();
                // etc., etc.
            }