Reputation: 3820
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
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