views:

311

answers:

7

This seems like it should be something very easy to do, but every time I approach this issue, I end up w/ solutions that feel "less than elegant"

Here is my basic question: If I am looping through a dictionary that I have ordered in a particular way, within any given point in the loop how can I "peek" or get reference to a dictionary item 'x' places ahead of the current item without changing the current enumerator? For instance:

Dim tempDictionary = New Dictionary(Of String, String)

tempDictionary.Add("TestName1", "TestValue1")
tempDictionary.Add("TestName2", "TestValue2")
tempDictionary.Add("TestName3", "TestValue3")
'... and so on ...  '

For Each Item In tempDictionary
 DoStuff(Item)

 'Here is the point in which I want to see what the value of the'
 'dictionary item that is 1, 2, 3, [x] places in front of the current'
 'item without interfering w/ the current loop.'

 'CODE'

Next

Thanks in advance!

+1  A: 

The sounds like you want a c-style for loop. Forgive the C# here (since you asked in VB)

for (int i = 0; i < myArray.Length; i++) {
   object current = myArray[i];

   if(i + 1 < myArray.Length) { // make sure there is a next item
       object nextItem = myArray[i + 1]
   }
}

As mentioned, a Dictionary isn't ordered, but you could put the Keys in an array to use the above.

ViNull
That's a good approach. I'd have to make a 2nd collection w/ the keys to an index, but that approach would be lightweight but still allow me to keep my generics.
Nathan
+4  A: 

The problem is that a Dictionary is not an ordered data structure. You can't rely on the order on which items are enumerated in the Dictionary. If you need items to be in order you should stick to a generic List, or use a third-party ordered dictionary (for example Wintellect's Power Collections has one).

Actually, there is an OrderedDictionary in System.Collections.Specialized, but it's not generic.

If you want to create your own generic ordered dictionary, this CodeProject article might interest you.

Meta-Knight
I like the Ordered Dictionary approach and had not thought of it. I am going to double-check, but I fear that the generic aspect is a requirement. Thanks.
Nathan
I do think it's a good idea to stick to generic data structures. I updated my answer with a couple more tips.
Meta-Knight
Thanks for the great information! I ended up going a different route but I have made a note to investigate those collections, particularly Wintellects. :)
Nathan
There's already a generic `SortedDictionary<K,V>` class that lives in the `System.Collections.Generic` namespace. http://msdn.microsoft.com/en-us/library/f7fta44c.aspx
LukeH
SortedDictionary/SortedList has to be sorted by key though, which doesn't seem to be what Nathan wants.
Meta-Knight
A: 

What you are asking is meaningless because the order in which the items are returned is undefined. There's no such thing as order in a dictionary.

Darin Dimitrov
Not quite "meaningless" as I have my dictionary pre-ordered, as I had mentioned above.
Nathan
You can't order the standard Dictionary class since it is based on a hashmap internally, which doesn't conserve the order of the elements
Grizzly
There are no guarantees that your items will always stay in order, though. The documentation specifies that "The order in which the items are returned is undefined."
Meta-Knight
You can't have an ordered dictionary. There's no such notion as order in a dictionary. The fact that you add items in a particular order to your dictionary **doesn't** mean at all that when you iterate you will get them in the same order. Please read the documentation (http://msdn.microsoft.com/en-us/library/xfhwa508.aspx).
Darin Dimitrov
I think this argument is a victim of an oversimplified example that I created above. Basically, I am using linq to create a projection that is ordered.
Nathan
Should have added: Sorry for the confusion as I was trying to keep the concept simple. :)
Nathan
A: 

The only ways I could think of are either creating a copy of the current enumerator and advancing that (which obviously gets quite ugly), or using the .skip() method from Linq to create an enumerable which works ahead and then combining both using some kind of combine method, which you would again have to define yourself.

Onto another point: In order to have a meaningful order of the elements you probably want to use a System.Generics.SortedDictionary instead of a standard one.

This method should work for combining two ienumerables given an adequate combiner method

    public static IEnumerable<TResult> Combine<TParam1, TParam2, TResult>(this IEnumerable<TParam1> self, IEnumerable<TParam2> other, 
                                                                              Func<TParam1, TParam2, TResult> combiner)
    {
        using(var first=self.GetEnumerator())
        using (var second = other.GetEnumerator())
        {
            while (first.MoveNext() && second.MoveNext())
                yield return combiner(first.Current, second.Current);
        }

    }

I can't gurantee this will work exactly like this since i had to strip out some code from the original snippet

Edit: After thinking about it a bit, this might be what you want: (unfortenatly in c# since my vb isn't al that great):

skippedEnumerable = myEnumerable;         
myEnumerable.Select(item => {skippedEnumerable = skippedEnumerable.Skip(1); return new KeyValuePair<T, IEnumerable<T>>(item, skippedEnumerable )});

This will give you a pair of the current item and an enumerable starting at the current position, so you can use .ElementAt() to easily offset into that enumerable. This might nest the enumerables pretty deep though, so it might (or might not due to the O(n^2) runtime) be better to use myEnumerable.Skipe(index) directly;

Grizzly
I kept trying to find an elegant linq approach and could not do so. I think the approach would work except that I have to able to define the number to skip during the initial query (rather than the loop itself), which is a deal breaker.
Nathan
A: 

I don't know what you want to do with the dictionary entry exactly but something like this should work:

Dim dict As New Dictionary(Of String, String)
For i As Integer = 0 To dict.Count - 2
  If dict.ElementAt(i).Equals(dict.ElementAt(i + 1)) Then Exit For
Next

ElementAt() is a Linq function. However, if you're iterating through the list like this I do wonder if the Dictionary is the thing to use. A Generic List may be better.

Steve Wortham
A: 

Most likely, you'd want to create an array with the keys from the dictionary in the order you want, and iterate through that using indexes.

That is, your for loop would be the vb equivalent of for (int i = 0; i < array.length; i++), and you'd look up dictionary values with dictionary[array[i]]. You could, of course, substitute i for i + 1 (with bounds checking).

pianohacker
+1  A: 

Here's a way to peek ahead. As for order, I won't repeat what others have already mentioned.

Dim peek As Integer = 2   ''// peek ahead amount
For i As Integer = 0 To tempDictionary.Count - 1
    Dim key = tempDictionary.Keys(i)
    Dim value = tempDictionary.Values(i)
    Console.WriteLine("{0} : {1}", key, value)

    Dim peekIndex As Integer = i + peek
    If peekIndex < tempDictionary.Count - 1 Then
     Dim nextKey = tempDictionary.Keys(peekIndex)
     Dim nextValue = tempDictionary.Values(peekIndex)
     Console.WriteLine("Peek: {0} : {1}", nextKey, nextValue)
    End If
Next
Ahmad Mageed