Combine lists of key value pairs where keys match - c#

I have a list of List<KeyvaluePair<DateTime, int>>, which I want to merge into one (union), and where there are pairs with the same key in more than one list, have their values summed.
Example:
input list 1:
date1, 1
date2, 2
input list 2:
date2, 3
date3, 4
input list 3:
date3, 5
date4, 6
desired output:
date1, 1
date2, 5
date3, 9
date4, 6
From what I've been reading about LINQ trickery this sort of thing is possible to do really neatly, but so far I've not been able to get my head around it. If there's a nice way to do this with LINQ I'd love to hear it, if not I'll be grateful for any other tidy looking answer too - my C++ brain can only think of long-winded procedural ways of doing this :)
Note that I'm using a List for a reason so please don't suggest using a dictionary or another datatype. Thanks.

var sums = list1.Concat(list2).Concat(list3)
.GroupBy(pair => pair.Key, pair => pair.Value)
.ToDictionary(g => g.Key, g => g.Sum());
sums here is a Dictionary<DateTime, int>, and you can easily access data using sums[date].
To keep your current data structure, you may replace ToDictionary with:
.Select(g => new KeyValuePair<DateTime, int>(g.Key, g.Sum())).ToList();
A bit more general LINQ way is to use an ILookup - this is similar to a dictionary of lists, so you get the individual numbers, if you need them (again, you can make a quick transformation to get to the list you want):
ILookup<DateTime,int> sums = list1.Concat(list2).Concat(list3)
.ToLookup(pair=>pair.Key,pair=>pair.Value);
int number = sums[date].Sum();

ILookup<DateTime, int> lookup = source
.SelectMany(list => list)
.ToLookup(kvp => kvp.Key, kvp => kvp.Value);
Or
List<KeyValuePair<DateTime, int>> result = source
.SelectMany(list => list)
.GroupBy(kvp => kvp.Key, kvp => kvp.Value)
.Select(g => new KeyValuePair<DateTime, int>(g.Key, g.Sum()))
.ToList();

This is a great site for examples on LINQ: http://msdn.microsoft.com/en-us/vbasic/bb688085.aspx
I guess this one http://msdn.microsoft.com/en-us/vbasic/bb737926.aspx#grpbysum solves your problem

Related

c# `dictionary of dictionary` query

could someone tell me the correct way to query this:
dictionary of dictionary
Dictionary<int, Dictionary<Guid, AutoStopWatchAndLog>> _dicDictionaryThread
where what i am looking for is from any of the first level and then from any item in the second where the level is less than x
dics betlow is: Dictionary<int, Dictionary<Guid, AutoStopWatchAndLog>>
var mostlikey = dics.FirstOrDefault(x=>x.Value.Where(y=>y.Value.Level > x));
If you want to project to a new dictionary of dictionaries filtered to the desired items, you will need to project both levels of dictionaries, which would look something like:
var query = _dicDictionaryThread.Select(o => new {o.Key, Value = o.Value
.Where(y=>y.Value.Level > x)
.ToDictionary(y => y.Key, y => y.Value)})
.Where(o => o.Value.Any())
.ToDictionary(o => o.Key, o => o.Value);
If you can easily understand this and explain it to someone else, go for it, otherwise just use a traditional loop - you're not going to get any performance boost from Linq and it will likely take longer to decipher.

Merge the values of multiple dictionaries into one list

I want to merge the values of multiple dictionaries (3 to be exact) into a list. My current solution uses linq to first combine the dictionaries and then converts the values into a list.
private List<Part> AllParts()
{
return walls.Concat(floors)
.Concat(columns)
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value)
.Values
.ToList();
}
Merging the lists first seems redundant. How can I improve this?
You can simplify this code by concatenating your dictionaries and selecting values without converting to a dictionary:
return walls.Concat(floors)
.Concat(columns)
.Select(kvp => kvp.Value)
.ToList();
It looks like the shortest and most readable solution. You can avoid concatenating your collections by taking values only:
return walls.Values
.Concat(floors.Values)
.Concat(columns.Values)
.ToList();
However, I do not see any readability, maintainability or performance improvements here.
P.S. I assumed that there are no duplicates in your dictionaries. This code will contain duplicated values while ToDictionary approach will throw exceptions on key duplication.
First off, some useful links:
Combine multiple dictionaries into a single dictionary
Combine multiple dictionaries with same key in them into one dictionary with the sum of values
Merging dictionaries in C#
Basically, merging the dictionaries first is a must, but there are more efficient ways than yours to avoid duplicates, such as:
Option 1
var dictionaries = new[] { walls, floors, columns };
var result = dictionaries
.SelectMany(d => d)
.GroupBy(
kvp => kvp.Key,
(key, kvps) => new { Key = key, Value = kvps.Sum(kvp => kvp.Value) }
)
.ToDictionary(x => x.Key, x => x.Value).ToList();
return result;
Works with any number of dictionaries, not just 3.
Option 2
var result = walls.Union(floors).Union(columns)
.ToDictionary (k => k.Key, v => v.Value).ToList();
return result;
to avoid duplicates:
var result = walls.Concat(floors).Concat(columns).GroupBy(d => d.Key)
.ToDictionary (d => d.Key, d => d.First().Value).ToList();
Dictionary exposes a property called Values, which is basically a read-only collection of all the values in that Dictionary. This is the simplest way to get all the value when you don't care about the key, and it's the easiest way to combine them:
var allParts = walls.Values
.Concat(floors.Values)
.Concat(columns.Values);
This is much simpler, and possibly more performant, than various methods of merging dictionaries, using LINQ queries to convert KeyValuePairs to Parts and so on - you don't care about the Dictionariness here, only the list of values - so treat it as a list of values.
One thing, though, is that this will not strip out duplicate Parts, if any exist. You can do that by using Union() instead of Concat().

Dictionary get max key values else print all

I've checked many solutions on different sites but couldn't find what I was looking for. I'm working on a dictionary object with different Values against Keys. The structure is as follows:
Key Value
6 4
3 4
2 2
1 1
If they dictionary contains elements like this, the output should be 6 and 3, if Key (6) has the highest value, it should print only 6. However, if all the values are same against each key, it should print all the keys.
Trying to use the following but it only prints the highest Value.
var Keys_ = dicCommon.GroupBy(x => x.Value).Max(p => p.Key);
Any ideas
Instead of using Max(x=>x.Key) use .OrderByDescending(x=>x.Key) and .FirstOrDefault() that will give you the group that has the max value. You then can itterate over the group and display whatever you need.
var dicCommon = new Dictionary<int, int>();
dicCommon.Add(6, 4);
dicCommon.Add(3, 4);
dicCommon.Add(2, 2);
dicCommon.Add(1, 1);
var maxGroup = dicCommon.GroupBy(x => x.Value).OrderByDescending(x => x.Key).FirstOrDefault();
foreach (var keyValuePair in maxGroup)
{
Console.WriteLine("Key: {0}, Value {1}", keyValuePair.Key, keyValuePair.Value);
}
Run Code
First off a query can't return one and more than one result at the same time.So you need to pick one.
In this case if you want all Keys that has the highest corresponding Value, you can sort the groups based on Value then just get the first group which has the highest Value:
var Keys_ = dicCommon.GroupBy(x => x.Value)
.OrderByDescending(g => g.Key)
.First()
.Select(x => x.Key)
.ToList();
var keys = String.Join(",", dicCommon
.OrderByDescending(x=>x.Value)
.GroupBy(x => x.Value)
.First()
.Select(x=>x.Key));
You’re almost there:
dicCommon.GroupBy(x => x.Value)
.OrderByDescending(pair => pair.First().Value)
.First().Select(pair => pair.Key).ToList()
GroupBy returns an enumerable of IGrouping. So sort these descending by value, then get the first, and select the key of each containing element.
Since this requires sorting, the runtime complexity is not linear, although we can easily do that. One way would be figuring out the maximum value first and then getting all the keys where the value is equal to that:
int maxValue = dicCommon.Max(x => x.Value);
List<int> maxKeys = dicCommon.Where(x => x.Value == maxValue).Select(x => x.Key).ToList();

Linq Query Dictionary where value in List

I have a Dictionary<string, string> and another List<string>. What I am trying to achieve is a linq query to get all items out of the dictionary where any values from said dictionary are in the List<string>.
I found this post to be helpful, LINQ querying a Dictionary against a List . And was able to write the following linq expression, however my results never actually return anything.
What I have so far.
Data is the dictionary and PersonList is the list of strings.
var Persons = PersonList.Where(x => Data.ContainsKey(x))
.Select(z => new { key = z, value = Data[z] })
.ToList();
Are you looking for keys or values? If you're looking for values use
var Persons = Data.Where(kvp => PersonList.Contains(kvp.Value))
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
If instead you really want keys then your code should work but another option would be:
var Persons = Data.Where(kvp => PersonList.Contains(kvp.Key))
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
Try this one:
var Persons = Data.Where(x=>PersonList.Contains(x.Value))
.Select(x=>new { key=x.Key, value=x.Value})
.ToList();
I converted the result to a list, because I noticed that you used it in your code. If you want it to a dictionary, just take a look to the answer provided by D Stanley.
I think you don't have to convert it ToDictionary, because your source is a dictionary:
var Persons = Data.Where(kvp => personList.Contains(kvp.Key))
.Select(x => x);
I quickly tested it in LinqPad, but if this is a bad idea or I'm wrong, please leave a comment.

How can I order a Dictionary in C#?

edit: Thanks Jason, the fact that it was a dictionary isn't that important. I just wanted the runtime to have a low runtime. Is that LINQ method fast? Also, I know this is off topic but what does the n => n mean?
I have a list of numbers and I want to make another list with the numbers that appear most at the beginning and the least at the end.
So what I did was when through the list and checked if the number x was in the dictionary. If it wasn't then I made the key x and the value one. If it was then I changed the value to be the value plus one.
Now I want to order the dictionary so that I can make a list with the ones that appear the most at the beginning and the least at the end.
How can I do that in C#?
ps. runtime is very important.
So it sounds like you have a Dictionary<int, int> where the key represents some integer that you have in a list and corresponding value represents the count of the number of times that integer appeared. You are saying that you want to order the keys by counts sorted in descending order by frequency. Then you can say
// dict is Dictionary<int, int>
var ordered = dict.Keys.OrderByDescending(k => dict[k]).ToList();
Now, it sounds like you started with a List<int> which are the values that you want to count and order by count. You can do this very quickly in LINQ like so:
// list is IEnumerable<int> (e.g., List<int>)
var ordered = list.GroupBy(n => n)
.OrderByDescending(g => g.Count())
.Select(g => g.Key)
.ToList();
Or in query syntax
var ordered = (from n in list
group n by n into g
orderby g.Count() descending
select g.Key).ToList();
Now, if you need to have the intermediate dictionary you can say
var dict = list.GroupBy(n => n)
.ToDictionary(g => g.Key, g => g.Count());
var ordered = dict.Keys.OrderByDescending(k => dict[k]).ToList();
Use the GroupBy extension on IEnumerable() to group the numbers and extract the count of each. This creates the dictionary from the list and orders it in one statement.
var ordered = list.GroupBy( l => l )
.OrderByDescending( g => g.Count() )
.ToDictionary( g => g.Key, g.Count() );
You may also consider using SortedDictionary.
It sorts the items on the basis of key, while insertion. more..
List<KeyValuePair<type, type>> listEquivalent =
new List<KeyValuePair<type, type>>(dictionary);
listEquivalent.Sort((first,second) =>
{
return first.Value.CompareTo(second.Value);
});
Something like that maybe?
edit: Thanks Jason for the notice on my omission

Categories