【问题标题】:Using linq to find best match across multiple properties使用 linq 跨多个属性查找最佳匹配
【发布时间】:2015-09-15 20:52:37
【问题描述】:

我正在尝试使用 linq 在一组属性的自定义对象列表中找到最佳匹配。在下面创建的 MyObject 列表中,我想在 MyObject 的所有四个属性中找到与 testObject 最匹配的一个:

IList<MyObject> list = new List<MyObject>();

list.Add(new MyObject { Property1 = "A", Property2 = "B", Property3 = "C", Property4 = "D" });
list.Add(new MyObject { Property1 = "A", Property2 = "A", Property3 = "C", Property4 = "D" });
list.Add(new MyObject { Property1 = "A", Property2 = "A", Property3 = "A", Property4 = "D" });


var testObject = new MyObject { Property1 = "A", Property2 = "A", Property3 = "A", Property4 = "A" };

在上面的示例中,我希望将最后一个对象匹配为 3 或 4 个属性与 testObject 中的属性匹配。

我可以通过这样做计算出有多少个属性匹配:

 var matchCount = list.Max(x => (x.Property1 == testObject.Property1 ? 1 : 0) +
        (x.Property2 == testObject.Property2 ? 1 : 0) +
        (x.Property3 == testObject.Property3 ? 1 : 0) +
        (x.Property4 == testObject.Property4 ? 1 : 0));

但我不知道如何选择与三个属性匹配的实体,除了写出一个非常长的 linq 表达式来检查每个属性组合上的 3 个匹配项。理想情况下,我想要一个能够优雅地适用于具有 10 个属性的对象的解决方案。

有谁知道是否有一种可接受的方式来做到这一点?

编辑

我从原始问题中遗漏的另一条信息...如果匹配的对象超过 1 个,那么我需要选择与该精度级别匹配的对象列表(即,如果有一个对象匹配 3 个属性,那么我需要找到匹配 3 个属性的所有对象)

解决方案

根据树懒的回答,我已经能够使用它得到我想要的东西。不过,我很想看看是否有人对此有更简洁的答案...

var grouping = list.GroupBy(x => (x.Property1 == testObject.Property1 ? 1 : 0) +
(x.Property2 == testObject.Property2 ? 1 : 0) +
(x.Property3 == testObject.Property3 ? 1 : 0) +
(x.Property4 == testObject.Property4 ? 1 : 0));

var maxCount = grouping.Max(x => x.Key);
var resultSet = grouping.FirstOrDefault(x => x.Key == maxCount).Select(g => g).ToList();

【问题讨论】:

    标签: c# .net performance linq filtering


    【解决方案1】:

    你也可以试试这个

        IList<MyObject> list = new List<MyObject>();
    
        list.Add(new MyObject { Property1 = "A", Property2 = "B", Property3 = "C", Property4 = "D" });
        list.Add(new MyObject { Property1 = "A", Property2 = "A", Property3 = "C", Property4 = "D" });
        list.Add(new MyObject { Property1 = "A", Property2 = "A", Property3 = "A", Property4 = "D" });
    
    
        var testObject = new MyObject { Property1 = "A", Property2 = "A", Property3 = "A", Property4 = "A" };
    
    //list of objects with 3 or matches
        var sorted = list.Select(x => new
        {
            MatchCount = (x.Property1 == testObject.Property1 ? 1 : 0)
                        + (x.Property2 == testObject.Property2 ? 1 : 0)
                        + (x.Property3 == testObject.Property3 ? 1 : 0)
                        + (x.Property4 == testObject.Property4 ? 1 : 0),
            MyObj = x
        })
        .OrderBy( x => x.MatchCount)
        .Where( x => x.MatchCount >= 3 );
    
    //gets the first object from the list
        var match = sorted.Any() ? sorted.OrderBy(x => x.MatchCount).FirstOrDefault().MyObj : null;
    

    【讨论】:

      【解决方案2】:

      你可以使用一些好的旧反射:

      // get all get methods of all public properties
      var getter = typeof(MyObject).GetProperties().Select(prop => prop.GetMethod).ToList();
      
      // sort by number of matches
      var result = list.OrderBy(l => getter.Count(a => a.Invoke(l, null).Equals(a.Invoke(testObject, null)))).LastOrDefault();
      

      不是最快的方法,而是简单的。


      回应您的评论:

      只需使用GroupBy

      var grouped = list.GroupBy(l => getter.Count(a => a.Invoke(l, null).Equals(a.Invoke(testObject, null))))
                        .OrderBy(grp => grp.Key)
                        .LastOrDefault();
      

      grouped 现在包含所有具有最佳匹配的项目。

      【讨论】:

      • 谢谢懒惰。这将解决存在一个最佳匹配的情况下的问题。我已经更新了我的问题,解释说我还需要处理具有相同准确性的多个匹配的情况,我不确定这个方法是否可以扩展到涵盖该用例。
      • 查看我的编辑。只需将OrderBy 替换为GroupBy,然后再进行订购。
      【解决方案3】:

      Eric Matson 的回答很好,但对于 SOLID 而言,propertyMatches 和 linq 查询的拆分实现可能更好,并为您提供更具可读性的查询。

      另外,您可以直接从 linq 查询返回 MyObject。

      using System;
      using System.Linq;
      using System.Collections.Generic;
      
      namespace ConsoleApplication1
      {
        public class Program
      {
          public static void Main()
      
          {
              IList<MyObject> list = new List<MyObject>();
      
              list.Add(new MyObject { P1 = "A", P2 = "B", P3 = "C", P4 = "D" });
              list.Add(new MyObject { P1 = "A", P2 = "A", P3 = "C", P4 = "D" });
              list.Add(new MyObject { P1 = "A", P2 = "A", P3 = "A", P4 = "D" });
      
              var testObject = new MyObject { P1 = "A", P2 = "A", P3 = "A", P4 = "A" };
      
             //create a list of annonymous class with inner MyObject and propertyMatches count, filter it by matches and return its inner MyObject
             //I think this query is easy to read, returs what you want (list of MyObject) in one line and can be applied without changes to any class as long implements propertyMatches.
              var res = from ao in (from obj in list select new { obj = obj, matches = obj.propertyMatches(testObject) }) where ao.matches >= 3 select ao.obj;
      
            //same query in method call form
            var res2 = list.Select(o => new
                {
                   matches = o.propertyMatches(testObject),
                   obj = o
                }).Where(ao => ao.matches >= 3).Select(ao => ao.obj);
      
              Console.WriteLine(res.First().P1);
              Console.WriteLine(res.First().P2);
              Console.WriteLine(res.First().P3);
              Console.WriteLine(res.First().P4);
      
              Console.WriteLine(res2.First().P1);
              Console.WriteLine(res2.First().P2);
              Console.WriteLine(res2.First().P3);
              Console.WriteLine(res2.First().P4);
      
          }
      }
      

      propertyMatches 可以是 MyObject 基类中的抽象方法,也可以是接口方法或扩展方法或任何您需要的方法,取决于您的设计和架构。

       public static class oMyExtensions
              {
                  public static int propertyMatches(this MyObject o, MyObject otherObj)
                  {
                      return (o.P1 == otherObj.P1 ? 1 : 0)
                      + (o.P2 == otherObj.P2 ? 1 : 0)
                      + (o.P3 == otherObj.P3 ? 1 : 0)
                      + (o.P4 == otherObj.P4 ? 1 : 0);
      
                  }
              }
      

      完整示例Here

      【讨论】:

        猜你喜欢
        • 2011-08-01
        • 1970-01-01
        • 2020-04-12
        • 1970-01-01
        • 2020-02-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多