NSN
NSN

Reputation: 750

lambda expression foreach loop

I have the following code

int someCount = 0;

for ( int i =0 ; i < intarr.Length;i++ )
{
   if ( intarr[i] % 2 == 0 )
   { 
       someCount++;
       continue;
   }
   // Some other logic for those not satisfying the condition
}

Is it possible to use any of the Array.Where or Array.SkiplWhile to achieve the same?

foreach(int i in intarr.where(<<condtion>> + increment for failures) )
{
      // Some other logic for those not satisfying the condition    
}

Upvotes: 1

Views: 2891

Answers (3)

devgeezer
devgeezer

Reputation: 4189

Nothing (much) prevents you from rolling your own Where that counts the failures. "Nothing much" because neither lambdas nor methods with yield return statements are allowed to reference out/ref parameters, so the desired extension with the following signature won't work:

// dead-end/bad signature, do not attempt
IEnumerable<T> Where(
    this IEnumerable<T> self,
    Func<T,bool> predicate,
    out int failures)

However, we can declare a local variable for the failure-count and return a Func<int> that can get the failure-count, and a local variable is completely valid to reference from lambdas. Thus, here's a possible (tested) implementation:

public static class EnumerableExtensions
{
    public static IEnumerable<T> Where<T>(
        this IEnumerable<T> self,
        Func<T,bool> predicate,
        out Func<int> getFailureCount)
    {
        if (self == null) throw new ArgumentNullException("self");
        if (predicate == null) throw new ArgumentNullException("predicate");

        int failures = 0;

        getFailureCount = () => failures;

        return self.Where(i =>
            {
                bool res = predicate(i);
                if (!res)
                {
                    ++failures;
                }
                return res;
            });
    }
}

...and here's some test code that exercises it:

Func<int> getFailureCount;
int[] items = { 0, 1, 2, 3, 4 };
foreach(int i in items.Where(i => i % 2 == 0, out getFailureCount))
{
    Console.WriteLine(i);
}
Console.WriteLine("Failures = " + getFailureCount());

The above test, when run outputs:

0
2
4
Failures = 2

There are a couple caveats I feel obligated to warn about. Since you could break out of the loop prematurely without having walked the entire IEnumerable<>, the failure-count would only reflect encountered-failures, not the total number of failures as in @nneonneo's solution (which I prefer.) Also, if the implementation of LINQ's Where extension were to change in a way that called the predicate more than once per item, then the failure count would be incorrect. One more point of interest is that, from within your loop body you should be able to make calls to the getFailureCount Func to get the current running failure count so-far.

I presented this solution to show that we are not locked-into the existing prepackaged solutions. The language and framework provides us with lots of opportunities to extend it to suit our needs.

Upvotes: 0

Tim M.
Tim M.

Reputation: 54377

I definitely prefer @nneonneo's way for short statements (and it uses an explicit lambda), but if you want to build a more elaborate query, you can use the LINQ query syntax:

var count = ( from val in intarr 
    where val % 2 == 0 
    select val ).Count();

Obviously this is probably a poor choice when the query can be expressed with a single lambda expression, but I find it useful when composing larger queries.

More examples: http://code.msdn.microsoft.com/101-LINQ-Samples-3fb9811b

Upvotes: 3

nneonneo
nneonneo

Reputation: 179422

Use LINQ:

int someCount = intarr.Count(val => val % 2 == 0);

Upvotes: 6

Related Questions