LINQ按数组内容分组

时间:2013-09-23 13:17:36

标签: c# arrays linq

扩展我的linq查询时遇到一些困难。

假设我有以下数据:

Vault      Items
-----      -----
1          100, 102
2          100, 102
3          101
4          101

我想扁平化这个数组,所以我明白了:

Items      Vaults
-----      ------
100, 102   1, 2
101        3, 4

我目前拥有的代码就是这个

using System;
using System.Linq;
using System.Collections.Generic;


class Program
{
    static void Main()
    {
        var Vaults = new[]
            {
                new Vault
                    {
                        Id = 1,
                        Contents = new Contents
                            {
                                Items = new[]
                                    {
                                        new Item
                                            {
                                                Id = 100,
                                                Number = "100"
                                            },
                                        new Item
                                            {
                                                Id = 102,
                                                Number = "102"
                                            }
                                    }
                            }
                    },
                new Vault
                    {
                        Id = 2,
                        Contents = new Contents
                            {
                                Items = new[]
                                    {
                                        new Item
                                            {
                                                Id = 100,
                                                Number = "100"
                                            },
                                        new Item
                                            {
                                                Id = 102,
                                                Number = "102"
                                            }
                                    }

                            }
                    },
                new Vault
                    {
                        Id = 3,
                        Contents = new Contents
                            {
                                Items = new[]
                                    {
                                        new Item
                                            {
                                                Id = 101,
                                                Number = "101"
                                            }
                                    }
                            }
                    },
                new Vault
                    {
                        Id = 4,
                        Contents = new Contents
                            {
                                Items = new[]
                                    {
                                        new Item
                                            {
                                                Id = 101,
                                                Number = "101"
                                            }
                                    }
                            }
                    }
            };


        var grouping = Vaults.SelectMany(
            c => c.Contents.Items.Select(s => new { Item = s, Vault = c }))
                                .GroupBy(o => o.Item.Id)
                                .Select(
                                    grouping1 =>
                                    new Tuple<Item, IEnumerable<Vault>>(
                                        grouping1.First().Item, grouping1.Select(o => o.Vault)));

        foreach (var tuple in grouping)
        {
            Console.WriteLine("Item(s) {0}: Vault {1}", tuple.Item1.Number,
                              String.Join(", ", tuple.Item2.Select(c => c.Id.ToString()).ToArray()));
        }
    }
}



public class Vault
{
    public long Id { get; set; }
    public Contents Contents { get; set; }
}

public class Contents
{
    public IEnumerable<Item> Items { get; set; }
}

public class Item
{
    public long Id { get; set; }
    public string Number { get; set; }
}

此查询尚未完成,因为它没有在项目列表上进行分组,结果与此类似:

Item       Vaults         //Note: this isn't the output I need
----       ------
100        1, 2
102        1, 2
101        3, 4

有人能指出我正确的方向来修复此查询吗? 对于此示例,我们可以假设保管库共享的项目始终相同。 (含义Vault 2 必须包含与Vault 1完全相同的项目)

1 个答案:

答案 0 :(得分:2)

你可以这样做:

首先,将GetHashCodeEquals添加到Item,如下所示:

public class Item {
    public long Id { get; set; }
    public string Number { get; set; }
    public override bool Equals(object obj) {
        if (obj == this) return true;
        var other = obj as Item;
        if (other == null) return false;
        return Id == other.Id;
    }
    public override int GetHashCode() {
        return Id.GetHashCode();
    }
}

接下来,定义一个可以比较项集的相等性的类,如下所示:

internal class MultipartKey<T> {
    private readonly HashSet<T> items;
    private readonly int hashCode;
    public MultipartKey(IEnumerable<T> items) {
        this.items = new HashSet<T>(items);
        hashCode = this.items.Where(i => i != null)
           .Aggregate(0, (p, v) => p*31 + v.GetHashCode());
    }
    public override int GetHashCode() {
        return hashCode;
    }
    public override bool Equals(object obj) {
        if (obj == this) return true;
        var other = obj as MultipartKey<T>;
        if (other == null) return false;
        return items.SetEquals(other.items);
    }
}

现在您的查询可以表达如下:

var grouping = Vaults
    .GroupBy(v => new MultipartKey<Item>(v.Contents.Items))
    .Select(g => new {
        Items = g.First().Contents.Items
    ,   Vaults = g.ToList()
    });
    foreach (var g in grouping) {
        Console.WriteLine("{0} ---- {1}"
        ,   string.Join(",", g.Items.Select(i=>i.Id))
        ,   string.Join(",", g.Vaults.Select(v => v.Id))
        );
    }