1

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.

auhmaan
  • 726
  • 1
  • 8
  • 28
  • 1
    Keys have to be unique. Values... not so much. What would be the "right" answer if the `String E` was replaced by another `String C`? – Damien_The_Unbeliever Sep 09 '16 at 12:04
  • I've updated the question. Also, both `Key` and `Value` are unique, and I can ensure that the `Value` will not be changed at any time. Even if it does change, I've a way to keep track of it. – auhmaan Sep 09 '16 at 12:27
  • 1
    Is dictionnary mandatory ? I would suggest to switch to something more suitable to your indexation problem – Pomme De Terre Sep 09 '16 at 12:49

7 Answers7

4

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.

Scott Perham
  • 2,410
  • 1
  • 10
  • 20
  • As I've mentioned on the update, the order must be kept. Unfortunately, this isn't the solution I'm seeking. – auhmaan Sep 09 '16 at 12:33
  • If you use a SortedDictionary you are in control of the IComparer implementation used to sort the elements... if you want the order to be that in which they were added then you can do that :) – Scott Perham Sep 09 '16 at 12:50
4

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)

Community
  • 1
  • 1
user1859022
  • 2,585
  • 1
  • 21
  • 33
  • I apologize for what I'm going to say, but **if I cycle through the `Dictionary`, wouldn't I encounter the same entries in the same order?** This is, if I use a `foreach` to iterate the `Dictionary`, wouldn't I encounter first the `"String A"`, then `"String B"`, etc. in the same order? – auhmaan Sep 09 '16 at 12:30
  • 1
    quote from [msdn](http://msdn.microsoft.com/en-us/library/xfhwa508.aspx): For purposes of enumeration, each item in the dictionary is treated as a KeyValuePair structure representing a value and its key. The order in which the items are returned is undefined. – user1859022 Sep 09 '16 at 12:35
2

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!

Community
  • 1
  • 1
slawekwin
  • 6,270
  • 1
  • 44
  • 57
  • Using a `foreach` to iterate every entry is what I'm planning to do, but unfortunately, the `Dictionary` has more than 1000 entries ( I'm still testing with beta data, I assume that this number will be nothing compared with the final version ). The problem is: _It locks due to the amount of data it has to iterate._ – auhmaan Sep 09 '16 at 12:37
  • @auhmaan unfortunately there is no other way to search in the `Dictionary` by value, please consider structure based on two Dictionaries like in the linked question or you will have to change the design. On the other hand, if it slows down noticeably on only 1000 records, maybe the bottleneck is somewhere else, as enumerating such number of entries should not be a problem? – slawekwin Sep 09 '16 at 12:44
  • @auhmaan I heave edited the answer to include solution to your edit. It still needs a little work, but the concept is hopefully clear. – slawekwin Sep 09 '16 at 13:27
  • I've come with _probably_ the solution to this issue. Just making some corrections and then I'll post it here. – auhmaan Sep 09 '16 at 13:33
0

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!

Olivier Albertini
  • 684
  • 2
  • 6
  • 22
  • Only now had the time to check this answer, and, in a blunt way, isn't this a `Dictionary`? I might be blind on this, but I don't see any difference between this and a `Dictionary` - aside from the name. Care to explain the difference? – auhmaan Sep 12 '16 at 09:01
0

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)

Community
  • 1
  • 1
-1

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.

Cologler
  • 724
  • 6
  • 18
  • That, actually, is a darn good solution! But I think I found a solution for this issue. It isn't the prettiest solution, but it kinda does the job. I'll post here whenever I feel that it is the solution I want and that works. – auhmaan Sep 09 '16 at 12:59
-2

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.

auhmaan
  • 726
  • 1
  • 8
  • 28
  • Unfortunately, it will not (always) work. As already mentioned in a comment below @user1859022's answer, enumeration of `Dictionary`'s entries is unspecified, which is the case here as well. Please see first sentence in [msdn `Keys`](https://msdn.microsoft.com/en-us/library/yt2fy5zk.aspx) specification's *Remarks* section – slawekwin Sep 09 '16 at 14:39
  • So far, this method has worked. I've been doing some tests to this to check when the `Dictionary` returned the `Keys` or the `Values` with other order. Until now, it kept being identical. If it is a 1 in 1,000,000,000 of failing, there isn't much too break, and I can keep up with the damages. It's bad, yes. Has a bad impact, probably not. – auhmaan Sep 09 '16 at 14:58
  • @auhmaan Dictionary's order will change after resize or delete & add item or something else, please read .Net source code or MSDN document. – Cologler Sep 11 '16 at 01:35
  • @Cologler Until the time of this comment - and after some tests -, I haven't seen the order changed in anyway. Since in both tests and where I'm going to implement this the `Dictionary` doesn't change - there aren't items being added or removed -, I think this is a rather stable solution. As I mentioned on the previous comment, **I am aware this isn't stable**. If the order changes, the user won't probably notice the difference. – auhmaan Sep 12 '16 at 08:43