How to delete an item from a Dictonary using LINQ in C# - c#

I need to delete a specific item from a dictonary..
The dictonary is like
dict["Key1"]="Value1"
dict["Key2"]="Value2"
dict["Key3"]="Value3"
dict["Key4"]="Value2"
How to delete the item if another item has the same value using LINQ
Thanks in advance

Here my tested solution:
dict.GroupBy(x => x.Value, x => x.Key)
.Where(x => x.Count() > 1)
.SelectMany(x => x.Skip(1))
.ToList().ForEach(x => dict.Remove(x))

check orginal answer by #Jon Skeet : C#: Remove duplicate values from dictionary?
var uniqueValues = myDict.GroupBy(pair => pair.Value)
.Select(group => group.First())
.ToDictionary(pair => pair.Key, pair => pair.Value);

var dupKeys = dict.GroupBy(innerD => innerD.Value)
.Where(mergedByValue => mergedByValue.Count() > 1)
.Select(mergedByValue => mergedByValue.OrderByDescending(m => m.Key).First().Key);
dict.Where(d => dupKeys.Contains(d.Key)).ToList()
.ForEach(d => dict.Remove(d.Key));
This assumes you want the last duplicate removed, where last is defined as the last ordinal string value.
If you want all duplicates removed, change your dupKeys to this:
var dupKeys = dict.GroupBy(innerD => innerD.Value)
.Where(mergedByValue => mergedByValue.Count() > 1).Dump()
.SelectMany(mergedByValue => mergedByValue.Select(m => m.Key));

You need to use Distinct.

Related

Linq get Distinct ToDictionary

need help to only select/get distinct entries based on i.Code.
There are duplicates and thus I'm getting an error in my expression "An item with the same key has already been added."
var myDictionary = dbContext.myDbTable
.Where(i => i.shoesize>= 4)
.OrderBy(i => i.Code)
.ToDictionary(i => i.Code, i => i);
Have tried to use Select and/or Distinct in different combinations and also by themselves but am still getting the same error
var myDictionary= dbContext.myDbTable
.Where(i => i.shoesize>= 4)
.OrderBy(i => i.Code)
//.Select(i => i)
//.Distinct()
.ToDictionary(i => i.Code, i => i);
Can anybody help? C#
UPDATE: If there are multiple objects with the same code I only want to add the first object(with that particular code) to myDictionary.
You can group by Code and select the first item from each group (which is equivalent to distinct):
var myDictionary = dbContext.myDbTable
.Where(i => i.shoesize >= 4) // filter
.GroupBy(x => x.Code) // group by Code
.Select(g => g.First()) // select 1st item from each group
.ToDictionary(i => i.Code, i => i);
You don't need the OrderBy since Dictionarys represent an unordered collection. If you need an ordered dictionary you could use SortedDictionary.
It sounds to me that what you are looking for is .DistinctBy() (available in .NET 6), which lets you specify which property to distinct the elements in your collection by:
var myDictionary= dbContext.myDbTable
.Where(i => i.shoesize>= 4)
.DistinctBy(i => i.Code)
.ToDictionary(i => i.Code, i => i);
By dividing it and creating a list first it worked as compared to when it was all bundled up into one linq, guess the First() needed it to be in a list before being able to make it into a dict.
var firstLinq = dbContext.myDbTable
.Where(i => i.shoesize>= 4)
.ToList();
then
var finalLinq = fromConcurWithDuplicates
.GroupBy(i => i.Code)
.Select(i => i.First())
.ToList()
.ToDictionary(i => i.Code, i => i);

Foreach loops correctly - checking with .Any() returns an error

I have a list of Guid, where I keep any duplicate Guid from my list of items:
IEnumerable<Guid> duplicateExists = ListOfMyItems
.GroupBy(x => x.ID)
.Where(group => group.Count() > 1)
.Select(group => group.Key);
Now I want to check if duplicate existst, and if so, remove the last occurence of it from ListOfMyItems. DeleteItemFromOrder just deletes a row from database.
This works:
foreach (var item in duplicateExists)
{
_ = ListOfMyItems.Remove(ListOfMyItems.Last(x => x.ID == item));
MyLogic.DeleteItemFromOrder(Bill.ID, item);
return;
}
While this will enter the conditional if and then fail on DeleteItemFromOrder, telling me that duplicateExists - sequence contains no elements:
if(duplicateExists.Any())
{
_ = ListOfMyItems.Remove(ListOfMyItems.Last(x => x.ID == duplicateExists.First()));
MyLogic.DeleteItemFromOrder(Bill.ID, duplicateExists.First());
return;
}
Why is the 2nd example failing?
It is a result of Linq deferred execution: when you put
IEnumerable<Guid> duplicateExists = ListOfMyItems
.GroupBy(x => x.ID)
.Where(group => group.Count() > 1)
.Select(group => group.Key);
The system actually doesn't execute the query; then you have
if (duplicateExists.Any())
{
_ = ListOfMyItems.Remove(ListOfMyItems.Last(x => x.ID == duplicateExists.First()));
MyLogic.DeleteItemFromOrder(Bill.ID, duplicateExists.First());
return;
}
And the system does the following:
Executes duplicateExists.Any() and gets true, since duplicates exist
Executes duplicateExists.First() gets the duplicate and removes it from ListOfMyItems - ListOfMyItems.Remove
Tries to execute duplicateExists.First() once again for MyLogic.DeleteItemFromOrder and now fails since ListOfMyItems has been modified and has no duplicates - duplicateExists is empty
Quick patch: materialize the duplicateExists, force the system execute the query and store results in a collection, e.g. array:
IEnumerable<Guid> duplicateExists = ListOfMyItems
.GroupBy(x => x.ID)
.Where(group => group.Count() > 1)
.Select(group => group.Key)
.ToArray();
LINQ uses lazy evaluation. You need to resolve your initial query to a physical list before using it to modify the original data set, like this:
var duplicateExists = ListOfMyItems
.GroupBy(x => x.ID)
.Where(group => group.Count() > 1)
.Select(group => group.Key)
.ToArray();

LINQ: Select all from each group except the first item

It is easy to select the first of each group:
var firstOfEachGroup = dbContext.Measurements
.OrderByDescending(m => m.MeasurementId)
.GroupBy(m => new { m.SomeColumn })
.Where(g => g.Count() > 1)
.Select(g => g.First());
But...
Question: how can I select all from each group except the first item?
var everythingButFirstOfEachGroup = dbContext.Measurements
.OrderByDescending(m => m.MeasurementId)
.GroupBy(m => new { m.SomeColumn })
.Where(g => g.Count() > 1)
.Select( ...? );
Additional information:
My real goal is to delete all duplicates except the last (in a bulk way, ie: not using an in-memory foreach), so after the previous query I want to use RemoveRange:
dbContext.Measurements.RemoveRange(everythingButFirstOfEachGroup);
So, if my question has no sense, this information might be handy.
Use Skip(1) to skip the first record and select the rest.
Something like:
var firstOfEachGroup = dbContext.Measurements
.OrderByDescending(m => m.MeasurementId)
.GroupBy(m => new { m.SomeColumn })
.Where(g => g.Count() > 1)
.SelectMany(g => g.OrderByDescending(r => r.SomeColumn).Skip(1));
See: Enumerable.Skip
If you do not need a flattened collection then replace SelectMany with Select in code snippet.
IGrouping<K, V> implements IEnumerable<V>; you simply need to skip inside the select clause to apply it to each group:
.Select(g => g.Skip(1))
You can always use .Distinct() to remove duplicates; presumably sorting or reverse-sorting and then applying .distinct() would give you what you want.

C# LINQ get count to dictionary

I got a LINQ query with Entity Framework (EF) and getting a list of items. Now I want to create a dictionary with the incrementing index of the item and the item itself.
I have it like this:
result = context
.Items
.Where(b => !b.Deleted)
.OrderBy(b => b.Name)
.ToDictionary(COUNTHERE, b => b.Name)
So the dictionary have to look like this:
1: "item1"
2: "item2"
3: "item5"
4: "item10"
5: "item100"
I think what you need is to have the item name as the key instead the count as the key, because if there is two items that have the same count, it will throw exception that the key has been added.
Then you can Use GroupBy before ToDictionary so that you can count it.
result = context
.Items
.Where(b => !b.Deleted)
.GroupBy(x => x.Name)
.ToDictionary(g => g.Key, g => g.Count())
.OrderBy(g => g.Key);
based on your updated comment, then what you need is
result = context
.Items
.Where(b => !b.Deleted)
.OrderBy(b => b.Name)
.AsEnumerable()
.Select((v,i) => new { i, v.Name })
.ToDictionary(g => g.i + 1, g => g.Name);
Note that you need to add AsEnumerable so that the Select clause works as linq to object (Select that accept index is not supported in L2S).
Just use:
int count = 0;
var result = context
.Items
.Where(b => !b.Deleted)
.OrderBy(b => b.Name)
.ToDictionary(b => ++count, b => b.Name);
An alternative way of achieving this is:
var sortedNames = context
.Items
.Where(b => !b.Deleted)
.Select(b => b.Name)
.OrderBy(b => b.Name)
.ToArray();
result = Enumerable.Range(1, sortedNames.Length)
.ToDictionary(i => i, i => sortedNames[i - 1]);
To get zero-based numbering, use Enumerable.Range(0, sortedNames.Length) and sortedNames[i] instead.

Dictionaries: An item with the same key has already been added

In my MVC app I am using 2 dictionaries to populate SelectList for DropDownList. Those dictionaries will be supplied with dates as string and datetime values.
I have this chunk of code for the first dictionary that works just fine:
if (m_DictDateOrder.Count == 0)
{
m_DictDateOrder = new Dictionary<string, DateTime>();
m_DictDateOrder =
m_OrderManager.ListOrders()
.OrderBy(x => x.m_OrderDate)
.Distinct()
.ToDictionary(x => x.m_OrderDate.ToString(), x => x.m_OrderDate);
}
But when I get to the second dictionary:
if (m_DictDateShipped.Count == 0)
{
m_DictDateShipped = new Dictionary<string, DateTime>();
m_DictDateShipped =
m_OrderManager.ListOrders()
.OrderBy(x => x.m_ShippedDate)
.Distinct()
.ToDictionary(x => x.m_ShippedDate.ToString(), x => x.m_ShippedDate);
}
I get a runtime error on the LINQ request for the second dictionary:
An item with the same key has already been added.
I first though that I add to instantiate a new dictionary (that's the reason for the "new" presence), but nope. What did I do wrong?
Thanks a lot!
You are Distinct'ing the rows, not the dates.
Do this instead:
if (m_DictDateShipped.Count == 0)
{
m_DictDateShipped = m_OrderManager.ListOrders()
//make the subject of the query into the thing we want Distinct'd.
.Select(x => x.m_ShippedDate)
.Distinct()
.ToDictionary(d => d.ToString(), d => d);
}
Don't bother sorting. Dictionary is unordered.
My standard pattern for this (since I have disdain for Distinct) is:
dictionary = source
.GroupBy(row => row.KeyProperty)
.ToDictionary(g => g.Key, g => g.First()); //choose an element of the group as the value.
You applied the Distinct to the order, not to the date. Try
m_OrderManager.ListOrders()
.OrderBy(x => x.m_ShippedDate)
.Select(x =>x.m_ShippedDate)
.Distinct()
.ToDictionary(x => x.ToString(), x => x);

Categories

Resources