【问题标题】:Filter Linq EXCEPT on properties过滤 Linq 除了属性
【发布时间】:2013-03-21 06:31:14
【问题描述】:

这可能看起来很愚蠢,但我发现的所有在 linq 中使用 Except 的示例都使用两个仅包含字符串或整数的列表或数组,并根据匹配项对其进行过滤,例如:

var excludes = users.Except(matches);

我想使用 exclude 来保持我的代码简洁明了,但似乎不知道如何执行以下操作:

class AppMeta
{
    public int Id { get; set; }
}

var excludedAppIds = new List<int> {2, 3, 5, 6};
var unfilteredApps = new List<AppMeta>
                         {
                           new AppMeta {Id = 1},
                           new AppMeta {Id = 2},
                           new AppMeta {Id = 3},
                           new AppMeta {Id = 4},
                           new AppMeta {Id = 5}
                         }

我如何获得在excludedAppIds 上过滤的AppMeta 列表?

【问题讨论】:

    标签: c# linq


    【解决方案1】:

    尝试一个简单的 where 查询

    var filtered = unfilteredApps.Where(i => !excludedAppIds.Contains(i.Id)); 
    

    except 方法使用相等,您的列表包含不同类型的对象,因此它们包含的所有项目都不会相等!

    【讨论】:

    • 为了提高效率,建议将excludedAppIds 存储为HashSet,否则您有一个O(N²) 算法,该算法遍历您的排除列表的次数与源中的元素一样多。
    • @NigelTouch 这很有帮助!你能再扩展一下吗?否则,大 O 效率会是什么?因此,不是算法遍历排除列表的次数与列表中被过滤的元素一样多,会发生什么?当您过滤 IQueryable 时会发生什么?
    • @nmit026 哈希集使用元素的哈希码进行树搜索。因此在搜索匹配项时不必迭代集合。结果是 O(N * hash depth) aka O(N)
    • @NigelTouch 为了实现您的想法,excludedAppIds 必须覆盖 EqualsGetHashCode 方法,但是,OP 的问题使用“积木”类型这一事实并不意味着强制性的。即使是这样,Linq 的Distinct() 也比HashSet 更可取。
    【解决方案2】:

    ColinE 的回答简单而优雅。如果您的列表较大并且排除的应用程序列表已排序,BinarySearch&lt;T&gt; 可能会比Contains 更快。

    示例:

    unfilteredApps.Where(i => excludedAppIds.BinarySearch(i.Id) < 0);
    

    【讨论】:

    • 这很有帮助,谢谢。在这种情况下,它们既不大,也没有排序排除列表,但我会将其归档并发送支持您的方向。
    • !(a &gt;= b) 是一种有趣的说法 a&lt;b ;)
    【解决方案3】:

    我使用了Except 的扩展方法,它允许您将Apples 与Oranges 进行比较,只要它们都具有可用于比较它们的共同点,例如Id 或Key。

    public static class ExtensionMethods
    {
        public static IEnumerable<TA> Except<TA, TB, TK>(
            this IEnumerable<TA> a,
            IEnumerable<TB> b,
            Func<TA, TK> selectKeyA,
            Func<TB, TK> selectKeyB, 
            IEqualityComparer<TK> comparer = null)
        {
            return a.Where(aItem => !b.Select(bItem => selectKeyB(bItem)).Contains(selectKeyA(aItem), comparer));
        }
    }
    

    然后像这样使用它:

    var filteredApps = unfilteredApps.Except(excludedAppIds, a => a.Id, b => b);
    

    该扩展与 ColinE 的答案非常相似,只是打包成一个简洁的扩展,可以重复使用而无需太多的精神开销。

    【讨论】:

      【解决方案4】:

      这是 LINQ 需要的

      public static IEnumerable<T> Except<T, TKey>(this IEnumerable<T> items, IEnumerable<T> other, Func<T, TKey> getKey) 
      {
          return from item in items
                  join otherItem in other on getKey(item)
                  equals getKey(otherItem) into tempItems
                  from temp in tempItems.DefaultIfEmpty()
                  where ReferenceEquals(null, temp) || temp.Equals(default(T))
                  select item; 
      }
      

      【讨论】:

      • 那将是一个很好的扩展方法
      • 这很好,但我不确定 join 是排除匹配项的最有效方法,而且双方使用相同的 getKey,这不适用于原始问题。
      • “状态”未定义
      【解决方案5】:

      从排除列表中构造一个List&lt;AppMeta&gt; 并使用Except Linq 运算符。

      var ex = excludedAppIds.Select(x => new AppMeta{Id = x}).ToList();                           
      var result = ex.Except(unfilteredApps).ToList();
      

      【讨论】:

      • 此解决方案允许大型数据集 +1
      • 占用太多内存和时间。
      【解决方案6】:

      我喜欢 except 扩展方法,但原始问题没有对称密钥访问权限,我更喜欢包含(或任何变体)加入,因此感谢 azuneca's answer

      public static IEnumerable<T> Except<T, TKey>(this IEnumerable<TKey> items,
          IEnumerable<T> other, Func<T, TKey> getKey) {
      
          return from item in items
              where !other.Contains(getKey(item))
              select item;
      }
      

      然后可以像这样使用:

      var filteredApps = unfilteredApps.Except(excludedAppIds, ua => ua.Id);
      

      此外,此版本允许使用 Select 映射异常 IEnumerable:

      var filteredApps = unfilteredApps.Except(excludedApps.Select(a => a.Id), ua => ua.Id);
      

      【讨论】:

        【解决方案7】:

        MoreLinq 有一些有用的东西 MoreLinq.Source.MoreEnumerable.ExceptBy

        https://github.com/gsscoder/morelinq/blob/master/MoreLinq/ExceptBy.cs

        namespace MoreLinq
        {
            using System;
            using System.Collections.Generic;
            using System.Linq;
        
            static partial class MoreEnumerable
            {
                /// <summary>
                /// Returns the set of elements in the first sequence which aren't
                /// in the second sequence, according to a given key selector.
                /// </summary>
                /// <remarks>
                /// This is a set operation; if multiple elements in <paramref name="first"/> have
                /// equal keys, only the first such element is returned.
                /// This operator uses deferred execution and streams the results, although
                /// a set of keys from <paramref name="second"/> is immediately selected and retained.
                /// </remarks>
                /// <typeparam name="TSource">The type of the elements in the input sequences.</typeparam>
                /// <typeparam name="TKey">The type of the key returned by <paramref name="keySelector"/>.</typeparam>
                /// <param name="first">The sequence of potentially included elements.</param>
                /// <param name="second">The sequence of elements whose keys may prevent elements in
                /// <paramref name="first"/> from being returned.</param>
                /// <param name="keySelector">The mapping from source element to key.</param>
                /// <returns>A sequence of elements from <paramref name="first"/> whose key was not also a key for
                /// any element in <paramref name="second"/>.</returns>
        
                public static IEnumerable<TSource> ExceptBy<TSource, TKey>(this IEnumerable<TSource> first,
                    IEnumerable<TSource> second,
                    Func<TSource, TKey> keySelector)
                {
                    return ExceptBy(first, second, keySelector, null);
                }
        
                /// <summary>
                /// Returns the set of elements in the first sequence which aren't
                /// in the second sequence, according to a given key selector.
                /// </summary>
                /// <remarks>
                /// This is a set operation; if multiple elements in <paramref name="first"/> have
                /// equal keys, only the first such element is returned.
                /// This operator uses deferred execution and streams the results, although
                /// a set of keys from <paramref name="second"/> is immediately selected and retained.
                /// </remarks>
                /// <typeparam name="TSource">The type of the elements in the input sequences.</typeparam>
                /// <typeparam name="TKey">The type of the key returned by <paramref name="keySelector"/>.</typeparam>
                /// <param name="first">The sequence of potentially included elements.</param>
                /// <param name="second">The sequence of elements whose keys may prevent elements in
                /// <paramref name="first"/> from being returned.</param>
                /// <param name="keySelector">The mapping from source element to key.</param>
                /// <param name="keyComparer">The equality comparer to use to determine whether or not keys are equal.
                /// If null, the default equality comparer for <c>TSource</c> is used.</param>
                /// <returns>A sequence of elements from <paramref name="first"/> whose key was not also a key for
                /// any element in <paramref name="second"/>.</returns>
        
                public static IEnumerable<TSource> ExceptBy<TSource, TKey>(this IEnumerable<TSource> first,
                    IEnumerable<TSource> second,
                    Func<TSource, TKey> keySelector,
                    IEqualityComparer<TKey> keyComparer)
                {
                    if (first == null) throw new ArgumentNullException("first");
                    if (second == null) throw new ArgumentNullException("second");
                    if (keySelector == null) throw new ArgumentNullException("keySelector");
                    return ExceptByImpl(first, second, keySelector, keyComparer);
                }
        
                private static IEnumerable<TSource> ExceptByImpl<TSource, TKey>(this IEnumerable<TSource> first,
                    IEnumerable<TSource> second,
                    Func<TSource, TKey> keySelector,
                    IEqualityComparer<TKey> keyComparer)
                {
                    var keys = new HashSet<TKey>(second.Select(keySelector), keyComparer);
                    foreach (var element in first)
                    {
                        var key = keySelector(element);
                        if (keys.Contains(key))
                        {
                            continue;
                        }
                        yield return element;
                        keys.Add(key);
                    }
                }
            }
        }
        

        【讨论】:

          【解决方案8】:
          public static class ExceptByProperty
          {
              public static List<T> ExceptBYProperty<T, TProperty>(this List<T> list, List<T> list2, Expression<Func<T, TProperty>> propertyLambda)
              {
                  Type type = typeof(T);
          
                  MemberExpression member = propertyLambda.Body as MemberExpression;
          
                  if (member == null)
                      throw new ArgumentException(string.Format(
                          "Expression '{0}' refers to a method, not a property.",
                          propertyLambda.ToString()));
          
                  PropertyInfo propInfo = member.Member as PropertyInfo;
                  if (propInfo == null)
                      throw new ArgumentException(string.Format(
                          "Expression '{0}' refers to a field, not a property.",
                          propertyLambda.ToString()));
          
                  if (type != propInfo.ReflectedType &&
                      !type.IsSubclassOf(propInfo.ReflectedType))
                      throw new ArgumentException(string.Format(
                          "Expresion '{0}' refers to a property that is not from type {1}.",
                          propertyLambda.ToString(),
                          type));
                  Func<T, TProperty> func = propertyLambda.Compile();
                  var ids = list2.Select<T, TProperty>(x => func(x)).ToArray();
                  return list.Where(i => !ids.Contains(((TProperty)propInfo.GetValue(i, null)))).ToList();
              }
          }
          
          public class testClass
          {
              public int ID { get; set; }
              public string Name { get; set; }
          }
          

          为了测试这个:

                  List<testClass> a = new List<testClass>();
                  List<testClass> b = new List<testClass>();
                  a.Add(new testClass() { ID = 1 });
                  a.Add(new testClass() { ID = 2 });
                  a.Add(new testClass() { ID = 3 });
                  a.Add(new testClass() { ID = 4 });
                  a.Add(new testClass() { ID = 5 });
          
                  b.Add(new testClass() { ID = 3 });
                  b.Add(new testClass() { ID = 5 });
                  a.Select<testClass, int>(x => x.ID);
          
                  var items = a.ExceptBYProperty(b, u => u.ID);
          

          【讨论】:

          • 反思应该永远是你最后的手段,这对于一个根本不需要它的简单任务来说是很多反思。
          猜你喜欢
          • 2021-04-22
          • 2014-11-20
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-12-28
          • 1970-01-01
          • 1970-01-01
          • 2020-12-09
          相关资源
          最近更新 更多