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

时间:2018-04-10 08:50:32

标签: c# linq

我有一组对象本身包含一组。

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

以下是一些测试数据:

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" }) },
    };

我想在Linq中使用OrderBy,以便:

  • 史密斯和琼斯一起订购,因为他们驾驶同样的飞机
  • 希金斯和威尔逊一起被命令因为他们驾驶同样的飞机
  • 无论Higgins + Wilson是在Smith + Jones之前还是之后结束
  • 最好史密斯在琼斯之前(稳定排序),但这不太重要

我认为我需要实现IComparer<Pilot>传递给OrderBy,但不知道如何处理“无关紧要”方面(上图)和稳定排序。

更新

我希望输出为相同的五个 Pilot个对象的数组,但是不同的顺序

3 个答案:

答案 0 :(得分:7)

GroupBy你必须为你要分组的类型IEqualityComparer<T>实施HashSet<string>时,例如。

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;
  }
}

然后使用它:

 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));

结果:

 Jones, Smith
 Higgins, Wilson
 Celko

修改:如果您想要重新设置数组,请添加SelectMany()以便展平分组和最终{{ 1}}:

ToArray()

结果:

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

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

请注意,string.join将每个组的名称组合在一行中,即 Jones, Smith, Higgins, Wilson, Celko 都具有相同的技能组合。

将其作为 DotNetFiddle

运行

答案 1 :(得分:1)

您可以使用 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;
    }
}

它可能不是最有效的解决方案,因为它分别为每个比较命令哈希集。如果使用数百万飞行员和许多技能,您可能需要对其进行优化,但对于小数字,它可以正常工作。

用法示例:

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

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

输出是:

Smith
Jones
Higgins
Wilson
Celko

所以它会保留相同的项目排序OrderBy的默认行为 - 您无需担心)。顺便说一下,GroupBy的解决方案不允许你根据我的知识恢复项目顺序。

答案 2 :(得分:0)

以下是orderby

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