使用Linq查找多个属性的最佳匹配

3

我正在尝试使用linq在一组属性中查找自定义对象列表中的最佳匹配项。在下面创建的MyObjects列表中,我想找到最接近testObject的对象,跨越MyObject的四个属性:

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个属性的对象。

有人知道是否有一种被认可的方法来做到这一点吗?

编辑

我错过了原问题中的一个额外信息......如果有多个对象匹配,则需要选择与该准确度相匹配的对象列表(即,如果有一个对象在3个属性上匹配,则我需要找到所有在3个属性上匹配的对象)

解决方案

根据Sloth的答案,我已经能够通过以下方式获得想要的结果。不过,如果有人有更好的答案,我很感兴趣...

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();
3个回答

2
你也可以尝试这个。
    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;

1
您可以使用一些很好的旧反射技术:
// 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现在包含了所有最佳匹配的项。


谢谢,Sloth。这将解决只有一个最佳匹配的情况下的问题。我已经更新了我的问题,解释了我还需要处理多个准确度相等的匹配项的情况,而且我不确定这种方法是否可以扩展来涵盖这种情况。 - undefined
看我的修改。只需将OrderBy替换为GroupBy,然后再进行排序。 - undefined

0

Eric Matson的答案不错,但是也许拆分propertyMatches和linq查询的实现更适合SOLID,并且可以获得更可读的查询。

此外,你可以直接从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);

            }
        }

完整的例子 这里


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接