【问题标题】:Check if all items in a Collection have the same value检查集合中的所有项目是否具有相同的值
【发布时间】:2010-09-10 19:14:33
【问题描述】:

一个名为 MeasurementCollection 的集合上的扩展方法检查每个项目的属性 Template.Frequency (Enum) 是否具有相同的值。

public static bool IsQuantized(this MeasurementCollection items)
{
    return  (from i in items 
             select i.Template.Frequency)
            .Distinct()
            .Count() == 1;
}

编辑 有关底层类的信息

MeasurementCollection : ICollection<IMeasurement>

IMeasurement 
{
    IMeasurementTemplate Template { get; }        
    ......
}

这是一种正确的方法,还是在 Linq 中已有更简单的解决方案? 此方法将在应用程序中大量使用。

你有什么小窍门要跟我一起回到绘图板上吗?

【问题讨论】:

  • 您对可读性的要求是什么?速度重要吗?通常的情况是什么。它们是相同的还是不同的?
  • 根据您在此问题上添加的#arrays 标签,我们是否可以假设您的MeasurementCollection 对象存储为数组或ArrayList?我们能否从 MeasurementCollection 中获得 .Count 而不会产生太多开销?哪个答案是正确的将取决于对您的基础收藏有更多了解。
  • 为什么您不想将此方法(带有任何实现)添加到 MeasurementCollection 中?你为什么要为此使用扩展方法?如果此功能是此特定抽象的一部分,请明确显示。
  • @Sergey Template 是Measurement 的成员但不是MeasurementCollection 的成员,只有在这种方法的情况下,两个类都满足。通过在 MeasurementCollection 类中实现此方法,我会将它们永远耦合在一起,而 IMO 会造成一团糟。 (对不起,我不能在这里提供更多代码;)

标签: c# linq arrays extension-methods


【解决方案1】:

您可以只找到第一个值并检查其他值是否不同,这将避免评估整个集合(除非单个不同的值是最后一个)

public static bool IsQuantized(this MeasurementCollection items)
{
    if(!items.Any())
        return false; //or true depending on your use case

    //might want to check that Template is not null, a bit a violation of level of demeter, but just an example
    var firstProp = items.First().Template.Frequency;

    return !items.Any(x=> x.Template.Frequency != firstProp);

}

【讨论】:

    【解决方案2】:

    编辑:解决 Timwi 对 3 个枚举器的担忧:

    bool same = <your default> ;
    var first = items.FirstOrDefault();
    if (first != null)  // assuming it's a class
    {
       same = items.Skip(1).All(i => i.Template.Frequency == first.Template.Frequency); 
    }
    

    仍然使用 2 个枚举器。对于普通的List&lt;&gt; 来说不是问题,但对于数据库查询,使用可读性较差的可能会有所帮助:

    bool same = <your default> ;
    Item first = null;
    
    foreach(var item in items)
    {
        if (first == null)
        {
            first = item;
            same = true;
        }
        else
        {
            if (item.Template.Frequency != first.Template.Frequency)
            {
               same = false;
               break;
            }
        }
    }
    

    【讨论】:

    • 您的代码与 OP 的比较不同。他在比较一个属性的属性。你的正在做一个参考比较
    • @Rune:为了完整起见,我将添加它,但这不是根本区别。除非某些属性为 null,否则会造成混乱。
    • 这与my second answer 中的Trick #2 相同。它实例化两个枚举器并对第一个项目进行两次评估——如果你不想让它抛出,你需要第三个来检查Any()。如果集合是惰性的并且第一个元素需要一段时间来计算,这可能是一个性能问题。
    • @Timwi 但从好的方面来说,这种方法更具可读性,因为它可以编写,因此几乎可以按照要求读取
    • @Henk:我认为他的问题是,在您的选项 1 中,您正在直接比较集合中的项目(通过 ref),但在选项 2 中,您正在按 item.Template 比较项目.Frequency...如果它们是类,您的第一个解决方案将始终说每个项目都是唯一的(因为它们是不同的引用)
    【解决方案3】:

    首先是一般的 linq 建议。如果您只是想知道集合中是否恰好有一个,请使用 Single() 或 SingleOrDefault()。 Count 可能会迭代整个集合,这超出了您的需要,因为如果有两个,您可以退出。

    public static bool IsQuantized(this MeasurementCollection items)
            {
                var first = items.FirstOrDefault();
                return first != null && items.Skip(1).All(i => first.Template.Frequency == i.Template.Frequency));
            }
    

    【讨论】:

    • 不是我的 -1,但您的代码似乎不能很好地处理空集合。而你是完美主义者。
    • @Henk 你是对的,所以我更新了我的答案。然而,我认为在大多数情况下会产生错误答案的实现评论是完美主义的。我也看不出赞扬你的一般方法的可读性是多么完美:)
    • 如果集合中的第一项是null失败
    【解决方案4】:

    我得到了一点灵感,想到了一个只考虑速度的解决方案。这确实不是那么可读(我通常更喜欢),但在速度方面的特性应该是相当不错的。

    对于大多数其他实现 O(n) 来说,最坏的情况是相同的,但这是极不可能的,因为它要求所有元素的前半部分相等 并且 后半部分都相等但不等于前半部分的值。 并且需要与线性搜索相同数量的比较。 在大多数其他情况下,第一个奇数位于随机位置,这需要的比较次数是线性比较的一半。 在值成对的情况下。所以 item[0] == item[1] and item[2] == item[3] and item[0] != item[2] (和类似)然后线性搜索会更快。 一般来说,无论是随机数据还是少数奇数,这应该比线性搜索更快

    public static bool AllSame<T>(this IEnumerable<T> source,
                                  IEqualityComparer<T> comparer = null)
            {
                if (source == null)
                    throw new ArgumentNullException("source cannot be null.", "source");
    
                if (comparer == null)
                    comparer = EqualityComparer<T>.Default;
                var enumerator = source.GetEnumerator();
    
                return source.Zip(comparer);
            }
    
            private static bool Zip<T>(this IEnumerable<T> sequence, IEqualityComparer<T> comparer)
            {
                var result = new List<T>();
                var enumerator = sequence.GetEnumerator();
                while (enumerator.MoveNext())
                {
                    var first = enumerator.Current;
                    result.Add(enumerator.Current);
                    if (enumerator.MoveNext())
                    {
                        if (!comparer.Equals(first, enumerator.Current))
                        {
                           return false;
                        }
                    }
                    else
                    {
                        break;
                    }
                }
                return result.Count == 1 ? true : result.Zip(comparer);
            }
    

    如果没有尾调用优化,这会使用额外的内存(最坏的情况是内存量接近原始源使用的内存量)。调用堆栈不应该深入,因为没有 IEnumerable 具体实现(至少我知道)可以容纳超过 int.MaxValue 元素。这需要最多 31 次递归。

    【讨论】:

      【解决方案5】:

      这样会更快:

      public static bool IsQuantized(this MeasurementCollection items)
      {
          if(items == null || items.Count == 0)
             return true;
      
          var valueToCompare = items.First().Template.Frequency;
      
          return items.All(i => i.Template.Frequency == valueToCompare);
      }
      

      它将在第一个项目的模板频率不同时返回 false,而在您的代码中,算法会传递整个集合。

      【讨论】:

      • 这假定集合是可索引的。
      • OP 在这个问题上放了一个#arrays 标签,这可能表明MeasurementCollection 是作为数组或类似结构实现的。
      • @Timwi:有一个 [arrays] 标签
      • ①当输入为null时,编辑后的版本返回true,从而掩盖了一个可能的bug。它应该抛出ArgumentNullException。 ②这里仍然假设集合有Count。诚然,顾名思义,它确实如此,但问题并没有说明这一点,并且问题询问了 LINQ 中的解决方案,该解决方案适用于纯枚举。
      【解决方案6】:

      我是这样做的:

      public static bool Same<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
          {
              var val = source.Select(keySelector).FirstOrDefault();
      
              return source.Select(keySelector).All(a => Object.Equals(a, val));
          }
      

      用途:

      ddlStatus.AppendDataBoundItems = true;
      ddlStatus.Items.Add(new ListItem("<Mixed>", "-1"));
      ddlStatus.DataSource = ctx.Status.OrderBy(s => s.AssetStatus).ToList();
      ddlStatus.DataTextField = "AssetStatus";
      ddlStatus.DataValueField = "id";
      ddlStatus.SelectedValue = Assets.Same(a => a.AssetStatusID) ? Assets.FirstOrDefault().AssetStatusID.ToString() : "-1";
      ddlStatus.DataBind();
      

      这是一个包含可用状态列表的下拉框。表单编辑多个资产。下拉列表需要知道资产是否都具有相同的价值。我的 Same 扩展程序就是这样做的。

      【讨论】:

        【解决方案7】:

        我建议以下解决方案:

        private static bool IsSameCollections(ICollection<> collection1, ICollection<> collection2)
                {
                  return collection1.Count == collection2.Count &&
             (collection1.Intersect(collection2).Count() == collection1.Count);
                }
        

        【讨论】:

          猜你喜欢
          • 2011-11-21
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-06-18
          • 1970-01-01
          • 1970-01-01
          • 2015-07-05
          • 1970-01-01
          相关资源
          最近更新 更多