【问题标题】:C# linq query aggregate nullable booleanC# linq 查询聚合可为空的布尔值
【发布时间】:2023-03-17 23:45:01
【问题描述】:

我想使用 linq 按照逻辑聚合一个可为空的 bool:

  • 如果一切都是真的,那么就是真的;
  • 如果全部为假则为假;
  • 否则为空

这是我的代码,我无法获得 bool 聚合。

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();

所以 t1 将是 "hello", "world", null; 谢谢

【问题讨论】:

  • 我希望我可以选择多个答案。谢谢大家的帮助!

标签: c# linq boolean nullable


【解决方案1】:

这个怎么样?

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

【讨论】:

  • 这似乎有效....但我不确定为什么有人在没有 cmets 的情况下对此投反对票?
  • 是的,不知道为什么它也被否决了!如果它对您有用,请接受! :)
  • 我正在等待那个 cmets 同时做更多的测试
  • 我看到这种方法的一个缺点是All 必须遍历数据集两次。诚然,这不是一个巨大的缺点。
  • p.BoolProperty.HasValue &amp;&amp; !p.BoolProperty.Value 可以简化为!(p.BoolProperty ?? true)p.BoolProperty.HasValue &amp;&amp; p.BoolProperty.Valuep.BoolProperty ?? false
【解决方案2】:

把它放在你的代码中,

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();

【讨论】:

    【解决方案3】:

    我建议以下解决方案。它需要使用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();
    

    关键位是使用OrderBy,以确保truefalsenull的值是相邻的(即按顺序组合在一起)。

    然后TagFirstLast 允许我们忽略除第一个和最后一个条目之外的所有内容(这是我们所需要的)。

    然后我们检查第一个和最后一个条目是否相同 - 如果是,则使用该值(无论是 nulltrue 还是 false - 无论哪种方式都符合要求)。

    否则,请使用null(因为那将是您的else 条件)。

    【讨论】:

      【解决方案4】:

      这一切都可以通过 linq 完成,但这可能永远不会是最有效的方法,因为您需要多次枚举或其他恶作剧。有时最好的方法是简单的旧代码。

      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;
      }
      

      那就写吧

      BoolProperty = CantFindAGoodName(g)
      

      如果你真的想用标准的 Linq 来做,可以通过一次迭代来完成,但它既不高效(因为它不会立即中断)也不美观......

      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);
      

      那么它是如何工作的呢?

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

      虽然该值是第一个或等于其前任,但它会不断被转发。因此,如果序列完全由相同的元素组成,那将被转发。但是,如果它不同,则 -2 将被转发,这将始终与剩余的任何其他元素不同。 最后,第二个 lambda 只是将结果转换回 bool?

      【讨论】:

      • 优雅且使用更少的资源。哇,答案越来越好了
      • 那么如果输入列表为空、假、假、假怎么办?它将返回 False,而预期的返回为空。如果我错了,请纠正我....谢谢
      • @Heisenberg 你错了。为什么不直接测试一下...dotnetfiddle.net/1tS3sv
      • 我正在测试函数 CantFindAGoodName,它返回 false....dotnetfiddle.net/aqQpqy
      【解决方案5】:

      你可以使用

      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);
      

      分组前。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-05-22
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多