简体   繁体   中英

C# linq query aggregate nullable boolean

I'd like to use linq to aggregate a nullable bool following the logic:

  • If all is true then true;
  • if all is false then false;
  • else null

Here is my code, I couldn't get the bool aggregate.

class T1
{
  public string property1{get;set;}
  public string property2{get;set;}
  public bool? BoolProperty{get;set;}
}

///initialize a list<T1> t2 with values......
List<T1> t2 = new List<T1>();
t2.Add(new T1() 
        {
            property1="hello",
            property2="world",
            BoolProperty=true
        });
t2.Add(new T1() 
        {
            property1="hello",
            property2="world",
            BoolProperty=false
        });

List<T1> t1 = t2.GroupBy(g => new
        {
            g.property1,
            g.property2               
        })
        .Select(g => new T1
        {
            property1 = g.Key.property1,
            property2 = g.Key.property2,                
            BoolProperty = ////can someone help? if all object in t2 are true, true; if all object in t2 are false, false; else null
 ///in this case i am expecting a null
        }).ToList();

So t1 will be "hello", "world", null; Thanks

How about this?

BoolProperty = g.All(p => p.BoolProperty.GetValueOrDefault())
    ? true
    : (g.All(p => !(p.BoolProperty ?? true))
        ? (bool?)false
        : null)
            }).ToList();

I'd suggest the following solution. It requires the use of MoreLINQ .

List<T1> t1 = t2.GroupBy(g => new
{
    g.property1,
    g.property2
}).Select(groupedItems =>
    {
        // My code starts here
        bool? result = null;
        var bools = groupedItems
            .OrderBy(z => z.BoolProperty)
            .TagFirstLast((z, first, last) => new { z, firstOrLast = first || last })
            .Where(z => z.firstOrLast)
            .Select(z => z.z.BoolProperty).ToList();

        if (bools.Count == 0)
        {
            // Do nothing
        }
        else if (bools.First() == bools.Last())
        {
            result = bools.First();
        }
        // My code ends here

        return new T1
        {
            property1 = groupedItems.Key.property1,
            property2 = groupedItems.Key.property2,
            BoolProperty = result
        };
    }).ToList();

The key bit is the use of OrderBy , to ensure that the true , false and null values are adjacent (ie grouped together in order).

Then TagFirstLast allows us to ignore everything other than the first and last entries (which is all we need).

Then we check whether the first and last entry are the same - if they are, use that value (whether it be null or true or false - either way it fits the requirements).

Otherwise, use null (since that would be your else condition).

Put this in your code,

List<T1> t1 = t2.GroupBy(g => new
        {
            g.property1,
            g.property2               
        })
        .Select(g => new T1
        {
            property1 = g.Key.property1,
            property2 = g.Key.property2,                
            BoolProperty =  g.GroupBy(grp => grp.BoolProperty).Count() > 1 ? null : g.Select(g_val=>g_val.BoolProperty).First()
        }).ToList();

This can all be done by linq, but that would propbably never be the most efficient way because you either need to enumerate more than once or other shenenigans. Sometimes best approach ist plain old code.

public static bool? CantFindAgoodName(IEnumerable<bool?> items)
{ 
     bool? check = null;
     bool first = true;
     foreach(var item in items)
     {
         if(first)
         {
            check = item;
            first = false;
         }
         else if(check != item) 
            return null;
     }
     return check;
}

Then just write

BoolProperty = CantFindAGoodName(g)

If you really wanna do it with standard Linq, it can be done with a single iteration, but its neither efficient (because it doesnt break imediately) nor beatuiful...

BoolProperty = g.Aggregate(-1,
              (n, b) => n == -1
                ? (b == null ? 0 : (b.Value ? 1 : 2))
                : (n == (b == null ? 0 : (b.Value ? 1 : 2)) ? n : -2), 
              i => i == -2 ? default(bool?) : i == 1);

So how does it work?

-1  = seed
 0  = null
 1  = true
 2  = false
-2  = different values aka null

While the value is either the first or equals its predecessor, it keeps getting forwareded. So if the sequence consists purely of the same element, that will get forwarded. If its different however, -2 will get forwarded, which will allways be different to any other element remaining. In the end the second lambda just transforms the result back into a bool?

You can use

var allTrue = t2.All(x=>x.BoolProperty == true);
var allFalse = t2.All(x=>x.BoolProperty == false);
var anyNull = t2.Any(x=>x.BoolProperty.HasValue == false);

before grouping.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM