Getting Items from Dictionary by key - c#

I have this structure:
static Dictionary<int, Dictionary<int, string>> tasks =
new Dictionary<int, Dictionary<int, string>>();
it looks like that
[1]([8] => "str1")
[3]([8] => "str2")
[2]([6] => "str3")
[5]([6] => "str4")
I want to get from this list all of the [8] strings, meaning str1 + str2
The method should look like the following:
static List<string> getTasksByNum(int num){
}
How do I access it?

With LINQ, you can do something like:
return tasks.Values
.Where(dict => dict.ContainsKey(8))
.Select(dict => dict[8])
.ToList();
While this is elegant, the TryGetValue pattern is normally preferable to the two lookup operations this uses (first trying ContainsKey and then using the indexer to get the value).
If that's an issue for you, you could do something like (with a suitable helper method):
return tasks.Values
.Select(dict => dict.TryGetValueToTuple(8))
.Where(tuple => tuple.Item1)
.Select(tuple => tuple.Item2)
.ToList();

Just iterate over all values of the first hierarchy level and use TryGetValue on the second level:
var result = new List<string>();
foreach(var inner in tasks.Values)
{
string tmp;
if(inner.TryGetValue(yourKey, out tmp)
result.Add(tmp);
}
This solution has a major advantage over all other solutions presented so far:
It actually uses the dictionaries of the second hierarchy level as a dictionary, i.e. the part inside the foreach loop is O(1) instead of O(n) as with all other solutions.

Check this function:
tasks.
Where(task => task.Value.ContainsKey(8)).
Select(task => task.Value[8]);

Daniel's solution is probably best, since it's easier to understand. But it's possible to use TryGetValue in a linq approach, too:
return tasks.Values
.Select(dictionary => {
string task;
var success = dictionary.TryGetValue(yourKey, out task);
return new { success, task };
})
.Where(t => t.success)
.Select(t => t.task)
.ToList();

Are you building tasks ?
And if I'm guessing right it's tasks[task_id]([cpu] => "task_name");
I would advice you also build cpu_tasks[cpu]([task_id] => "task_name);
static Dictionary<int, Dictionary<int, string>> cpu_tasks
It would require some more maintenance but would give you a faster run on this specific function.

Dictionary<int, Dictionary<int, string>> tasks = new Dictionary<int, Dictionary<int, string>>();
List<string> strings = new List<string>();
foreach(var dict in tasks.Values)
{
if(dict.ContainsKey(8))
strings.Add(dict[8]);
}

Dictionary<int, Dictionary<int, string>> tasks = new Dictionary<int, Dictionary<int, string>>();
var result = string.Empty;
//more human-readable version
var searchValue = 8;
foreach (var task in tasks)
{
if (task.Value.ContainsKey(searchValue))
result += task.Value[searchValue];
}
//one-line version
result = tasks.ToList().Aggregate(string.Empty, (a, kvp) => a += kvp.Value.ContainsKey(searchValue) ? kvp.Value[searchValue] : string.Empty);

Related

Populate new dictionary from old dictionary

I have a Dictionary<string, int> where the string is a randomized collection of characters and the int is the ASCII sum of that string.
e.g.
["aaaaaaaaab", 971],
["aaaaaaaaba", 971],
["aaaaaaabaa", 971],
["aaaaaabaaa", 971]
I would like to make a new dictionary from the original where the new key is the value from the original, and the new value is the List<string> which would contain all the strings with the key as the ASCII sum.
e.g.
[971, List<string>{ "aaaaaaaaab", "aaaaaaaaba", "aaaaaaabaa", "aaaaaabaaa"}]
How can I achieve this? I cannot wrap my head around the required steps.
Use could GroupBy and ToDictionary
The premise is :
group by the old Value
project to a new dictionary given the values of the GroupBy
which will be the grouped list of KeyValuePair from the original dictionary, that in-turn has the key selected out of it (.Select(y => y.Key)
Example
var newDict = old.GroupBy(x => x.Value)
.ToDictionary(x => x.Key, x => x.Select(y => y.Key)
.ToList());
Additional Resources
Enumerable.GroupBy Method
Groups the elements of a sequence.
Enumerable.ToDictionary Method
Creates a Dictionary<TKey,TValue> from an IEnumerable<T>.
Since values are not unique, you need to group by Value before converting to dictionary:
var inverse = original
.GroupBy(p => p.Value)
.ToDictionary(g => g.Key, g => g.Select(p => p.Key).ToList());
If you wanted to do this without Linq, you could do the following:
foreach(KeyValuePair<string, int> entry in dict) {
if(!dict2.ContainsKey(entry.Value)) {
dict2[entry.Value] = new List<string>();
}
dict2[entry.Value].Add(entry.Key);
}
Assuming you have dict defined as Dictionary<string, int> dict and dict2 defined as Dictionary<int, List<string>> dict2
Here is a complete example for anyone that wants to "wrap their head around" how to do this, without LINQ.
using System;
using System.Collections.Generic;
public class Program
{
public static void Main()
{
Dictionary<string,int> origDict = new Dictionary<string,int>{{"tttt",1},{"fttt",1},{"fftt",2}};
var vals = new int[origDict.Count];
origDict.Values.CopyTo(vals,0);
var keys = new string[origDict.Count];
origDict.Keys.CopyTo(keys,0);
Dictionary<int,List<string>> newDict = new Dictionary<int,List<string>>();
for(int i = 0; i < vals.Length; i++){
int val = vals[i];
if(newDict.ContainsKey(val)){
newDict[val].Add(keys[i]);
}else{
newDict[val] = new List<string>();
newDict[val].Add(keys[i]);
}
}
foreach(var key in newDict.Keys){
Console.WriteLine(key);
foreach(var val in newDict[key]){
Console.WriteLine(val);
}
}
}
}
Output:
1
tttt
fttt
2
fftt

Use linq expression to filter a dictionary with a list of keys

I have a dictionary containing all users with their corresponding age.
Dictionary<string,int> AllUsers;
I have a list of specific users.
List<String> Users;
I would like to filter the first dictionary AllUsers with only the users who have their name in the SpecificUsers list.
I have done something manually with loops but I would like to use linq expression but I am not very familiar with them.
Thanks in advance for your help
You could filter Users:
Users.Where(i => AllUsers.ContainsKey(i)).Select(i => new { User = i, Age = AllUsers[i] });
The major benefit of this is that you're using the indexed AllUsers to do the filtering, so your total computational complexity only depends on the amount of users in Users (Dictionary.Contains is O(1)) - the naïve approaches tend to be Users * AllUsers.
If you want a dictionary on output, it's as simple as replacing the .Select(...) above with
.ToDictionary(i => i, i => AllUsers[i])
It might work
var newdict = AllUsers.Where(x => Users.Contains(x.Key))
.ToDictionary(val => val.Key, val => val.Value);
it will create new dictionary (cause linq is for querying not updating) with all the users from dictionary that are on the Users list. You need to use ToDictionary to actualy make it dictionary.
EDIT:
As #Rawling said it would be more performant to filter on Dictionary rather than on list. Solution to achieve that is present in #Luaan answer (I won't copy it as some do)
You can use a join() method to actually join the two collections. It allows us to get what you need with a single line of linq.
var allUsers = new Dictionary<string, int>();
allUsers.Add("Bob", 10);
allUsers.Add("Tom", 20);
allUsers.Add("Ann", 30);
var users = new List<string>();
users.Add("Bob");
users.Add("Tom");
users.Add("Jack");
var result = allUsers.Join(users, o => o.Key, i => i, (o, i) => o);
foreach(var r in result)
{
Console.WriteLine(r.Key + " " + r.Value);
}
It will output the following in the console:
Bob 10
Tom 20
Only the names that appears in both collection will be available in the result collection
There are multiple ways to do this
You can use this using where keyword
var result= yourDictionary.Where(p=> yourList.Contains(p.Key))
.ToDictionary(p=> p.Key, p=> p.Value);
But if you have lot of entries its better to use HashSet
var strings = new HashSet<string>(yourList);
var result= yourDictionary.Where(p=> strings.Contains(p.Key))
.ToDictionary(p=> p.Key, p=> p.Value);
using JOIN
var query =
from kvp in yourDictionary
join s in yourList on kvp.Key equals s
select new { kvp.Key, kvp.Value };
With the help of the following useful function
public static class Extensions
{
public static KeyValuePair<TKey,TValue>? Find<TKey, TValue>(this IDictionary<TKey, TValue> source, TKey key)
{
TValue value;
return source.TryGetValue(key, out value) ? new KeyValuePair<TKey, TValue>(key, value) : (KeyValuePair<TKey, TValue>?)null;
}
}
here is IMO the optimal solution (uses single lookup per key and does not introduce closure):
var filteredUsers = Users.Select(AllUsers.Find)
.Where(item => item.HasValue)
.ToDictionary(item => item.Value.Key, item => item.Value.Value);

C# get keys and values from List<KeyValuePair<string, string>

Given a list:
private List<KeyValuePair<string, string>> KV_List = new List<KeyValuePair<string, string>>();
void initList()
{
KV_List.Add(new KeyValuePair<string, string>("qwer", "asdf"));
KV_List.Add(new KeyValuePair<string, string>("qwer", "ghjk"));
KV_List.Add(new KeyValuePair<string, string>("zxcv", "asdf"));
KV_List.Add(new KeyValuePair<string, string>("hjkl", "uiop"));
}
(NOTE: there are multiple values for the key "qwer" and multiple keys for the value "asdf".)
1) Is there a better way to return a list of all keys than just doing a foreach on the KeyValuePair List?
2) Similarly, is there a better way to return a list of all values for a given key than using a foreach?
3) And then, how about returning a list of keys for a given value?
Thanks...
// #1: get all keys (remove Distinct() if you don't want it)
List<string> allKeys = (from kvp in KV_List select kvp.Key).Distinct().ToList();
// allKeys = { "qwer", "zxcv", "hjkl" }
// #2: get values for a key
string key = "qwer";
List<string> values = (from kvp in KV_List where kvp.Key == key select kvp.Value).ToList();
// values = { "asdf", "ghjk" }
// #3: get keys for a value
string value = "asdf";
List<string> keys = (from kvp in KV_List where kvp.Value == value select kvp.Key).ToList();
// keys = { "qwer", "zxcv" }
It sounds like you would benefit from using something like:
Dictionary<string, List<string>> kvlist;
kvlist["qwer"] = new List<string>();
kvlist["qwer"].Add("value1");
kvlist["qwer"].Add("value2");
foreach(var value in kvlist["qwer"]) {
// do something
}
It would be relatively easy to create a basic mutli-value dictionary class using a Dictionary and List.
This blog post talks more about Microsoft's MultiDictionary type available via NuGet.
Well you could definitly use your LINQ. But it's not "better" (in term of performance) since looping is already fast. It is perhaps more readable (personnal preference). For all the answers below, be aware that you need to have the System.Linq namespace imported. They also return IEnumerable<T> that are lazy loaded (executed when iterated over). If you want to return a concrete list, you can call the .ToList() extension.
Is there a better way to return a list of all keys than just doing a foreach on the KeyValuePair List?
KV_List.Select(kvp => kvp.Key);
Similarly, is there a better way to return a list of all values for a given key than using a foreach?
var theKeyToLookFor = "qwer";
KV_List.Where(kvp => kvp.Key == theKeyToLookFor).Select(kvp => kvp.Value);
And then, how about returning a list of keys for a given value?
var theValueToLookFor = "asdf";
KV_List.Where(kvp => kvp.Value == theValueToLookFor)
.Select(kvp => kvp.Value)
.ToList();
For more information on LINQ, look at LINQ (Language-Integrated Query)
You can use NameValueCollection from System.Collection.Specialized namespace:
NameValueCollection KV_List = new NameValueCollection();
KV_List.Add("qwer", "asdf");
KV_List.Add("qwer", "ghjk");
KV_List.Add("zxcv", "asdf");
KV_List.Add("hjkl", "uiop");
Example of use:
string singleValue = KV_List["zxcv"]; // returns "asdf"
string[] values = KV_List.GetValues("qwer"); // returns "asdf, "ghjk"
string[] allKeys = KV_List.AllKeys;
string[] allValues = KV_List.AllKeys;
https://msdn.microsoft.com/en-us/library/system.collections.specialized.namevaluecollection%28v=vs.110%29.aspx
1:
KV_List.Select(i => i.Key).ToList()
2:
KV_List.Where(i => i.Key == filterByKey).Select(i => i.Value).ToList()
3:
KV_List.Where(i => i.Value == filterByValue).Select(i => i.Key).ToList()
I would use ILookup<K,V> in your case. It is like a dictionary but you can get the values as IEnumerable<V> with the same key.
ILookup<string, string> lookup = KV_List.ToLookup(x => x.Key, x => x.Value);
IEnumerable<string> list = lookup["qwer"];
foreach(string str in list)
{
Console.WriteLine(str);
}
or simply
Console.WriteLine(string.Join(",", lookup["qwer"]));
Are you reading this and wondering why someone made some code have an IEnumerable<KeyValuePair<A,B>> instead of a Dictionary<A,B> but don't feel like asking and just want to get done?
if (collection == null)
return null;
return collection
.Where(z => z.Key == aThing)
.Select(z => z.Value)
.FirstOrDefault();

Clever aggregating of tuples in C#

I am processing a complex query in parallel. From the called methods I get a lot of Tuple<IEnumerable<Object>, int> objects. I would like to aggregate them quickly, but probably .Aggregate (code below) is not the best option. What is the right way to do it?
public static Tuple<IEnumerable<Object>, int> Parse(Object obj)
{
var ieo = new List<Object>();
var x = 5;
return new Tuple<IEnumerable<Object>, int>(ieo, x);
}
public static void Query(List<Object> obj)
{
var result = obj
.AsParallel()
.Select(o => Parse(o))
. // do something to aggregate this quickly and get a tuple of:
// - flattened IEnumerable<Object>
// - summed up all second items
}
And my aggregate suggestion, which probably is very slow and looks terribly. But works.
.Aggregate((t1, t2) => new Tuple<IEnumerable<Object>, int>(t1.Item1.Concat(t2.Item1), t1.Item2 + t2.Item2));
you can write custom flattener.
public static Tuple<IEnumerable<T>, int> MagicFlatten<T>(
this IEnumerable<Tuple<IEnumerable<T>, int>> tupleCrap)
{
var item1 = tupleCrap.SelectMany(x => x.Item1);
var item2 = tupleCrap.Sum(x => x.Item2);
return new Tuple<...>(item1, item2);
}
and later you can use it:
.AsParallel()
.Select(o => Parse(o))
.MagicFlatten();

Merging Dictionary containing a List in C#

This is kind-of related to this question, on how to merge two dictionaries in C#. An elegant Linq solution is presented, which is cool.
However, that question relates to Dictionary<Object1, Object2>, whereas I have a dictionary where the value is a List<Object2>.
I am looking for a solution for merging a Dictionary<Object1, List<Object2>>, with the following requirements:
If Dictionary1 contains the same key as Dictionary2, then their List<Object2> lists should be combined. You would end up with a new key-value-pair with the shared key, and the combined lists from the two dictionaries.
If Dictionary1 contains a key that Dictionary2 doesn't then the List<Object2> list from Dictionary1 should become the value, and vice versa.
This may not be possible in Linq, or it may be worth writing it out longhand with for loops and the like, but it would be nice to have an elegant solution.
I would suggest creating your own extension method. It will be more efficient and easier to modify.
public static void MergeDictionaries<OBJ1, OBJ2>(this IDictionary<OBJ1, List<OBJ2>> dict1, IDictionary<OBJ1, List<OBJ2>> dict2)
{
foreach (var kvp2 in dict2)
{
// If the dictionary already contains the key then merge them
if (dict1.ContainsKey(kvp2.Key))
{
dict1[kvp2.Key].AddRange(kvp2.Value);
continue;
}
dict1.Add(kvp2);
}
}
The difficulty is dealing with the merging of key conflicts.
If we start by flattening all the input dictionaries using SelectMany, we can group together the elements by their key.
var result = dictionaries
.SelectMany(dict => dict)
.GroupBy(kvp => kvp.Key)
The result set contains groups where each group's key is a key from the original dictionaries, and the contents of the group are an IEnumerable<List<T>> of the lists with the same key. From these groups, we can merge all List<T> into a single IEnumerable<T> using a Select transformation with SelectMany.
var result = dictionaries
.SelectMany(dict => dict)
.GroupBy(kvp => kvp.Key)
.Select(grp => new { Key = grp.Key, Items = grp.SelectMany(list => list)})
We can then get a dictionary from this using a ToDictionary transformation, converting the IEnumerable<T> back to a List<T>.
var result = dictionaries
.SelectMany(dict => dict)
.GroupBy(kvp => kvp.Key)
.Select(grp => new { Key = grp.Key, Items = grp.SelectMany(list => list)})
.ToDictionary(kip => kip.Key, kip => new List<T>(kip.Items));
Updated in response to comment
You can populate dictionaries however you like. I have assumed it is a type which implements IEnumerable<IDictionary<TKey, List<T>>> for a TKey and T of your choosing.
The simplest way would be using a List<T> as follows:
List<IDictionary<TKey, List<T>>> dictionaries
= new List<IDictionary<TKey, List<T>>>();
dictionaries.Add(dictionary1); // Your variable
dictionaries.Add(dictionary2); // Your variable
// Add any other dictionaries here.
// Code as above!
You just need to change item merging part in solution to the previous problem.
For object we have this:
.ToDictionary(group => group.Key, group => group.First())
i.e. for duplicated items, just take the first one.
But we could use this:
.ToDictionary(group => group.Key, group => group.SelectMany(list => list).ToList());
to concatenate lists.
So, the final expression would be
var result = dictionaries.SelectMany(dict => dict)
.ToLookup(pair => pair.Key, pair => pair.Value)
.ToDictionary(group => group.Key,
group => group.SelectMany(list => list).ToList());
You could try a different merging expression if you need some extra list merging logic (e.g. only merge distinct items)
I'll be the first to admit that this is not all that pretty but this works for me.
var d1 = new Dictionary<string, List<string>>();
var d2 = new Dictionary<string, List<string>>();
d1["test"] = new List<string>() { "Stockholm", "Motala" };
d1["more"] = new List<string>() { "numerous", "populous", "bigger", "plentiful" };
d2["test"] = new List<string>() { "Washington", "Charlottesville" };
d2["less"] = new List<string>() { "insufficient", "small", "imperceptible" };
var intersect = (from key in d1.Keys.Intersect(d2.Keys) select new { Key = key, Value = new List<string>(d1[key].Concat(d2[key])) }).ToDictionary(d => d.Key, d => d.Value);
var merged = d1.Concat(d2).Where(d => !intersect.Keys.Contains(d.Key)).Concat(intersect).ToDictionary(d => d.Key, d => d.Value);

Categories

Resources