Transform foreach to LINQ [duplicate] - c#

This question already has answers here:
LINQ equivalent of foreach for IEnumerable<T>
(22 answers)
Closed 3 years ago.
I have two collections, both contain objects.
First one is IList and the second one is Dictionary.
I need to traverse through IList and if the condition is filled then activate method from the certain object which is stored in Dictionary.
The current situation is like this:
foreach (MyObject mo in MyListOfObjects)
{
if (mo.Active == myStatus.Enabled)
{
DictList[mo.ID].Start();
}
}
So far i've done this:
var r = MyListOfObjects.Where(mo => mo.Active == myStatus.Enabled);
But I have no idea how to include in this DictList[mo.ID].Start();

Not a great use of linq, but you could filter the list using linq then loop through it.
var itemsToStart = MyListOfObjects.Where(mo => mo.Active == myStatus.Enabled)
.Select(mo=> DictList[mo]); //or ToList() if you intend to re-iterate
foreach (var itemToStart in itemsToStart) {
itemToStart.Start();
}

If anything at all, just remove the inner if
foreach (MyObject mo in MyListOfObjects.Where(x => x.Active == myStatis.Enabled))
{
DictList[mo.ID].Start();
}
But that is all you should do - it is perfectly readable and maintainable.

MyListOfObjects.Where(mo => mo.Active == myStatus.Enabled).ToList().ForEach(mo => DictList[mo.ID].Start());
ForEach is found: https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.list-1.foreach?view=netframework-4.7.2

Related

How to replace foreach with linq [duplicate]

This question already has answers here:
Update all objects in a collection using LINQ
(18 answers)
Closed 16 days ago.
foreach (var item in items)
{
item.category = "All Items";
}
How to replace the above foreach with Linq.
I have tried with following code, but it returns null for the item
_ = items.Select(x => x.item = "All Items")
Note : items is of type IEnumerable<ItemList>
linq is an example of functional programming - in general it does not change the input data it reads it in and outputs new data
you can do this (assuming you have a List to start with)
items = items.Select(item=>new {category="All Items", x=.., y=..}).ToList()
wher x and y are the other fields in ItemList

faster way to find persons not in list A but list B [duplicate]

This question already has answers here:
Using LINQ to remove elements from a List<T>
(14 answers)
c# Remove items in a custom list, based on another List<int>
(4 answers)
Filter List By items in Array (LINQ)
(1 answer)
Closed 1 year ago.
today I use this to get a list of persons that is not in list A but in list B. It works but seem to take a very long time to get the result. Is there a faster way that do the same?
var missingPeople = listofPersons.Where(p => allUsedPersons.All(p2 => p2.Id != p.Id)).ToList();
Your current implementation has O( n * m ) time complexity.
Where n is the cardinality of listofPersons.
Where m is the cardinality of allUsedPersons.
So if you have 500 listofPersons and 200 allUsedPersons your code will take 100,000 checks. That is bad.
This is because Linq's Where will run for every item in listofPersons, and inside your Where you have allUsedPersons.All, which will run the p2.Id != p.Id check for every item in allUsedPersons.
Instead, use a HashSet<T> to build a set of known values in O(n) time - which then lets you perform exists checks in O(1) time.
So if you have 500 listofPersons and 200 allUsedPersons my code below will take only 500 checks.
100,000 vs 500: spot the difference.
HashSet<Int32> allPeopleIds = listofPersons.Select( p => p.Id ).ToHashSet();
List<Person> missingPeople = allUsedPersons
.Where( p => !allPeopleIds.Contains( p.Id ) )
.ToList();
In relational-algebra (or is it relational-calculus?) what you're doing is known as an anti-join and Linq supports it via the Except method, however you would need to define a custom-comparator as Linq doesn't yet have an ExceptBy method (but MoreLinq does, though).
Another option is to provide a custom, reusable IEqualityComparer<Person>:
public class PersonIdComparer : IEqualityComparer<Person>
{
public bool Equals(Person x, Person y)
{
return x?.Id == y?.Id;
}
public int GetHashCode(Person obj)
{
return obj?.Id ?? int.MinValue;
}
}
You can use it for many LINQ methods. In this case you should use it for Except:
var missingPeople = listofPersons.Except(allUsedPersons, new PersonIdComparer()).ToList();
Except is quite efficient since it uses a collection similar to HashSet.
I think below method can help you for improve performance:
List<int> listOfIds = allUsedPersons.select(c => c.Id).ToList();
var missingPeople = listofPersons.Where(r=>!listOfIds.Contains(r.Id));
Putting something like allUsedPersons.All() within predicate will unnecessarily multiply the number of iterations. Prepare the list of required field(here p.id) beforehand and use it inside predicate.
Assuming allUsedPersons is list A, below would be faster
var usedPersonsId = allUsedPersons.Select(p => p.id).ToList();
var missingPeople = listofPersons.Where(p => !usedPersonsId.Contains(p.Id)).ToList();

Nested foreaches into a chained linq expression [duplicate]

This question already has answers here:
LINQ expression instead of nested foreach loops
(7 answers)
Closed 9 years ago.
I don't know if it is even possible to do what I think, but I guess it worths to try :)
Can I combine these two nested foreaches?
foreach ( var dept in curDevice.Personnel.Department.Company.Departments )
{
foreach ( var personnel in dept.Personnels )
{
myPersonnels.Add(personnel);
}
}
I want to turn this nested for each into a chained linq expression. Is is possible? If so how?
Use Enumerable.SelectMany
foreach ( var personnel in
curDevice.Personnel.Department.Company.Departments.SelectMany(x=> x.Personnels))
{
myPersonnels.Add(personnel);
}
Use the SelectMany<TSource, TResult> method:
var allPersonnel = curDevice.Personnel.Department.Company.Departments.SelectMany(dept => dept.Personnels);
// If there is no AddRange method:
foreach (var personnel in allPersonnel)
myPersonnels.Add(personnel);
// If there is an AddRange method:
myPersonnels.AddRange(personnel)
This is personnel preference (see what I did there?), but I like sticking with functional programming if that's what I start with.
You can replace the foreach language construct with the List<T>.ForEach method.
curDevice.Personnel.Department.Company.Departments
.SelectMany(department => department.Personnels)
.ToList()
.ForEach(personnel => myPersonnels.Add(personnel);
Typically, we'd use a shorter argument name for the delegates:
curDevice.Personnel.Department.Company.Departments
.SelectMany(d => d.Personnels)
.ToList()
.ForEach(p => myPersonnels.Add(p);
And, if myPersonnels is just a collection, you can create it outright:
var myPersonnels = curDevice.Personnel.Department.Company.Departments
.SelectMany(d => d.Personnels)
.ToList();
Or, if it's already a List<T>, you can add an IEnumerable<T> to it:
myPersonnels.AddRange(
curDevice.Personnel.Department.Company.Departments
.SelectMany(d => d.Personnels)
);
Ken is almost right, but if I'm right the myPersonnels list is an external one where you want to copy the result of your "query". The first two answers are very readable, but if you want to code it shortly, you can write this:
curDevice.Personnel.Department.Company.Departments
.SelectMany(x => x.Personnels) // selecting the personnels
.ToList()
.Foreach(myPersonnels.Add); // iterate trough the personnels list and copy them into the external list

How best to remove list items in a loop in C#

Given the code:
var AllItems = new List<CartItem>();
using(var db = new MainContext())
{
foreach (var item in AllItems)
{
if (!db.tblStoreItems.Where(i => i.ID == item.ItemID).Any())
{
AllItems.Remove(item);
}
}
}
Is this the best way to remove an item from the List object in a loop?
I don't think so. If you remove an item from the list on which you are iterating, the results will be securely wrong.
It's best to use an old fashion for - loop in reverse order
using(var db = new MainContext())
{
for(int x = AllItems.Count - 1; x >= 0; x--)
{
var item = AllItems[x];
if (!db.tblStoreItems.Where(i => i.ID == item.ItemID).Any())
{
AllItems.RemoveAt(x);
}
}
}
There are several things that are wrong with the loop approach, the main being - you cannot remove items from the collection you're currently iterating over with foreach - you will get an exception.
Since your main collection is a List<T>, you should use the RemoveAll method that takes in a predicate. You should also simplify your query like this:
AllItems.RemoveAll(item => !db.tblStoreItems.Any(i => i.ID == item.ItemID));
That's wrong (the OP's approach) as rightly suggested by Steve (Steve's way is probably the best in terms of performance),
I prefer to store the 'those to be removed' into a separate list, then you can do e.g.
AllItems = AllItems.Except(Items2Remove);
That is not best from performance way but for me makes things cleaner - you could also combine with LINQ enumerate - e.g. make IEnumerable from the list of records etc.
hope this helps
EDIT: just to clarify as per Steve's response

Replacing nested foreach with LINQ; modify and update a property deep within

Consider the requirement to change a data member on one or more properties of an object that is 5 or 6 levels deep.
There are sub-collections that need to be iterated through to get to the property that needs inspection & modification.
Here we're calling a method that cleans the street address of a Employee. Since we're changing data within the loops, the current implementation needs a for loop to prevent the exception:
Cannot assign to "someVariable" because it is a 'foreach iteration variable'
Here's the current algorithm (obfuscated) with nested foreach and a for.
foreach (var emp in company.internalData.Emps)
{
foreach (var addr in emp.privateData.Addresses)
{
int numberAddresses = addr.Items.Length;
for (int i = 0; i < numberAddresses; i++)
{
//transform this street address via a static method
if (addr.Items[i].Type =="StreetAddress")
addr.Items[i].Text = CleanStreetAddressLine(addr.Items[i].Text);
}
}
}
Question:
Can this algorithm be reimplemented using LINQ? The requirement is for the original collection to have its data changed by that static method call.
Update: I was thinking/leaning in the direction of a jQuery/selector type solution. I didn't specifically word this question in that way. I realize that I was over-reaching on that idea (no side-effects). Thanks to everyone! If there is such a way to perform a jQuery-like selector, please let's see it!
foreach(var item in company.internalData.Emps
.SelectMany(emp => emp.privateData.Addresses)
.SelectMany(addr => addr.Items)
.Where(addr => addr.Type == "StreetAddress"))
item.Text = CleanStreetAddressLine(item.Text);
var dirtyAddresses = company.internalData.Emps.SelectMany( x => x.privateData.Addresses )
.SelectMany(y => y.Items)
.Where( z => z.Type == "StreetAddress");
foreach(var addr in dirtyAddresses)
addr.Text = CleanStreetAddressLine(addr.Text);
LINQ is not intended to modify sets of objects. You wouldn't expect a SELECT sql statement to modify the values of the rows being selected, would you? It helps to remember what LINQ stands for - Language INtegrated Query. Modifying objects within a linq query is, IMHO, an anti-pattern.
Stan R.'s answer would be a better solution using a foreach loop, I think.
I don't like mixing "query comprehension" syntax and dotted-method-call syntax in the same statement.
I do like the idea of separating the query from the action. These are semantically distinct, so separating them in code often makes sense.
var addrItemQuery = from emp in company.internalData.Emps
from addr in emp.privateData.Addresses
from addrItem in addr.Items
where addrItem.Type == "StreetAddress"
select addrItem;
foreach (var addrItem in addrItemQuery)
{
addrItem.Text = CleanStreetAddressLine(addrItem.Text);
}
A few style notes about your code; these are personal, so I you may not agree:
In general, I avoid abbreviations (Emps, emp, addr)
Inconsistent names are more confusing (addr vs. Addresses): pick one and stick with it
The word "number" is ambigious. It can either be an identity ("Prisoner number 378 please step forward.") or a count ("the number of sheep in that field is 12."). Since we use both concepts in code a lot, it is valuable to get this clear. I use often use "index" for the first one and "count" for the second.
Having the type field be a string is a code smell. If you can make it an enum your code will probably be better off.
Dirty one-liner.
company.internalData.Emps.SelectMany(x => x.privateData.Addresses)
.SelectMany(x => x.Items)
.Where(x => x.Type == "StreetAddress")
.Select(x => { x.Text = CleanStreetAddressLine(x.Text); return x; });
LINQ does not provide the option of having side effects. however you could do:
company.internalData.Emps.SelectMany(emp => emp.Addresses).SelectMany(addr => Addr.Items).ToList().ForEach(/*either make an anonymous method or refactor your side effect code out to a method on its own*/);
You can do this, but you don't really want to. Several bloggers have talked about the functional nature of Linq, and if you look at all the MS supplied Linq methods, you will find that they don't produce side effects. They produce return values, but they don't change anything else. Search for the arguments over a Linq ForEach method, and you'll get a good explanation of this concept.
With that in mind, what you probaly want is something like this:
var addressItems = company.internalData.Emps.SelectMany(
emp => emp.privateData.Addresses.SelectMany(
addr => addr.Items
)
);
foreach (var item in addressItems)
{
...
}
However, if you do want to do exactly what you asked, then this is the direction you'll need to go:
var addressItems = company.internalData.Emps.SelectMany(
emp => emp.privateData.Addresses.SelectMany(
addr => addr.Items.Select(item =>
{
// Do the stuff
return item;
})
)
);
To update the LINQ result using FOREACH loop, I first create local ‘list’ variable and then perform the update using FOREACH Loop. The value are updated this way. Read more here:
How to update value of LINQ results using FOREACH loop
I cloned list and worked NET 4.7.2
List<TrendWords> ListCopy = new List<TrendWords>(sorted);
foreach (var words in stopWords)
{
foreach (var item in ListCopy.Where(w => w.word == words))
{
item.disabled = true;
}
}

Categories