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

时间:2015-06-29 11:46:50

标签: c# .net performance linq filtering

我正在尝试使用linq在一组属性中的自定义对象列表中找到最佳匹配。在下面创建的MyObjects列表中,我希望在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个属性的对象的解决方案。

有没有人知道是否有可以接受的方法吗?

修改

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

溶液

根据Sloths的回答,我已经能够得到我想要的东西了。我有兴趣看看是否有人对此有更简洁的回答......

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 个答案:

答案 0 :(得分:1)

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

GroupBy

不是紧固方法,而是简单。

回应你的评论:

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

grouped

@Override public void run() { int imageHeight = filter.getBitmap().getHeight(); int start = threadNumber * imageHeight / threadCount; int stop = (threadNumber + 1) * imageHeight / threadCount; for (int j = start; j < stop; j++) { filter.processLine(j); } } //... protected void processLine(int lineNumber) { int width = bitmap.getWidth(); int pixels[] = new int[width]; bitmap.getPixels(pixels, 0, width, 0, lineNumber, width, 1); for (int i = 0; i < width; i++) { pixels[i] = processPixel(pixels[i]); } bitmap.setPixels(pixels, 0, width, 0, lineNumber, width, 1); } 现在包含所有匹配最佳的项目。

答案 1 :(得分: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 :(得分:0)

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