I have a legacy code which converts lists to dictionary by doing some manipulation as shown below
var items = await processTask;
var itemDict = items.ToDictionary(dto => dto.ClientId, dto => mapper.ConvertTo(dto, hj));
But recently we started to see this issue which looks like we are getting duplicate keys
An item with the same key has already been added
What's the best way to fix this so that if duplicate keys comes it should not throw exception but we can log it. Can we do this in linq or it has to be done in for loop?
Unfortunately, you can't eliminate dups while in ToDictionary. You have to do something before ToDictionary to eliminate it, like call Distinct or similar. But may be better to have an explicit loop, where you get opportunity to do something with a dupe
var dict = new Dictionary<int, string>(); // whatever mapper converts to
foreach(var dto in items)
{
if (dict.ContainsKey(dto.ClientId))
{
// log duplicate here or do something
continue;
}
dict.Add(dto.ClientId, mapper.ConvertTo(dto, hj));
}
You can do it with LINQ using GroupBy:
var itemDict = items
.GroupBy(dto => dto.ClientId)
.ToDictionary(gr => gr.Key, gr => mapper.ConvertTo(gr.First(), hj))
Also logging duplicates will make this code less elegant.
Related
I'm trying to use Linq to get all DictionaryEntry objects from an IEnumerable<Hashtable> but for the life of me can't figure out how to do it.
I mean the most obvious thing for me would be to use .SelectMany(x => x).Cast<DictionaryEntry>() to try to flatten it, but that doesn't work!
Anyone has any ideas how this can be done using Linq alone?
EDIT
I've got the following code that works, trying to convert this to Linq:
var t = new Dictionary<string, object>();
foreach (PSObject obje in objects)
{
foreach (DictionaryEntry entry in (Hashtable) obje.BaseObject)
{
t.Add((string) entry.Key, entry.Value);
}
}
return t;
I've got this far with trying to convert this to Linq:
objects.Cast<PSObject>().Select(x => (Hashtable) x.BaseObject), which leaves me with a IEnumerable<Hashtable> but like I said, can't find a way to get the DictionaryEntry objects from there.
Would something like this work for you?
var t = objects
.Select(e => e.BaseObject)
.OfType<Hashtable>()
.SelectMany(e => e.Cast<DictionaryEntry>())
.ToDictionary(e => (string)e.Key, e => e.Value);
It:
Selects all of the BaseObject values.
Finds all of type Hashtable
Selects all of the DictionaryEntry values.
Combines them all into a dictionary.
Note that you will get an ArgumentException if there is a duplicate key in any of the Hashtables. That is an issue with your existing solution too, however. You could get around this by using .GroupBy(...) and selecting the first item, or making a decision in some other way.
You will also get an InvalidCastException when you try to convert the key to a string if the key is not a string. This is also an issue with your current solution. You could add a .Where(e => e.Key is string) below the .SelectMany(...) to filter out any entries with keys that are not strings.
I am trying to concate List<> as follows-
List<Student> finalList = new List<Student>();
var sortedDict = dictOfList.OrderBy(k => k.Key);
foreach (KeyValuePair<int, List<Student>> entry in sortedDict) {
List<Student> ListFromDict = (List<Student>)entry.Value;
finalList.Concat(ListFromDict);
}
But no concatenation happens. finalList remains empty. Any help?
A call to Concat does not modify the original list, instead it returns a new list - or to be totally accurate: it returns an IEnumerable<string> that will produce the contents of both lists concatenated, without modifying either of them.
You probably want to use AddRange which does what you want:
List<Student> ListFromDict = (List<Student>)entry.Value;
finalList.AddRange(ListFromDict);
Or even shorter (in one line of code):
finalList.AddRange((List<Student>)entry.Value);
And because entry.Value is already of type List<Student>, you can use just this:
finalList.AddRange(entry.Value);
Other answers have explained why Concat isn't helping you - but they've all kept your original loop. There's no need for that - LINQ has you covered:
List<Student> finalList = dictOfList.OrderBy(k => k.Key)
.SelectMany(pair => pair.Value)
.ToList();
To be clear, this replaces the whole of your existing code, not just the body of the loop.
Much simpler :) Whenever you find yourself using a foreach loop which does nothing but build another collection, it's worth seeing whether you can eliminate that loop using LINQ.
You may want to read up the documentation on Enumerable.Concat:
Return Value
Type: System.Collections.Generic.IEnumerable
An IEnumerable that contains the concatenated elements of the two input sequences.
So you may want to use the return value, which holds the new elements.
As an alternative, you can use List.AddRange, which Adds the elements of the specified collection to the end of the List.
As an aside, you can also achieve your goal with a simple LINQ query:
var finalList = dictOfList.OrderBy(k => k.Key)
.SelectMany(k => k.Value)
.ToList();
As specified here, Concat generates a new sequence whereas AddRange actually adds the elements to the list. You thus should rewrite it to:
List<Student> finalList = new List<Student>();
var sortedDict = dictOfList.OrderBy(k => k.Key);
foreach (KeyValuePair<int, List<Student>> entry in sortedDict) {
List<Student> ListFromDict = (List<Student>)entry.Value;
finalList.AddRange(ListFromDict);
}
Furthermore you can improve the efficiency a bit, by omitting the cast to a List<T> object since entry.Value is already a List<T> (and technically only needs to be an IEnumerable<T>):
var sortedDict = dictOfList.OrderBy(k => k.Key);
foreach (KeyValuePair<int, List<Student>> entry in sortedDict) {
finalList.AddRange(entry.Value);
}
Concat method does not modify original collection, instead it returns brand new collection with concatenation result. So, either try finalList = finalList.Concat(ListFromDict) or use AddRange method which modifies target list.
I have a list that I want to put in a dictionary, for simplicity the values being inserted will all be the same.
I can use a foreach loop.
List<string> list = new List<string>();
list.Add("Earth");
list.Add("Wind");
list.Add("Fire");
list.Add("Water");
list.Add("Water"); // Will NOT BE INSERTED using the foreach loop
var myDictionary= new Dictionary<string, int>();
foreach (string value in list)
{
if (!myDictionary.ContainsKey(value))
{
myDictionary.Add(value, 1);
}
}
The above works.
But I want to use ToDictionary do the same in the following way -
Dictionary<string, int> myDictionary2 = list.ToDictionary(i => i, i => 1);
Of course this fails because I'm adding "Water" twice.
What is the correct way of checking for duplicate entries when using ToDictionary?
You could use Distinct() to filter out duplicates:
Dictionary<string, int> myDictionary2 = list.Distinct().ToDictionary(i => i, i => 1);
The same approach would make your traditional loop much clearer too, since you don't have to check "manually" for duplicates:
foreach (string value in list.Distinct())
{
myDictionary.Add(value, 1);
}
Distinct is one option that avoids the duplicate key issue. If you need a count of duplicates, you might try something more like this GroupBy as follows:
var dict = list.GroupBy(i => i).ToDictionary(g => g.Key, g => g.Count());
If your application is not just a simple string-list/duplicate-count structure, you might get some mileage from choosing a different structure like a Lookup that you can get from calling the ToLookup extension -or possibly going with a Grouping like the GroupBy I used above.
So I a collection of dictionary items in a list:
List<Dictionary<string, string>> inputData = new List<Dictionary<string, string>>(inputs);
List<Dictionary<string, string>> itemStack = new List<Dictionary<string, string>>();
Now what I want to do is for each inputData dictionary item I want to check if itemStack has the same value (Dictionary Item) already.
I was thinking it would be like?
foreach (var item in inputData)
{
if(!itemStack.Contains(item){ itemStack.Add(item)}
else{ //Duplicate found}
}
It doesn't really check the items values inside? It just assumes that it doesn't have it...
All i want is if itemStack contains and item that is already in the stack don't include it.
I know I'm missing something obvious.
Thanks,
Dictionary is reference type, so it doesn't check the "deep" value like you expected.
You will have to write your own "Contains" method, either as totally separate method or extension of the Dictionary itself then use it instead, for example:
if(!MyContains(itemStack, item)){ itemStack.Add(item)}
True that a HashSet would be better, but if you want to do it here, try this (assuming you are filtering duplicate keys only):
foreach (var item in inputData.Keys)
{
if (itemStack.Where(x => x.Key == item.Key).Count() > 0)
// There was a duplicate
}
Or, if you only care when the data is coming out you can call:
itemStack.Distinct()
I think, your way is right. On my mind, HashSet is good, but when you add a new element, it performs the same test on the contents of the same items.
Regards.
Based on your initial problem statement, you might do something like this:
var aggregateKnownKeys = itemStack.SelectMany(d => d.Keys);
itemStack.AddRange(
inputData.Select(d=> d.Where(p => !aggregateKnownKeys.Contains(p.Key))
.ToDictionary(p => p.Key, p => p.Value)));
If you only need to combine two dictionaries then you could do this to skip keys that exist in itemStack:
var inputData = new Dictionary<string, string>();
var itemStack = new Dictionary<string, string>();
var oldStack = itemStack;
itemStack = new[] { inputData.SkipWhile(d => oldStack.Keys.Contains(d.Key)), itemStack }
.SelectMany(d => d)
.ToDictionary(d => d.Key, d => d.Value);
Okay so this isn't quite a full answer but it's what I did.
So I have a List of items and instead of doing a full compare to whats in an List(Hence the other considered) I just did a single item check:
if(!String.IsNullOrEmpty(item["itemId"]))
{
alert.DaleksApproaching(item["itemId"]);
}
So when it does see it has a value it just does another event to get rid of it.
The idea of using LINQ and the method approaches about(Contains and Distinct)I like. I have yet to try that, but I plan on doing that. For this it doesn't use LINQ :(
Thanks everyone!
I am using LINQ to query a generic dictionary and then use the result as the datasource for my ListView (WebForms).
Simplified code:
Dictionary<Guid, Record> dict = GetAllRecords();
myListView.DataSource = dict.Values.Where(rec => rec.Name == "foo");
myListView.DataBind();
I thought that would work but in fact it throws a System.InvalidOperationException:
ListView with id 'myListView' must
have a data source that either
implements ICollection or can perform
data source paging if AllowPaging is
true.
In order to get it working I have had to resort to the following:
Dictionary<Guid, Record> dict = GetAllRecords();
List<Record> searchResults = new List<Record>();
var matches = dict.Values.Where(rec => rec.Name == "foo");
foreach (Record rec in matches)
searchResults.Add(rec);
myListView.DataSource = searchResults;
myListView.DataBind();
Is there a small gotcha in the first example to make it work?
(Wasn't sure what to use as the question title for this one, feel free to edit to something more appropriate)
Try this:
var matches = dict.Values.Where(rec => rec.Name == "foo").ToList();
Be aware that that will essentially be creating a new list from the original Values collection, and so any changes to your dictionary won't automatically be reflected in your bound control.
I tend to prefer using the new Linq syntax:
myListView.DataSource = (
from rec in GetAllRecords().Values
where rec.Name == "foo"
select rec ).ToList();
myListView.DataBind();
Why are you getting a dictionary when you don't use the key? You're paying for that overhead.
You might also try:
var matches = new List<Record>(dict.Values.Where(rec => rec.Name == "foo"));
Basically generic collections are very difficult to cast directly, so you really have little choice but to create a new object.
myListView.DataSource = (List<Record>) dict.Values.Where(rec => rec.Name == "foo");
Just adding knowledge the next sentence doesn´t recover any data from de db. Just only create the query (for that it is iqueryable type). For launching this query you must to add .ToList() or .First() at the end.
dict.Values.Where(rec => rec.Name == "foo")