I have a list and a dictionary.
Dictionary's key and value are items of the list.
I want to loop through dictionary and if list.id and key match change item with value.
I did this and dont know if its right or wrong but worked for me:
foreach (KeyValuePair<string, string> entry in dict)
{
list.Where(x => x.Id== entry.Key)
.Select(y => { y.item= entry.Value; return y; })
.ToList();
}
Now also I need to change date with datetime.now. Can I do it in the same linq if I can how?
First, let's see what your current code does:
foreach (KeyValuePair<string, string> entry in dict) // loop through each key/value pair in the dictionary
{
list.Where(x => x.Id== entry.Key).Select(y => { y.item= entry.Value; return y; }) // construct a query on `list` and project the result using Select
.ToList(); // Force evaluation of the query in order to execute the projection
}
The projection code actually modifies the source item (y.item = ~~), which is why this "works", but it's definitely not the right way to do it.
Unless you have a huge list (in which case the list should probably be changed), you're doing this in the least efficient way possible. Dictionaries are designed for close to O(1) lookups, and you're throwing this away with the way that you're using it. You should instead iterate through the list:
foreach (var listItem in list)
And then see if there is a corresponding item in the dictionary:
if (dict.TryGetValue(listItem.Key, out var value))
From here, we can update the list item with the new value. Putting it all together:
foreach (var listItem in list)
{
if (dict.TryGetValue(listItem.Key, out var value))
{
listItem.item = value;
}
}
Generally, we should avoid Select for updating items in the list. For update, we have other LINQ extensions such as ForEach. Moreover, you can simplify the iterations.
list.Where(listItem => dict.Keys.Contains(listItem.Id))
.ToList()
.ForEach(listItem =>
{
listItem.item = dict[item.Id].Value;
});
If you have some aversion to using for loops you could use List.ForEach, which is a list thing not a LINQ thing:
list.ForEach(x => {
if(dictionary.TryGetValue(x.Id, out var s)) {
x.Item = s;
x.ModifiedDate = DateTime.Now;
}
});
But how about making the property setter for Item maintain the modified date then you don’t have to remember to set it everywhere you make a change to Item? (Though that is not an excuse for carrying on with the LINQ side effect route in the question)
Related
I am noticing a huge performance issue with trying to get a list of keys in a ConcurrentDictionary value object that exist in an IEnumerable collection as follows:
Customer object has:
string CustomerNumber;
string Location;
var CustomerDict = ConcurrentDictionary<string, Customer>();
var customers = IEnumerable<string>();
I am trying to get a list of the keys in the dictionary where the customers.CustomerNumber is in the dictionary. What I have is below the removeItems takes a very long time to return:
var removeItems = CustomerDict
.Where(w => customers.Any(c => c == w.Value.CustomerNumber))
.Select(s => s.Key)
.ToList();
foreach(var item in removeItems)
{
CustomerDict.TryRemove(item, out _);
}
Any help would be much appreciated what best to do with this.
Make customers a HashSet<string>, who's Contains method is O(1):
var customers = HashSet<string>();
var removeItems = CustomerDict
.Where(w => customers.Contains(w.Value.CustomerNumber))
.Select(s => s.Key);
Currently, Any is iterating over customers every time which has an O(n) complexity.
Also you're call to ToList is superfluous: it adds an additional, unnecessary iteration over customers, not to mention increased memory usage.
I think its better to create HashSet from customers in order to look faster,
HashSet<string> customersHashSet = new HashSet<string>(customers);
var removeItems = CustomerDict
.Where(c => customersHashSet.Contains(c.Value.CustomerNumber))
.Select(s => s.Key);
foreach (var item in removeItems)
{
CustomerDict.TryRemove(item, out _);
}
When removing consider if you have many items in the HashSet ( relatively to the dictionary ) its maybe better to iterate over the dictionary and search in the HashSet, like this :
foreach (var item in CustomerDict.ToArray())
{
if (customersHashSet.Contains(item.Value.CustomerNumber))
CustomerDict.TryRemove(item.Key, out _);
}
The problem is that .Any will do a linear scan of the underlying collection, which in your case is the key collection of your concurrent dictionary. This takes linear effort. It would be better to dump the keys into a local HashSet and then check the inclusion via .Contains(w.Value.CustomerNumber). This becomes nearly constant effort.
Why not just simply do this:
foreach(var customer in customers) //enumerate customers
CustomerDict.TryRemove(customer, out _); //trytoremove the customer, won't do anything if the customer isn't found
I have this code:
HashSet<string> MyHash = new HashSet<String>();
foreach (MyType a in myCollection)
{
foreach (string b in a.mylist)
{
MyHash.Add(b);
}
}
I tried to make it easier to read like this but it doesn't work:
myCollection.MyType.select(x => x.mylist.select(y => MyHash.add(y)));
Any suggestions?
Select will return a collection of items instead of modifying it. Thus you have to assign its return-value into a variable or member or pass it into a method. Furthermore you´d need to flatten the results to add the members of your inner list into the hashset.
Thus when you want to add the result into your list use HashSet.UnionWith:
myHash.UnionWith(myCollection.SelectMany(x => x.MyList));
Alternativly you can also use the constructor of HashSet that accepts a collection of items:
var myHash = new HashSet<string>(...);
However IMHO this isn´t any more readable than using some foreach-based approach.
Here's how I would do it, separating query definition and state modification.
IEnumerable<string> items = myCollection.SelectMany(a => a.myList);
foreach(string b in items)
{
MyHash.Add(b);
}
If you want to one-line it for some reason:
myCollection.SelectMany(a => a.myList).ToList().ForEach(b => MyHash.Add(b));
I have the following code working with Tuples. Input is list of items, output is list of tuples and we need to calculate number of items for each date basically.
List<Tuple<DateTime, int>> list = new List<Tuple<DateTime, int>>();
foreach (ItemClass item in items)
{
foreach(Tuple<DateTime, int> tuple in list)
{
if (tuple.Item1 == item.date)
{
tuple.Item2++;
continue;
}
}
list.Add(Tuple.Create<DateTime, int>(item.date, 1));
}
This code currently doesn't compile because Item2 is read-only, the question is how to make it work?
Earlier this worked with the Dictionary but I had to remove it because it was not acceptable for outer code to work with the Dictionary.
Tuples are not intended for use in scenarios where mutability is required. You could make your own class that combines a DateTime with a mutable integer counter, but you can also do it with LINQ, converting to a list of tuples at the very end:
var list = items
.GroupBy(item => item.date)
.Select(g => Tuple.Create(g.Key, g.Count()))
.ToList();
The above code creates a group for each date, and then produces tuples only when the final counts of items in each group are known.
Try using Linq, GroupBy()to group by date, then use Select() and create a tuple for each group and finally convert to a list using ToList(). Something like
var result = items.GroupBy(x => x.Date)
.Select(x => Tuple.Create(x.Key, x.Count()))
.ToList();
I'm assuming because it's read only it already has a property, I think I've used tuple before so yeah, it probably does.
maybe you can't edit it because you can edit iterators in the Foreach() loop, perhaps experiment with another kind of loop.
OR
Set the item2 object to an object outside of the current loop and use that instead of the iterator.
why cant I select remove or delete? I want to remove a record from a list
IEnumerable<StockLocation_Table> AllCurrentStocklocations = db.StockLocation_Table.ToList();
List<StockLocation> StockLocations = ServerHelper.GetStockLocationsBatch(BatchUrl, i, batchSize);
foreach (StockLocation_Table _stock_table in AllCurrentStocklocations)
{
foreach (StockLocation _stock in StockLocations)
{
if (_stock.ServerLocationId == _stock_table.ServerLocationId)
{
AllCurrentStocklocations.?? why cant i say remove._stock_table
}
}
}
Because it is IEnumerable<T> and the Remove method is not defined in IEnumerable<T>.Since you are using ToList just use a List<T> as type:
List<StockLocation_Table> AllCurrentStocklocations = db.StockLocation_Table.ToList();
Edit: Also you can't modify the collection inside of foreach loop.You can use LINQ instead:
var AllCurrentStocklocations = db.StockLocation_Table
.Where(x => !StockLocations
.Any(s => s.ServerLocationId == x.ServerLocationId).ToList()
What you want to do here is get all of the items from your DB table where the ID is not in this other list. What you should do here is construct a query such that you get just those items without those IDs, rather than pulling down the entire DB table into a list, and then going through this other list for each item) to look for IDs so that you can remove the current item from this list. In addition to being super inefficient, this would also mean removing the item from a collection being iterated, which would break the iterator. Instead write something that can be translated into a DB query:
List<StockLocation> stockLocations = ServerHelper.GetStockLocationsBatch(
BatchUrl, i, batchSize);
var locationIDs = stockLocations.Select(location => location.ServerLocationId);
db.StockLocation_Table.Where(item =>
!locationIDs.Contains(item.ServerLocationId));
I have an IList<Price> SelectedPrices. I also have an IEnumerable<Price> that gets retrieved at a later date. I would like to add everything from the latter to the former where the former does NOT contain the primary key defined in the latter. So for instance:
IList<Price> contains Price.ID = 1, Price.ID = 2, and IEnumerable<Price> contains Price.ID = 2, Price.ID = 3, and Price.ID = 4. What's the easiest way to use a lambda to add those items so that I end up with the IList containing 4 unique Prices? I know I have to call ToList() on the IList to get access to the AddRange() method so that I can add multiple items at once, but how do I select only the items that DON'T exist in that list from the enumerable?
I know I have to call ToList() on the IList to get access to the AddRange() method
This is actually not safe. This will create a new List<T>, so you won't add the items to your original IList<T>. You'll need to add them one at a time.
The simplest option is just to loop and use a contains:
var itemsToAdd = enumerablePrices.Where(p => !SelectedPrices.Any(sel => sel.ID == p.ID));
foreach(var item in itemsToAdd)
{
SelectedPrices.Add(item);
}
However, this is going to be quadratic in nature, so if the collections are very large, it may be slow. Depending on how large the collections are, it might actually be better to build a set of the IDs in advance:
var existing = new HashSet<int>(SelectedPrices.Select(p => p.ID));
var itemsToAdd = enumerablePrices.Where(p => !existing.Contains(p.ID));
foreach(var item in itemsToAdd)
{
SelectedPrices.Add(item);
}
This will prevent the routine from going quadratic if your collection (SelectedPrices) is large.
You can try that:
var newPrices = prices.Where(p => !SelectedPrices.Any(sp => sp.ID == p.ID));
foreach(var p in newPrices)
SelectedPrices.Add(p);
I know I have to call ToList() on the IList to get access to the AddRange() method so that I can add multiple items at once
ToList will create a new instance of List<Price>, so you will be modifying another list, not the original one... No, you need to add the items one by one.
Try yourEnumerable.Where(x => !yourList.Any(y => y.ID == x.ID)) for the selection part of your question.
If you want to add new elements to the existing list and do that in a most performant way you should probably do it in a conventional way. Like this:
IList<Price> selectedPrices = ...;
IEnumerable<Price> additionalPrices = ...;
IDictionary<int, Price> pricesById = new Dictionary<int, Price>();
foreach (var price in selectedPrices)
{
pricesById.Add(price.Id, price);
}
foreach (var price in additionalPrices)
{
if (!pricesById.ContainsKey(price.Id))
{
selectedPrices.Add(price);
}
}