Reputation: 726
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?
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
Reputation: 694
I think you should use KeyedCollection<TKey,TItem>
, It's designed for that...
you can do myKeyedCollectionObject[TItem] ---> return TKEY
In the sample I use the index or the value as index.
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
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
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
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();
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
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
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
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