Looking for someone to help me with C#, LINQ.
I have a Dictionary<int,int> that I am ordering like so: .OrderBy(_ => _.Value).ThenBy(_ => ThisMethodReturnsAnotherIReadOnlyDict<int,int>()).ThenByDescending(_ => _.Key).
What I want is to order the first dictionary by its value and then if there are still equal values I want that tie to be broke by the ThisMethodReturnsAnotherIReadOnlyDict<int,int>(). This first key/value of this ThisMethodReturnsAnotherIReadOnlyDict to break the tie and be on top. And finally, if everything fails, then order by it's key descending.
Some data for this like:
(First Dictionary)
[1,400]
[2,550]
[3,200]
[4,200]
(Second dictionary)
[3,50]
[4,140]
[2,600]
[1,700]
For this scenario I want my ordering to return: [3,50]
Can anyone help please?
Thanks!
It looks like you're looking for something like this:
var firstDict = new Dictionary<int, int>() {
{1,400},
{2,550},
{3,200},
{4,200}
};
var secondDict = new Dictionary<int, int>() {
{3,50},
{4,140},
{2,600},
{1,700}
};
var result = (from kvp in firstDict
join tieBreaker in secondDict on kvp.Key equals tieBreaker.Key
select new { kvp.Key, V1 = kvp.Value, V2 = tieBreaker.Value })
.OrderBy(x => x.V1)
.ThenBy(x => x.V2)
.ThenByDescending(x => x.Key)
.First();
This will join the first and second dictionaries together by its keys and will, respectively, order by the value of the first dictionary, the value of the second dictionary and then descending by the key itself.
How about:
var ans = firstDict
.OrderBy(kv => kv.Value)
.ThenBy(kv => ThisMethodReturnsAnotherIReadOnlyDict<int,int>().TryGetValue(kv.Key, out var val2) ? val2 : kv.Key)
.ToList();
Unless ThisMethodReturnsAnotherIReadOnlyDict<int,int> may change, you may want to cache the return value in a variable before sorting.
Related
I am trying to create a dictionary that can hold multiple values per key, and I have created an class called Pair that consists of two strings. I have defined idDictionary to contain a string as the key, and Pair as the value, but I am unsure how to write the ToDictionary statement as this concept is new to me, and I couldn't find any examples of this.
Dictionary<string, Pair<string, string>> idDictionary = new Dictionary<string, Pair<string, string>>();
I know with a regular generic Dictionary of I would simply use something like this:
idDictionary = resultData.Rows.Select(row => row.Split(','))
.ToDictionary(id => id[0], id => id[1]);
I am not sure how I would implement something similar for the object called Pair. Maybe I'm missing something really simple, but many thanks to those with answers.
EDIT to include full code block and more thorough explanation
The original code block is here (with a generic dictionary). The reason I am changing this is due to the fact that if there is more than 1 value per key, the application errors out due to duplicate keys.
private List<ImportItem<T>> ProcessReportResult(CSVTable resultData, ICollection<ImportItem<T>> data, Func<T, string> keyFilter)
{
WriteLog("{1}{0} records found.{1}", resultData.Rows.Length, Environment.NewLine);
//key = Order Number; value = Order ID
var idDictionary = resultData.Rows.Select((row => row.Split(','))).ToDictionary(id => id[0], id => id[1]);
idDictionary.ForEach(id => WriteLog("Input Id = {0} - Matching record Id = {1}", id.Key, id.Value));
var processList = data.Where(item => idDictionary.ContainsKey(keyFilter(item.DataItem))).ToList();
processList.ForEach(item => item.id = idDictionary[keyFilter(item.DataItem)]);
return processList;
}
A genral solution to get the one to many key value store can be achieved via grouping but that would require to have value as List of items. If I try to explain it via your given sample then the query to convert the rows to per key multi value store can be created by:
idDictionary =
resultData.Rows
.GroupBy(row => row.Id, row => row.Split(','))
.ToDictionary(g => g.Key, g => g.ToList());
Update:
Specific solution to your problem. Assuming that data would have structure something like:
List<Row> rows = new List<Row>{
new Row{
values = "1,A"
},
new Row{
values = "2,C,D,E"
},
new DataRow{
values = "3,E,X,CV,B"
},
};
You can use the Group here as well to get the Key, Value(List). Note, Here I have skipped the first value which is already captured as key at index 0.
var idDictionary =
rows.GroupBy(row => row.values.Split(',')[0],
row => row.values.Split(',').Skip(1))
.ToDictionary(g => g.Key, g => g.ToList());
This will give you the result like:
/* output -
|1, (A)|
|2, (C,D,E)|
|3, (E,X,CV,B)|
*/
Though you have to change the implementation for fetching the values via List.
But this solution will prevent the Program if there are more than one values found per key.
Not sure exactly what you need maybe this simple example will help?
idDictionary = resultData.Rows
.Select((row => row.Split(',')))
.ToDictionary<string, Pair<string, string>>
(id => id[0],id => new Pair(id[1],id[1]));
This version of ToDictionary takes two functions, one that returns the key and one that returns the value for each item in the enumeration.
You'll have to decide whether you want a tuple-based approach (or pair even) if you know how many items are in each row or if you need to consider that each row may have a different number of items.
// Setup sample data
var resultData = new
{
Rows = new string[] { "1,A,B,C", "2,A,B", "3,A,B,C,D" }
};
// If same length for each row, tuple would work easily
// Dictionary<string, Tuple<string, string>>
var tuples = resultData.Rows
.Select(r => r.Split(','))
.ToDictionary(
r => r[0],
r => Tuple.Create(r[1], r[2])
);
// If length is variable, then some type of collection could be better
// Dictionary<string, List<string>>
var lists = resultData.Rows
.Select(r => r.Split(','))
.ToDictionary(
r => r[0],
r => r.Skip(1).ToList() // Skip adding id element
);
Here is the output for the 1st item to compare each:
?lists["1"]
Count = 3
[0]: "A"
[1]: "B"
[2]: "C"
?tuples["1"]
{(A, B)}
Item1: "A"
Item2: "B"
The original code block is here (with a generic dictionary). The reason I am changing this is due to the fact that if there is more than 1 value per key, the application errors out due to duplicate keys.
Seems like what you are looking for is ToLookup
"Lookup<TKey, TElement>
represents a collection of keys each mapped to one or more values."
.
var idDictionary = resultData.Rows.Select((row => row.Split(',')))
.ToLookup(id => id[0], id => id[1]);
EDIT
A short sample:
var lines = new string[] { "a,b", "a,c", "d,e" };
var dict = lines.Select(line => line.Split(','))
.ToLookup(x => x[0], x => x[1]);
result:
Key: a Value: [b,c]
Key: e Value: [e]
Sample usage:
Console.WriteLine(string.Join(",", dict["a"]));
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);
public static IEnumerable<KeyValuePair<string, string>> GetGroupKeyValuePairs(string category)
{
var list = new List<KeyValuePair<string, string>>();
using (DataConnection connection = new DataConnection())
{
List<KeyValuePair<string,string>> settings = connection.Get<Settings>()
.Where(a => a.Category == category )
.Select(pair => new KeyValuePair<string,string>(pair.TheName, pair.TheValue))
.ToList();
list = settings;
}
return list;
}
The exception is:
InvalidOperationException:
Key 'Garanti.Oda' appears more than one time
How can I collect duplicate keys?
The method that you show isn't going to have a problem with multiple pairs with the same key. I assume that afterward, you're doing something like creating a dictionary of these pairs, and that's where you have a problem. E.g.
var pairs = GetGroupKeyValuePairs("some category");
var dict = new Dictionary<string, string>();
foreach (var pair in pairs)
dict.Add(pair.Key, pair.Value); // exception when it hits a duplicate
Instead, you need to use the pairs in a way that's friendly to duplicates, e.g. ToLookup.
var pairs = GetGroupKeyValuePairs("some category");
var lookup = pairs.ToLookup(x => x.Key, x => x.Value);
Then, for example if the list had "a", "b" and "a", "c", then lookup["a"] gives you "b" and "c".
Assuming you want to find duplicates by Key only (e.g. so that you can build a dictionary), you could GroupBy the prospective key and find all instances of more than one:
var dupeSettings = connection.Get<Settings>()
.Where(a => a.Category == category)
.GroupBy(a => a.TheName)
.Where(grp => grp.Count() > 1)
.Select(dupe => dupe.Key)
.ToList();
Or, if you want duplicates of both key and value, project and group by an anonymous class:
var dupeSettings = connection.Get<Settings>()
.Where(a => a.Category == category)
.GroupBy(a => new {a.TheName, a.TheValue})
.Where(grp => grp.Count() > 1)
.Select(dupe => dupe.Key) // Key.TheName, Key.TheValue
.ToList();
I have the following dictionary:
Dictionary<int,string> dic = new Dictionary<int,string>();
dic[1] = "A";
dic[2] = "B";
I want to filter the dictionary's items and reassign the result to the same variable:
dic = dic.Where (p => p.Key == 1);
How can I return the result as a dictionary from the same type [<int,string>] ?
I tried ToDictionary, but it doesn't work.
ToDictionary is the way to go. It does work - you were just using it incorrectly, presumably. Try this:
dic = dic.Where(p => p.Key == 1)
.ToDictionary(p => p.Key, p => p.Value);
Having said that, I assume you really want a different Where filter, as your current one will only ever find one key...
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);