Can we use multiple variables in foreach - c#

Can we use multiple variables in foreach
foreach (var item1 in collection1;var items2 in collection2)
{
}
I want to do this because I need to fetch two collections from a database and append both of them to a ComboBox.

Use LINQ to join the arrays putting the result into an anonymous type, then iterate over the resulting collection.
var col = collection1.Join(collection2, x => x, y => y, (x, y) => new { X = x, Y = y });
foreach (var entry in col) {
// entry.X, entry.Y
}
Edit:
When posting the answer I assumed that collection1 and collection2 contained different types. If they contain both the same type or share a common base type, there are alternatives:
If you want to allow duplicates:
collection1.Concat(collection2); // same type
collection1.select(x => (baseType)x).Concat(collection2.select(x => (baseType)x)); // shared base type
No duplicates:
collection1.Union(collection2); // same type
collection1.select(x => (baseType)x).Union(collection2.select(x => (baseType)x)); // shared base type
Form framework 4.0 onwards Zip can replace the original solution:
collection1.Zip(collection2, (x, y) => new { X = x, Y = y });
For an overview over most of the available LINQ funktions please refer to 101 LINQ Samples.
Without LINQ use two hierarchical foreach loops (increasing the number of interations) or one foreach loop to create an inermediate type and a second to iterate over the collection of intermediates or if the types in the collections are the same add them to a list (using AddRange) and then iterate over this new list.
Many roads lead to one goal ... its up to you to chose one.

You can Zip the collections
foreach (var item in collection1.Zip(collection2, (a, b) => new { A = a, B = b }))
{
var a = item.A;
var b = item.B;
// ...
}
This assumes that the elements match at the same position (e.g. the first element from collection1 joins the first element of collecion2). It is quite efficient.

No, you cannot use multiple variables in a foreach, in loop. Check the language reference. What would happen if each collection had a different number of items?
If you want to iterate over both collections, try using a union:
foreach (var item1 in collection1.Union(collection2))
{
...
}

foreach is used to enumerate individual items in a collection. So no you can't. You have to use it one after the other.
It would be better to use:
void myfunc()
{}
foreach(var item1 in collection1){myfunc();}
foreach(var item2 in collection2){myfunc();}
than
foreach(var item1 in collection1)
foreach(var item2 in collection2)
{
myfunc();
}
This would run for n*m times. Whereas previous example would run for only n+m times.

Judging by your comments I think what you're really trying to do is not get the Cartesian product of the two collections, but a [SQL] UNION of the two sets. You have two options:
Concat the two collections:
foreach(var items in collection1.Concat(collection2)) {}
Just add them both separately, assuming you don't need to do anything fancy by iterating (probably the best/simplest):
myComboBox.Items.AddRange(collection1);
myComboBox.Items.AddRange(collection2);
If, however, you do want the n*m Cartesian product of [SQL pseudocode] collection1 CROSS JOIN collection2, you would use two nested foreach statements:
foreach(var item1 in collection1)
foreach(var item2 in collection2)
{
}
Or, you can join the two in LINQ and iterate over the joined collection:
foreach(var items in (from i1 in collection1
from i2 in collection2
select Tuple.Create(i1, i2)))
{
}

You can do it using a newer syntax:
var collection1 = new List<int>(){1,2,3,4};
var collection2 = new List<int>(){5,6,7,8};
var zip = collection1.Zip(collection2, (i,j) => (i,j));
foreach (var (item1, item2) in zip)
{
Console.WriteLine($"item1:{item1} item2:{item2}");
}
// outputs:
//item1:1 item2:5
//item1:2 item2:6
//item1:3 item2:7
//item1:4 item2:8

Do you want to pair the items from one collection with corresponding items of the other?
foreach (var pair in col1.Zip(col2, (Item1, Item2) => new { Item1, Item2 })
{
//do something with pair.Item1 and pair.Item2
}
Note: if the first collection has 10 items and the second has 8, you will get 8 pairs; the last two items in the first collection will be dropped because there's nothing to match them with in the second collection. More generally, the number of iterations will be Min(col1.Count(), col2.Count()).
Do you want to iterate all of the items in one collection and then all of the items in the second?
foreach (var element in col1.Concat(col2))
{
//do something with element
}
Note: if the first collection has 10 elements and the second has 8, this loop will execute 18 times, or, more generally, the number of iterations will be col1.Count() + col2.Count().
Do you want to pair each item in one collection with each item in the other?
foreach (var item1 in col1)
foreach (var item2 in col2)
{
//do something with item1 and item2
}
Note: this is the cartesian product, so, not surprisingly, the number of iterations is the product of the collections' sizes. If we have 10 and 8 items, the loop will execute 80 times. For consistency's sake, that's col1.Count() * col2.Count().

You could use an iterator:
IEnumerator <Item2Type> item2Itt = Collection2.GetEnumerator();
item2Itt.MoveNext(); // The iterator returned above is BEFORE the first element in the collection.
foreach (Item1Type item1 in collection1)
{
item1.blahblahblah;
item2Itt.Current.blahBlahBlah;
item2Itt.MoveNext();
}

I think this way can be used:
List<Type> allOfThem = new List<Type>(); //use proper type for collection
allOfThem.AddRange(collection1);
allOfThem.AddRange(collection2);
foreach (Type item in allOfThem)
{
...
}

Using enumerator is the simplest way to do it. You can use either of the two collection to get the enumerator and run foreach on the other one.
public void MatchSentences() {
string[] OrigSentences = { "hello you", "what are you doing", "hope things are fine" };
string[] CompareSentences = { "hello you", "what are you doing", "hope things are fine" };
// Get enumerator on the second collection
var outputStrEnum = CompareSentences.GetEnumerator();
// Run foreach on the first collection
foreach (var sentence in OrigSentences) {
outputStrEnum.MoveNext();
string testAgainst = outputStrEnum.Current.ToString();
bool result = sentence.Equals(testAgainst);
Assert.IsTrue(result,
String.Format(" Expected for '{0}': {1}; Actual: '{2}'",
testAgainst, result,sentence) );
}
}

Related

Update a property field in a List

I have a List<Map> and I wanted to update the Map.Target property based from a matching value from another List<Map>.
Basically, the logic is:
If mapsList1.Name is equal to mapsList2.Name
Then mapsList1.Target = mapsList2.Name
The structure of the Map class looks like this:
public class Map {
public Guid Id { get; set; }
public string Name { get; set; }
public string Target { get; set; }
}
I tried the following but obviously it's not working:
List<Map> mapsList1 = new List<Map>();
List<Map> mapsList2 = new List<Map>();
// populate the 2 lists here
mapsList1.Where(m1 => mapsList2.Where(m2 => m1.Name == m2.Name) ) // don't know what to do next
The count of items in list 1 will be always greater than or equal to the count of items in list 2. No duplicates in both lists.
Assuming there are a small number of items in the lists and only one item in list 1 that matches:
list2.ForEach(l2m => list1.First(l1m => l1m.Name == l2m.Name).Target = l2m.Target);
If there are more than one item in List1 that must be updated, enumerate the entire list1 doing a First on list2.
list1.ForEach(l1m => l1m.Target = list2.FirstOrDefault(l2m => l1.Name == l2m.Name)?.Target ?? l1m.Target);
If there are a large number of items in list2, turn it into a dictionary
var d = list2.ToDictionary(m => m.Name);
list1.ForEach(m => m.Target = d.ContainsKey(m.Name) ? d[m.Name].Target : m.Target);
(Presumably list2 doesn't contain any repeated names)
If list1's names are unique and everything in list2 is in list1, you could even turn list1 into a dictionary and enumerate list2:
var d=list1.ToDictionary(m => m.Name);
list2.ForEach(m => d[m.Name].Target = m.Target);
If List 2 has entries that are not in list1 or list1 has duplicate names, you could use a Lookup instead, you'd just have to do something to avoid a "collection was modified; enumeration may not execute" you'd get if you were trying to modify the list it returns in response to a name
mapsList1.Where(m1 => mapsList2.Where(m2 => m1.Name == m2.Name) ) // don't know what to do next
LINQ Where doesn't really work like that / that's not a statement in itself. The m1 is the entry from list1, and the inner Where would produce an enumerable of list 2 items, but it doesn't result in the Boolean the outer Where is expecting, nor can you do anything to either of the sequences because LINQ operations are not supposed to have side effects. The only thing you can do with a Where is capture or use the sequence it returns in some other operation (like enumerating it), so Where isn't really something you'd use for this operation unless you use it to find all the objects you need to alter. It's probably worth pointing out that ForEach is a list thing, not a LINQ thing, and is basically just another way of writing foreach(var item in someList)
If collections are big enough better approach would be to create a dictionary to lookup the targets:
List<Map> mapsList1 = new List<Map>();
List<Map> mapsList2 = new List<Map>();
var dict = mapsList2
.GroupBy(map => map.Name)
.ToDictionary(maps => maps.Key, maps => maps.First().Target);
foreach (var map in mapsList1)
{
if (dict.TryGetValue(map.Name, out var target))
{
map.Target = target;
}
}
Note, that this will discard any possible name duplicates from mapsList2.

List of Objects with int property compared to List of Int

I have 2 lists. First is a list of objects that has an int property ID. The other is a list of ints.
I need to compare these 2 lists and copy the objects to a new list with only the objects that matches between the two lists based on ID. Right now I am using 2 foreach loops as follows:
var matched = new list<Cars>();
foreach(var car in cars)
foreach(var i in intList)
{
if (car.id == i)
matched.Add(car);
}
This seems like it is going to be very slow as it is iterating over each list many times. Is there way to do this without using 2 foreach loops like this?
One slow but clear way would be
var matched = cars.Where(car => intList.Contains(car.id)).ToList();
You can make this quicker by turning the intList into a dictionary and using ContainsKey instead.
var intLookup = intList.ToDictionary(k => k);
var matched = cars.Where(car => intLookup.ContainsKey(car.id)).ToList();
Even better still, a HashSet:
var intHash = new HashSet(intList);
var matched = cars.Where(car => intHash.Contains(car.id)).ToList();
You could try some simple linq something like this should work:
var matched = cars.Where(w => intList.Contains(w.id)).ToList();
this will take your list of cars and then find only those items where the id is contained in your intList.

Concat three list by entry

I was just wondering whether it is posisble to concat list by entry so;
List One Concat List Two Concat List Three
Entry One -> Entry Two -> Entry Three
Entry One -> Entry Two -> Entry Three
Entry One -> Entry Two -> Entry Three
The reason I ask is I have three list that loop the values I need off diferent parts of the program. I need a way on concat the entries in the order they appear across the three list. The code for looping each list is;
List<string> QualificationList = new List<string>();
List<string> SubjectList = new List<string>();
List<string> GradeList = new List<string>();
foreach (XmlNode QualificationNode in GetQualification)
{
GetQualificationType = QualificationNode.InnerText;
QualificationList.Add(GetQualificationType);
}
foreach (XmlNode SubjectNode in GetSubject)
{
GetSubjects = SubjectNode.InnerText;
SubjectList.Add(GetSubjects);
}
foreach (XmlNode node in GetGrade)
{
GetGrades = node.InnerText;
GradeList.Add(GetGrades);
}
I was just wondering whether this is possible if not how could I gather the information which I need to concat or stringbuilder properly. Thanks for any help which you can provide
I think you want Zip:
var concatList = QualificationList.Zip(SubjectList, (q,s) => new {q, s})
.Zip(GradeList, (qs, g) => new {qs.q, qs.s, qs.g})
That will give you an anonymous type with three properties: q (qualification), s (subject), and g (grade). If you want to concatenate them into one string just project it with Select:
.Select( qsg => string.Format("{0} {1} {2}", q, s, g);
You could also replace each xxxList with a projection if you want; it just adds more complexity to the Linq statement (and may make it harder to debug):
var concatList = GetQualification.Select(node => node.InnerText)
.Zip(GetSubject.Select(node => node.InnerText),
(q,s) => new {q, s})
.Zip(GetGrade.Select(node => node.InnerText),
(qs, g) => new {qs.q, qs.s, qs.g})
Your code can be simplified to this:
QualificationList.AddRange(GetQualification.Select(item => item.InnerText));
SubjectList.AddRange(GetSubject.Select(item => item.InnerText));
GradeList.AddRange(GetGrade.Select(item => item.InnerText));
Using AddRange will save you from the loops and Select method will return the texts inside the order they appear.

Simplest way to check db values against values in an array

I'm looping through the items in my database using C# .NET and I'm attempting to display different data dependant on if a column value matches any of the values in an array. Because my array could potentially have hundreds of values, I'd rather not create hundreds of different IF statements, if possible. Is there a simpler way to achieve this?
Here's some example code, where "Items" is my db data and "Categories" is a column of said data:
var categoryList = new List<int> { 1, 2, 3, 4 };
foreach(var item in Items){
if(item.Categories.Any(x => #categoryList.Equals(x))){
<p>Yes</p>
}else{
<p>No</p>
}
}
The answer I give is based on the answer of this question. I modified the code to your situation.
foreach(var item in Items)
{
bool hasCategory = categoryList.Any(x => item.Categories.Any(c => c.Id == x));
}
or for larger collections (performance-wise):
bool hasCategory = item.Categories.Select(c => c.Id)
.Intersect(categoryList)
.Any();
Edit:
At first I thought item.Categories was a collection of IDs or something but then I started doubting. If item.Categories is just a single integer, following code will work:
foreach(var item in Items)
{
if(categoryList.Any(x => x == item.Categories))
<p>Yes</p>
else
<p>No</p>
}

Linq equivalent of for in foreach

I'm borrowing code from this question as I went there for inspiration. I have a list of objects, the object has an integer property and I want to foreach the list and the loop the number of integers.
It's a very basic for inside a foreach but I suspect I could use a SelectMany but can't get it working. The following code works but I would like a linq version.
//set up some data for our example
var tuple1 = new { Name = "Tuple1", Count = 2 };
var tuple2 = new { Name = "Tuple2", Count = 3 };
//put the tuples into a collection
var tuples = new [] { tuple1, tuple2 };
foreach(var item in tuples)
{
for(int i = 0; i < item.Count; i++)
Console.WriteLine(item.Name);
}
var flattened = tuples.SelectMany(t => Enumerable.Repeat(t.Name, t.Count));
foreach(var word in flattened)
{
Console.WriteLine(word);
}
You can use SelectMany; you simply need to generate sequences:
tuples.SelectMany(t => Enumerable.Repeat(t.Name, t.Count))
There is no Values property in your anonymous type. But i assume that you mean the Count property instead and you want to repeat the name this number. You can either use Enumerable.Range or Enumerable.Repeat:
IEnumerable<String> tupleNames = tuples
.Select(t => string.Join(Environment.NewLine, Enumerable.Repeat(t.Name, t.Count)));
Console.Write(string.Join(Environment.NewLine, tupleNames));
Output:
Tuple1
Tuple1
Tuple2
Tuple2
Tuple2
There is no linq equivalent of a foreach. You should use an actual foreach to iterate an IEnumerable and perform an action on each item.

Categories

Resources