auhmaan
auhmaan

Reputation: 726

Get the next KeyValuePair<,> from a Dictionary<,> without index

Is is possible to obtain the position of a given KeyValuePair<,> within a Dictionary<,>, or the next object, without having an index or alike?

For instance, assume that I have the following code:

Dictionary<Object, String>
    dictionary = new Dictionary<Object, String>() {
        { new Object(), "String A" },
        { new Object(), "String B" },
        { new Object(), "String C" },
        { new Object(), "String D" },
        { new Object(), "String E" },
        { new Object(), "String F" },
    };

String
    knownValue = "String C";

From there, how should I procede to obtain the KeyValuePair<Object, String> index or the KeyValuePair<Object, String> with the "String D" value?


UPDATE

A little bit of more info

In both the given example and where I'm trying to do this, both Keys and Values are unique. I'm using the Dictionary<,> to keep track of two objects while knowing which one is associated to.

A little of more details, I'm using this Dictionary<,> to keep track of a location and a Marker on an Android app. I was requested to, after selecting a Marker and popping out a little card with basic information about that location, enable swipping that card and show the next or previous location.

This is where this issue enters. I receive a list of locations from a server, which the order must be kept. After processing that list, I associate each location with a Marker on the map.

At this moment, whenever the user clicks on a Marker I use a LINQ expression to find that Marker on the Dictionary<,> and retrieve the associated location.

Upvotes: 1

Views: 3869

Answers (7)

Olivier Albertini
Olivier Albertini

Reputation: 694

I think you should use KeyedCollection<TKey,TItem>, It's designed for that... you can do myKeyedCollectionObject[TItem] ---> return TKEY

Demo

.NET Fiddle Sample

In the sample I use the index or the value as index.

.NET Reference for souce code

UPDATE

KeyedCollection<TKey,TItem> has a List of TItems (List<TItems>) and a Dictionary<TKey,TItem> Both are kept in sync behind the scene.

The Major argument for using this is when you want to extract key from the value and since this is what you want... I think this should work well for you!

Upvotes: 0

auhmaan
auhmaan

Reputation: 726

This issue actually has a rather easy solution - although not pretty, but it does the job.


In this case I'm trying to obtain the next or previous entry from a Dictionary object. Unfortunately, it does not have a IndexOf( ... ) like List does, so I can't do something like Dictionary[index + 1].

But Dictionary objects have a way to index them. Through Dictionary.Keys or Dictionary.Values after converted to a List. It's not the indexing we're used to, but's it's close to.

Having that in mind, I can create a extension like this one:

public static KeyValuePair<K, V> After<K, V>( this Dictionary<K, V> dictionary, K key ) {
    Int32 position = dictionary.Keys.ToList().IndexOf( key );

    if( position == -1 )
        throw new ArgumentException( "No match.", "key" );

    position++;

    if( position >= dictionary.Count )
        position = 0;

    K k = dictionary.Keys.ToList()[ position ];
    V v = dictionary.Values.ToList()[ position ];

    return new KeyValuePair<K, V>( k, v );
}
public static KeyValuePair<K, V> After<K, V>( this Dictionary<K, V> dictionary, V value ) {
    Int32 position = dictionary.Values.ToList().IndexOf( value );

    if( position == -1 )
        throw new ArgumentException( "No match.", "value" );

    position++;

    if( position >= dictionary.Count )
        position = 0;

    K k = dictionary.Keys.ToList()[ position ];
    V v = dictionary.Values.ToList()[ position ];

    return new KeyValuePair<K, V>( k, v );
}
public static KeyValuePair<K, V> Before<K, V>( this Dictionary<K, V> dictionary, K key ) {
    Int32 position = dictionary.Keys.ToList().IndexOf( key );

    if( position == -1 )
        throw new ArgumentException( "No match.", "key" );

    position--;

    if( position < 0 )
        position = dictionary.Count - 1;

    K k = dictionary.Keys.ToList()[ position ];
    V v = dictionary.Values.ToList()[ position ];

    return new KeyValuePair<K, V>( k, v );
}
public static KeyValuePair<K, V> Before<K, V>( this Dictionary<K, V> dictionary, V value ) {
    Int32 position = dictionary.Values.ToList().IndexOf( value );

    if( position == -1 )
        throw new ArgumentException( "No match.", "value" );

    position--;

    if( position < 0 )
        position = dictionary.Count - 1;

    K k = dictionary.Keys.ToList()[ position ];
    V v = dictionary.Values.ToList()[ position ];

    return new KeyValuePair<K, V>( k, v );
}

This extensions are suited to what I need.

With this, I can get the entry After/Before any given Key or Value.

You can view it working here.

Upvotes: -2

slawekwin
slawekwin

Reputation: 6310

In C#, Dictionary has no such thing as index - objects are kept in no particular order. This is because of how dictionaries work, they place keys and values in so called "buckets" based on the hash of the key.

If you need elements to be sorted, you could use SortedDictionary instead.

Perhaps you just need to enumerate all elements, in which case you should do it like so:

foreach (var kvp in dictionary)
{
    if ("String D".Equals(kvp.Value))
    ; //do stuff
}

If you need to be able to search by key, as well as by value, maybe a different structure would be more suitable. For example see here

After question edit:

The edit made it interesting, this should work for you:

class SortedBiDcit<T1, T2> //this assumes T1 and T2 are different (and not int for indexer) and values are unique
{
    Dictionary<T1, Tuple<T2, int>> dict1 = new Dictionary<T1, Tuple<T2, int>>();
    Dictionary<T2, T1> dict2 = new Dictionary<T2, T1>();

    List<T1> indices = new List<T1>();

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

    public T2 this[T1 arg]
    {
        get { return dict1[arg].Item1; }
    }

    public T1 this[T2 arg]
    {
        get { return dict2[arg]; }
    }

    public Tuple<T1, T2> this[int index]
    {
        get
        {
            T1 arg1 = indices[index];
            return new Tuple<T1, T2>(arg1, dict1[arg1].Item1);
        }
    }

    public void Add(T1 arg1, T2 arg2)
    {
        dict1[arg1] = new Tuple<T2, int>(arg2, indices.Count);
        dict2[arg2] = arg1;

        indices.Add(arg1);
    }

    public void Remove(T1 arg)
    {
        var arg2 = dict1[arg];
        dict1.Remove(arg);
        dict2.Remove(arg2.Item1);
        indices.RemoveAt(arg2.Item2);
    }

    public void Remove(T2 arg)
    {
        var arg2 = dict2[arg];
        var arg1 = dict1[arg2];

        dict1.Remove(arg2);
        dict2.Remove(arg1.Item1);
        indices.RemoveAt(arg1.Item2);
    }
}

It lacks basic error checking, but you can take it from there. It should allow you to use it like so:

var test = new SortedBiDcit<object, string>();
test.Add(new object(), "test");
for (int i = 0; i < test.Count; ++i)
{
    var tuple = test[i];
    var str = test[tuple.Item1]; //retrieve T2
    var obj = test[tuple.Item2]; //retrieve T1
    Console.WriteLine(tuple.Item2); //prints "test"
}

Hope it helps!

Upvotes: 2

Cologler
Cologler

Reputation: 724

Because of SortedDictionary is sort by key, not by add(), I believe SortedDictionary is Not what you want.

Maybe OrderedDictionary is what you want, but seem like you are a WP developer, and OrderedDictionary is Not release for WP.

Your knownValue is a value (not a key), so I think the best way is:

var list = new List<string>() { .... , "String C", "String D", ... };
// if you still need dict, then:
var dictionary = list.ToDictionary(z => new object());
// whatever, find the "String D"
var knownValue = "String C";
var ret = list.SkipWhile(z => z != knownValue).Skip(1).First();

AFTER UPDATE

Why do not you just create a class?

class A
{
    Marker;
    Location;
}

when user click it, you should get the A, not a Marker.

Upvotes: -1

TheHowlingHoaschd
TheHowlingHoaschd

Reputation: 696

Well, there technically is a way to find an Index for Values in a Dictionary.

The dictionary.Values field is a Dictionary<T1,T2>.ValueCollection which inherits from IEnumerable<T2>.

As an IEnumerable you can iterate over it with foreach, or use something like this

IEnumerable<Int32> idxs = dictionary.values.SelectMany(
    (string value, Int32 idx) => 
    (value == "insertStringHere") ? (new int[]{ idx }) : {new int[]{}}
);

to select the index (or all indices if there are duplicates) of the value you're looking for.

I would however not recommend it, with the same reasoning as these other answers, since Dictionary does not guarantee an order of values. It might work fine with SortedDictionary or SortedList though.

(EDIT: or KeyedCollection apparenty, as https://stackoverflow.com/users/1892381/olivier-albertini pointed out)

Upvotes: 0

user1859022
user1859022

Reputation: 2685

the order in a dictionary is non deterministic

see: The order of elements in Dictionary

you could use an OrderedDictionary though (https://msdn.microsoft.com/en-us/library/system.collections.specialized.ordereddictionary(v=vs.110).aspx)

Upvotes: 4

Scott Perham
Scott Perham

Reputation: 2470

A Dictionary(TKey,TValue), doesn't store it's data in list format, it (very, very simply) hashes the key and stores it in buckets, therefore it doesn't have a concept of "next". Maybe you could consider using a SortedDictionary(TKey, TValue). Then you can use the iterator to move through the elements in whatever order you need to.

Upvotes: 4

Related Questions