2

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?

davis
  • 340
  • 1
  • 4
  • 17
  • Are you looking for [Zip](https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.zip?view=net-5.0), rather than finding all combinations of `multiTrimmed1` and `multiTrimmed2` (as in try 1)? – ProgrammingLlama Jul 11 '21 at 05:29
  • I haven't used it before. If that's the solution I will look into it – davis Jul 11 '21 at 05:31
  • 1
    _"In this case, forVal2 gets the values just fine, but forVal3 stays at A"_ - `break;` exits the loop that contains it. You're calling it unconditionally in Try 2 and Try 3, so on the first iteration of `multiTrimmed2`, it will exit the loop. It's effectively the same as writing `string dir2 = multiTrimmed2.First();` and doing away with the inner-most loop altogether. – ProgrammingLlama Jul 11 '21 at 05:32
  • If you can confirm that multiTrimmed1 and multiTrimmed2 have a 1:1 relationship determined by each element's position in the list (i.e. position 0 in multiTrimmed1 corresponds to position 0 in multiTrimmed2, 1 to 1, 2 to 2, etc.) then I can write an answer with `Zip`. – ProgrammingLlama Jul 11 '21 at 05:33
  • But if I don't break and loopAgain it won't go back to the first foreach and the forVal2 will stay at A. Only forVal3 goes through foreach. Not sure how to make both go through foreach – davis Jul 11 '21 at 05:34
  • Should be 1 on 1 based on my regex search. – davis Jul 11 '21 at 05:36
  • I think you misunderstand how looping works. I'll write an answer anyway. – ProgrammingLlama Jul 11 '21 at 05:37
  • 1
    And what about a single for-loop? You loop over all indices, which you then use to access the corresponding items from both lists – Hans Kesting Jul 11 '21 at 06:13
  • Please don't delete https://stackoverflow.com/questions/68492611/c-sharp-whats-the-best-method-to-handle-mapping-different-length-of-lists/68493437#68493437 - it's not bad, it would just take a second to fix. – Enigmativity Jul 23 '21 at 03:03

1 Answers1

5

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

ProgrammingLlama
  • 36,677
  • 7
  • 67
  • 86
  • Can Zip also loop through 3, 4, 5 ... more collections? just curious.. by doing (a, b, c, d ...) ? (If its all 1:1 relationships) – davis Jul 11 '21 at 05:54
  • 1
    @davis It can't. There's [this question](https://stackoverflow.com/questions/10297124/how-to-combine-more-than-two-generic-lists-in-c-sharp-zip) about how to do this with `Zip`. Essentially you `Zip` and then you `Zip` again. Alternatively, you could extend my demonstration `Zip` method to handle more than two collections. – ProgrammingLlama Jul 11 '21 at 05:57