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().
Related
I am trying to use Except to compare two list but one is an anonymous type.
For Example:
var list1 = customer.Select(x => new { x.ID, x.Name }).ToList();
var list2 = existcustomer.Select(x => x.ID).ToList();
And I trying to compare two list IDs and return a list of list one name.
My code:
var checkIfCustomerIdNotExist = list1.Where(x => x.ID.ToList().Except(list2)).Select(x => x.Name).ToList();
I am wondering if there is any workaround.
I'd advise using a dictionary instead of a List
//create a dictionary of ID to name
var customerDict = customer.ToDictionary(x => x.ID, x => x.Name);
//get your list to compare to
var list2 = existcustomer.Select(x => x.ID).ToList();
//get all entries where the id is not in the second list, and then grab the names
var newNames = customerDict.Where(x => !list2.Contains(x.Key)).Select(x => x.Value).ToList();
In general it's good to think about what data structure suits your data the best. In your case list1 contains a list of tuple where ID needs to be used to find Name - using a data structure more suited to that (A dictionary) makes solving your problem much easier and cleaner
Note, the solution assumes ID is unique, if there are duplicate IDs with different names you might need a Dictionary<string, List< string >> instead of just a Dictionary<string,string> - but that would probably require more than just a single line of linq, code would be fairly similar though
I have List<Dictionary<DateTime, Points[]>> taskResult generated from tasks
var taskResult = tasks.Select(t => t.Result).ToList();
var data = new Dictionary<DateTime, Points[]>();
in my function I want to return Dictionary<DateTime, Points[]> data but I cant figure out how to do that. I tried using foreach but had no luck
Enumerable.SelectMany extension method is right tool for the job, which combines many collections into one. Dictionary is a collection of key-value pairs.
var combined = dictionaries
.SelectMany(dictionary => dictionary.Select(pair => pair))
.GroupBy(pair => pair.Key)
.ToDictionary(
group => group.Key,
group => group.SelectMany(pair => pair.Value).ToArray());
Approach above will merge points of same date if original dictionaries contain duplicated dates
Because Dictionary implements IEnumerable you can remove .Select in first call of SelectMany.
Alternative for .GroupBy is .ToLookup method, which can have multiple values per one key.
var combined = dictionaries
.SelectMany(dictionary => dictionary)
.ToLookup(pair => pair.Key, pair.Value)
.ToDictionary(
lookup => lookup.Key,
lookup => lookup.SelectMany(points => points).ToArray());
So I have A dictionary (Employees2Name) Of int => (some class) which I need to turn into a sorted list of key value pairs of int => (some property in the class)
I have this working fine which is the good news. It just seems like I'm doing an extra step is there a way to shorten this in linq with a cast.
ComboBoxValues.Employees2Name.Select(k => new {Key = k.Key, Value = k.Value.Name})
.ToDictionary(k => k.Key, v => v.Value)
.ToList<KeyValuePair<int, string>>()
.OrderBy(kp => kp.Value)
The second to dictionary seems redundant.
It seems that all you need is
ComboBoxValues.Employees2Name
.Select(k => new KeyValuePair<int, string>(k.Key, k.Value.Name))
.OrderBy(item => item.Value);
Just Select and OrderBy; try no to materialize (i.e. ToList(), ToDictionary()) especially in the middle of the Linq.
#Servy Comments reflects the best answer.
Your already have this in an
IEnumberable<KeyPairValue<int, Class>> you just need to put the name to a dictionary then order by
#Html.PopulateCombobox(ComboBoxValues.Employees2Name
.ToDictionary(k => k, v => v.Value.Name)
.OrderBy(v => v.Value)
Dictionary class already implements IEnumerable>, that is a valid input for your OrderBy() then applying a ToList>() seems totally useless.
More, I think that the ToDictionary call is a waste of memory and time, because you are constructing the dictionary (which main purpose is to keep items unique by key) from a plain collection of items and later sort them by value (rather than key), thus without taking any advantage from the Dictionary<,> class.
I would rewrite your code as
ComboBoxValues.Employees2Name.Select(k => new KeyValuePair<int, string>(k.Key, k.Value.Name))
.OrderBy(kp => kp.Value)
Regards,
Daniele.
No need to use select and orderby. You can just try this
SortedList<int, string> sortedList =
new SortedList<int, string>(ComboBoxValues.Employees2Name
.ToDictionary(i => i.Key, i => i.Value.Name));
This is probably a simple question, but the answer is eluding me.
I have a collection of strings that I'm trying to convert to a dictionary.
Each string in the collection is a comma-separated list of values that I obtained from a regex match. I would like the key for each entry in the dictionary to be the fourth element in the comma-separated list, and the corresponding value to be the second element in the comma-separated list.
When I attempt a direct call to ToDictionary, I end up in some kind of loop that appears to kick me of the BackgroundWorker thread I'm in:
var MoveFromItems = matches.Cast<Match>()
.SelectMany(m => m.Groups["args"].Captures
.Cast<Capture>().Select(c => c.Value));
var dictionary1 = MoveFromItems.ToDictionary(s => s.Split(',')[3],
s => s.Split(',')[1]);
When I create the dictionary manually, everything works fine:
var MoveFroms = new Dictionary<string, string>();
foreach(string sItem in MoveFromItems)
{
string sKey = sItem.Split(',')[3];
string sVal = sItem.Split(',')[1];
if(!MoveFroms.ContainsKey(sKey))
MoveFroms[sKey.ToUpper()] = sVal;
}
I appreciate any help you might be able to provide.
The problem is most likely that the keys have duplicates. You have three options.
Keep First Entry (This is what you're currently doing in the foreach loop)
Keys only have one entry, the first one that shows up - meaning you can have a Dictionary:
var first = MoveFromItems.Select(x => x.Split(','))
.GroupBy(x => x[3])
.ToDictionary(x => x.Key, x => x.First()[1]);
Keep All Entries, Grouped
Keys will have more than one entry (each key returns an Enumerable), and you use a Lookup instead of a Dictionary:
var lookup = MoveFromItems.Select(x => x.Split(','))
.ToLookup(x => x[3], x => x[1]);
Keep All Entries, Flattened
No such thing as a key, simply a flattened list of entries:
var flat = MoveFromItems.Select(x => x.Split(','))
.Select(x => new KeyValuePair<string,string>(x[3], x[1]));
You could also use a tuple here (Tuple.Create(x[3], x[1]);) instead.
Note: You will need to decide where/if you want the keys to be upper or lower case in these cases. I haven't done anything related to that yet. If you want to store the key as upper, just change x[3] to x[3].ToUpper() in everything above.
This splits each item and selects key out of the 4th split-value, and value out of the 2nd split-value, all into a dictionary.
var dictionary = MoveFromItems.Select(s => s.Split(','))
.ToDictionary(split => split[3],
split => split[1]);
There is no point in splitting the string twice, just to use different indices.
This would be just like saving the split results into a local variable, then using it to access index 3 and 1.
However, if indeed you don't know if keys might reoccur, I would go for the simple loop you've implemented, without a doubt.
Although you have a small bug in your loop:
MoveFroms = new Dictionary<string, string>();
foreach(string sItem in MoveFromItems)
{
string sKey = sItem.Split(',')[3];
string sVal = sItem.Split(',')[1];
// sKey might not exist as a key
if (!MoveFroms.ContainsKey(sKey))
//if (!MoveFroms.ContainsKey(sKey.ToUpper()))
{
// but sKey.ToUpper() might exist!
MoveFroms[sKey.ToUpper()] = sVal;
}
}
Should do ContainsKey(sKey.ToUpper()) in your condition as well, if you really want the key all upper cases.
This will Split each string in MoveFromItems with ',' and from them make 4th item (3rd Index) as Key and 2nd item(1st Index) as Value.
var dict = MoveFromItems.Select(x => x.Split(','))
.ToLookup(x => x[3], x => x[1]);
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