Kippie
Kippie

Reputation: 3820

LINQ Grouping by content of an array

Having some difficulty expanding my linq query.

Suppose I have the following data:

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

I'd like to flatten this array so I get this:

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

The code that I currently have is this

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

This query doesn't quite work yet, as it doesn't group on the item list, with a result similar to this:

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

Could anyone point me in the right direction for fixing this query? For this example, we may assume that items shared by vaults are always the same. (Meaning Vault 2 has to contain the exact same items as Vault 1)

Upvotes: 2

Views: 358

Answers (1)

Sergey Kalinichenko
Sergey Kalinichenko

Reputation: 726569

You can do it like this:

First, add GetHashCode and Equals to Item, like this:

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

Next, define a class that can compare sets of items for equality, like this:

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

Now your query can be formulated as follows:

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

Upvotes: 2

Related Questions