c# 2 for each loop if one works the other doesn't

Viewed 98

I have 2 different regex matches and currently using 2 foreach loop to get the match values. However, for now, if one foreach works, then the other doesn't.

Try 1

foreach (string dir in multiTrimmed1)
{
  foreach (string dir2 in multiTrimmed2)
  {
    var forVal2= dir.Replace("$", "").Replace(" ", "");
    var forVal3 = dir2.Replace(",", "");
    var parsedParams= new ParsedMethod()
    {
      Value1= forVal1,
      Value2 = Convert.ToDecimal(forVal2),
      Value3 = Convert.ToDecimal(forVal3),
      Value4 = forVal4
    };
    MergeAllValues(parsedParams);
   }
}

So the problem here is that multiTrimmed1 and multiTrimmed2 have string[4] each.

Let's say A, B, C, and D. In my try 1, I want forVal2 and forVal3 to go through the loop correctly.

What I mean by that, when forVal2 is A, forVal3 is also A - send it to MergeAllValues, next, B and B - send it to MergeAllValues, next, C and C - send it to MergeAllValues, and next D and D - send it to MergeAllValues.

Currently only one loop works. If forVal3 value changes to A, B, C, and D -- then forVal2 just stops at A.

Try 2

bool loopAgain = true;
while (loopAgain)
{
  loopAgain = false;
  foreach (string dir in multiTrimmed1)
   {
    foreach (string dir2 in multiTrimmed2)
    {
      var forVal2= dir.Replace("$", "").Replace(" ", "");
      var forVal3 = dir2.Replace(",", "");
      var parsedParams= new ParsedMethod()
      {
        Value1= forVal1,
        Value2 = Convert.ToDecimal(forVal2),
        Value3 = Convert.ToDecimal(forVal3),
        Value4 = forVal4
      };
      MergeAllValues(parsedParams);
      loopAgain = true;
      break;
   }
}

In this case, forVal2 gets the values just fine, but forVal3 stays at A

Try 3

bool loopAgain = true;
while (loopAgain)
{
  loopAgain = false;
  foreach (string dir in multiTrimmed1)
   {
    var forVal2= dir.Replace("$", "").Replace(" ", "");
    
    foreach (string dir2 in multiTrimmed2)
    {
      var forVal3 = dir2.Replace(",", "");
      var parsedParams= new ParsedMethod()
      {
        Value1= forVal1,
        Value2 = Convert.ToDecimal(forVal2),
        Value3 = Convert.ToDecimal(forVal3),
        Value4 = forVal4
      };
      MergeAllValues(parsedParams);
      loopAgain = true;
      break;
   }
}

Still only work loop works. How can I make both work?

1 Answers

You've asserted that multiTrimmed1 and multiTrimmed2 have a 1:1 relationship based on element position, so we can use .Zip:

foreach (var (dir, dir2) in multiTrimmed1.Zip(multiTrimmed2, (a,b) => (a, b)))
{
      string forVal2 = dir.Replace("$", "").Replace(" ", "");
      string forVal3 = dir2.Replace(",", "");
      var parsedParams = new ParsedMethod()
      {
        Value1 = forVal1,
        Value2 = Convert.ToDecimal(forVal2),
        Value3 = Convert.ToDecimal(forVal3),
        Value4 = forVal4
      };
      MergeAllValues(parsedParams);
}

Try it online

Essentially what we're doing here is simultaneously looping through multiTrimmed1 and multiTrimmed2 to produce a ValueTuple of the elements at each position (a from multiTrimmed1 and b from multiTrimmed2). We're then decomposing that with var (dir, dir2) so that we can access the values individually.

The reason you were having issues with your code above is that for concrete collections such as List<T> or arrays (T[]), foreach will start from the beginning every time.

So when you write something like this:

foreach (var a in colA)
{
    foreach (var b in colB)
    {

    }
}

You're saying "for each item in colA, run the following code" (which itself happens to also contain a loop. So you're not looping both collections simultaneously, you're looping through the outer collection (colA) and for every item in colA, you're also going through every item in colB.

Zip's approach is to take an item from colA and take an item from colB, perform your merge function, and then yield the value. It then takes the next item from colA and the next item from colB and yields them. This allows you to effectively loop through the two collections simultaneously.

So to clarify what it does internally, you could implement your own Zip method with the following approximation:

public static IEnumerable<T> MyZip<T, A, B>(IEnumerable<A> eA, IEnumerable<B> eB, Func<A, B, T> mergeFunc)
{
    var aEnumerator = eA.GetEnumerator(); // get an enumerator for eA
    var bEnumerator = eB.GetEnumerator(); // get an enumerator for eB

    // while there are more items to be read in a AND b
    while (aEnumerator.MoveNext() && bEnumerator.MoveNext())
    {
        // execute the mergeFunction on the value from a and the value from b and store the result
        T itemResult = mergeFunc(aEnumerator.Current, bEnumerator.Current);
        // return the result to the caller
        yield return itemResult;
    }
}

Try it online

Related