Home > front end >  LINQ select should break on first match
LINQ select should break on first match

Time:11-10

I want to convert the following structured code into a more readable LINQ call:

foreach (string line in this.HeaderTexts)
{
    Match match = dimensionsSearcher.Match(line);

    if (match.Success)
    {
        // Do something
        return;
    }
}

I came up with the following code:

Match foundMatch = this.HeaderTexts
    .Select(text => dimensionsSearcher.Match(text))
    .Where(match => match.Success)
    .FirstOrDefault();

if (foundMatch != null)
{
    // Do something
    return;
}

However, from my understanding, this will run the Regex check for each header text, while my first code breaks as soon as it hits for the first time. Is there a way to optimize the LINQ version of that code, of should I rather stick to the structural code?

CodePudding user response:

Your LINQ query does what you hope it does. It will only execute the regex until one header matches. So it has the same behavior as your loop. That's ensured with FirstOrDefault (or First). You could rewrite it to:

Match foundMatch = this.HeaderTexts
    .Select(text => dimensionsSearcher.Match(text))
    .FirstOrDefault(m => m.Success);
// ...

Note that Single and SingleOrDefault ensure that there is at maximum one match(otherwise they throw an InvalidOperationException), so they might need to enumerate all because they have to check if there is a second match.

Read this blog if you want to understand how lazy evaluation(deferred execution) works: https://codeblog.jonskeet.uk/category/edulinq/

CodePudding user response:

Let's say you have a list of integers, you need to add 2 to each number, then find the first one that is even.

var input = new[] { 1, 2, 3, 4, 5, 6 };
var firstEvenNumber = input
    .Select(x => x   2)
    .Where(x => x % 2 == 0)
    .First();

// firstEvenNumber is 4, which is the input "2" plus two

Now, does the Select evaluate x 2 on every input before First gets ran? Let's find out. We can replace the code in Select with a multi-line lambda to print to the console when it's evaluated.

var input = new[] { 1, 2, 3, 4, 5, 6 };

var firstEvenNumber = input
    .Select(x => {
        Console.WriteLine($"Processing {x}");
        return x   2;
    })
    .Where(x => x % 2 == 0)
    .First();

Console.WriteLine("First even number is "   firstEvenNumber);

This prints:

Processing 1
Processing 2
First even number is 4

So it looks like Linq only evaluated the minimum number of entries needed to satisfy Where and First.

Where and First doesn't need all the processed records up-front in order to pass to the next step unlike Reverse(), ToList(), OrderBy(), etc.

If you instead stuck a ToList() before First, it would be a different story.

var input = new[] { 1, 2, 3, 4, 5, 6 };

var firstEvenNumber = input
    .Select(x => {
        Console.WriteLine($"Processing {x}");
        return x   2;
    })
    .Where(x => x % 2 == 0)
    .ToList() // same thing if you put it before Where instead
    .First();

Console.WriteLine("First even number is "   firstEvenNumber);

This prints:

Processing 1
Processing 2
Processing 3
Processing 4
Processing 5
Processing 6
First even number is 4
  • Related