4

I have a list of Foos that I would like to filter according to foo.HasBar.
Foo also have a property Baz.
When a Foo is selected, all Foos with the same Baz object should be filtered.
Is it possible to achieve this using a LINQ query or should I use a foreach instead?

Edit: Here's a sample dataset:

Foo.HasBar = true; Foo.Baz = 1;
Foo.HasBar = true; Foo.Baz = 1;
Foo.HasBar = false; Foo.Baz = 1;
Foo.HasBar = true; Foo.Baz = 2;
Foo.HasBar = false; Foo.Baz = 2;

What I'm trying to achieve is that no other iteration on another Foo.HasBar = true; Foo.Baz = 1; will be performed or no another iteration on Foo.HasBar = false; Foo.Baz = 2; will be performed if Foo.HasBar = true; Foo.Baz = 2; was already selected.

Here's how I would have done it with a foreach loop:

var selectedFoos = new List<Foo>();

foreach(var foo in foos)
{
  if (selectedFoos.Exists(f => f.Baz == foo.Baz))
    continue;

  if (foo.HasBar)
     selectedFoos.Add(foo);
}
abatishchev
  • 98,240
  • 88
  • 296
  • 433
the_drow
  • 18,571
  • 25
  • 126
  • 193

5 Answers5

3

Use IEnumerable<Foo>.Distinct, and implement your equals operator in a performant manner, where the Baz property is checked, and the HasBar property is ignored if Baz isn't equal. You can do this with &&, because if the left-hand expression is false, the right-hand expression isn't evaluated.

Then, filter based on HasBar with IEnumerable<Foo>.Where.

If you do not want to clutter your Foo object with an Equals operator, or you need different Equals implementations for different cases, then implement a separate IEqualityComparer<Foo>.

This also has the advantage that you can avoid checking the HasBar property completely while getting distinct values. If you skipped the check in the class itself, it might cause subtle bugs because people might expect them to be equal. But with a well named custom comparer, it is unlikely people will assume that it will ensure absolute equality.

Here is some example code:

IEnumerable<Foo> selectedFoos =
    sampleDataSet
        .Distinct(new PerformantFooComparer())
        .Where(f => f.HasBar);

// ...

private class PerformantFooComparer : IEqualityComparer<Foo>
{
    public bool Equals(Foo x, Foo y)
    {
        bool isXNull = x == null;
        bool isYNull = y == null;

        return isXNull == isYNull
            && isXNull
            || (
                x.Baz == y.Baz
                // && x.HasBar == y.HasBar
                // HasBar commented out to avoid performance overhead.
                // It is handled by a Where(foo => foo.HasBar) filter
                );
    }

    public int GetHashCode(Foo obj)
    {
        if (obj == null)
            return 0;

        // See: http://stackoverflow.com/questions/263400/what-is-the-best-algorithm-for-an-overridden-system-object-gethashcode
        int hash = 17;
        hash = hash * 23 + obj.Baz.GetHashCode();
        // HasBar intentionally not hashed
        return hash;
    }
}
Merlyn Morgan-Graham
  • 58,163
  • 16
  • 128
  • 183
  • 1
    Good one. BTW if you only hash Baz there is no need for the prime numbers math. – Stilgar May 02 '11 at 09:11
  • If you use this solution, make sure you triple-slash comment the `PerformantFooComparer` class to let people know it ignores the `HasBar` property. – Merlyn Morgan-Graham May 02 '11 at 09:15
  • @Stilgar: Yes, absolutely true. You could simply return `obj.Baz.GetHashCode()` in that case. I was assuming he was strongly redacting his data type, though, so I wanted to make sure he could expand the example to multiple properties. – Merlyn Morgan-Graham May 02 '11 at 09:16
  • Graham: You assumed correctly and I have learned more than what I've sought to. My gratitude to you :) – the_drow May 02 '11 at 09:31
1
var results = from f in Foos where (foo.HasBar) && (foo.Baz equals SelectedBaz) select f;
CD..
  • 72,281
  • 25
  • 154
  • 163
jimjim
  • 2,414
  • 2
  • 26
  • 46
1
var q = from f in foos
        group f by f.Baz into g
        let tempFoo = g.FirstOrDefault(foo => foo.HasBar)
        where tempFoo != null
        select tempFoo;

The best I could come up with (for now).

The use of let should avoid multiple calls to FirstOrDefault so your performance intensive HasBar won't be called more times than needed assuming that the implementation of FirstOrDefault would not iterate after it finds a result. If let was not used FirstOrDefault should be used in the where clause.

Stilgar
  • 22,354
  • 14
  • 64
  • 101
0

From your code, you just need to:

foos.Where(f => !foos.Where(f2 => f2.HasBar).Any(s => s.Baz == f.Baz));
Homam
  • 23,263
  • 32
  • 111
  • 187
0

you can simply go with

var filtered = from f in foos 
               where foo.HasBar == true
               select f;
anishMarokey
  • 11,279
  • 2
  • 34
  • 47
  • But HasBar is a real performance consumer. I'm trying to optimize here. – the_drow May 02 '11 at 08:09
  • @the_drow what is the use of if (selectedFoos.Exists(f => f.Baz == foo.Baz)) continue; ? if this is not there also it will filter RT? – anishMarokey May 02 '11 at 08:11
  • if the query already selected a foo with a baz there is no need to select other foos with the same baz. Thus it should skip it. – the_drow May 02 '11 at 09:07