简体   繁体   English

Linq OrderBy用于对具有相同集合的对象进行分组

[英]Linq OrderBy to group objects with the same sets

I have a set of objects that themselves contain a set. 我有一组自己包含一组的对象。

private class Pilot
{
    public string Name;
    public HashSet<string> Skills;
}

Here's some test data: 这是一些测试数据:

public void TestSetComparison()
{
    var pilots = new[] 
    {
        new Pilot { Name = "Smith", Skills = new HashSet<string>(new[] { "B-52", "F-14" }) },
        new Pilot { Name = "Higgins", Skills = new HashSet<string>(new[] { "Concorde", "F-14" }) },
        new Pilot { Name = "Jones", Skills = new HashSet<string>(new[] { "F-14", "B-52" }) },
        new Pilot { Name = "Wilson", Skills = new HashSet<string>(new[] { "F-14", "Concorde" }) },
        new Pilot { Name = "Celko", Skills = new HashSet<string>(new[] { "Piper Cub" }) },
    };

I want to use OrderBy in Linq so that: 我想在Linq中使用OrderBy ,以便:

  • Smith and Jones are ordered together because they fly the same planes 史密斯和琼斯一起订购是因为他们乘坐的飞机相同
  • Higgins and Wilson are ordered together because they fly the same planes 希金斯和威尔逊是一起订购的,因为他们在同一架飞机上飞行
  • doesn't matter whether Higgins+Wilson end up before or after Smith+Jones 希金斯+威尔逊是否在史密斯+琼斯之前或之后结束并不重要
  • preferably Smith is before Jones (stable sort) but this isn't too important 最好是史密斯在琼斯之前(稳定排序),但这不是太重要

I think I need to implement an IComparer<Pilot> to pass into OrderBy but don't know how to handle the "doesn't matter" aspect (above) and the stable sort. 我想我需要实现一个IComparer<Pilot>来传递给OrderBy但不知道如何处理“无关紧要”方面(上面)和稳定排序。

UPDATE: 更新:

I want the output to be an array of the same five Pilot objects but in a different order . 我希望输出是相同的五个 Pilot对象的数组 ,但顺序不同

When GroupBy you have to implement IEqualityComparer<T> for the type you are going to group ( HashSet<string> in your case), eg GroupBy你必须为要分组的类型(在你的情况下为HashSet<string> )实现IEqualityComparer<T> ,例如

private sealed class MyComparer : IEqualityComparer<HashSet<string>> {
  public bool Equals(HashSet<string> x, HashSet<string> y) {
    if (object.ReferenceEquals(x, y))
      return true;
    else if (null == x || null == y)
      return false;

    return x.SetEquals(y);
  }

  public int GetHashCode(HashSet<string> obj) {
    return obj == null ? -1 : obj.Count;
  }
}

And then use it: 然后使用它:

 IEnumerable<Pilot> result = pilots
    .GroupBy(pilot => pilot.Skills, new MyComparer())
    .Select(chunk => string.Join(", ", chunk
       .Select(item => item.Name)
       .OrderBy(name => name))); // drop OrderBy if you want stable Smith, Jones

 Console.WriteLine(string.Join(Environment.NewLine, result));

Outcome: 结果:

 Jones, Smith
 Higgins, Wilson
 Celko

Edit: If you want an array reodered then add SelectMany() in order to flatten the groupings and the final ToArray() : 编辑:如果要重新编码数组,请添加SelectMany()展平分组和最终的ToArray()

 var result = pilots
    .GroupBy(pilot => pilot.Skills, new MyComparer())
    .SelectMany(chunk => chunk)
    .ToArray();

 Console.WriteLine(string.Join(", ", result.Select(p => p.Name)));

Outcome: 结果:

 Jones, Smith,
 Higgins, Wilson,
 Celko

Note that string.join combines the names of each group in one line, ie Jones, Smith both have the same skill set. 请注意,string.join将每个组的名称组合在一行中,即Jones, Smith都具有相同的技能组合。

Run it as DotNetFiddle 将其作为DotNetFiddle运行

You can use the following implementation of HashSetByItemsComparer to accomplish what you need: 您可以使用HashSetByItemsComparer的以下实现来完成您的需要:

public class HashSetByItemsComparer<TItem> : IComparer<HashSet<TItem>>
{
    private readonly IComparer<TItem> _itemComparer;

    public HashSetByItemsComparer(IComparer<TItem> itemComparer)
    {
        _itemComparer = itemComparer;
    }

    public int Compare(HashSet<TItem> x, HashSet<TItem> y)
    {
        foreach (var orderedItemPair in Enumerable.Zip(
            x.OrderBy(item => item, _itemComparer),
            y.OrderBy(item => item, _itemComparer), 
            (a, b) => (a, b))) //C# 7 syntax used - Tuples
        {
            var itemCompareResult = _itemComparer.Compare(orderedItemPair.a, orderedItemPair.b);
            if (itemCompareResult != 0)
            {
                return itemCompareResult;
            }
        }

        return 0;
    }
}

It is not the most efficient solution probably, since it orders hashsets for each comparison separately. 它可能不是最有效的解决方案,因为它分别为每个比较命令哈希集。 You may need to optimize it if using with millions of pilots and many skills, but for small numbers it will work just fine. 如果使用数百万的飞行员和许多技能,您可能需要对其进行优化,但对于小数字,它可以正常工作。

Usage example: 用法示例:

var sortedPilots = pilots.OrderBy(p => p.Skills, new HashSetByItemsComparer<string>(StringComparer.Ordinal));

foreach (var pilot in sortedPilots)
{
    Console.WriteLine(pilot.Name);
}

And output is: 输出是:

Smith
Jones
Higgins
Wilson
Celko

So it preserves equal items ordering (default behavior of OrderBy - you don't need to worry about it). 因此它保留了相同的项目排序OrderBy默认行为 - 您不必担心它)。 By the way, solution with GroupBy would not allow you to restore items order as far as I know. 顺便说一下,就我所知, GroupBy解决方案不允许你恢复项目顺序。

Here is a sample with orderby 这是一个带有orderby的示例

  var groups = from c in GridImage (Your List)
               orderby c.grouping (Your Item inside List)
               group c by c.grouping;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM