How to get a Lookup as Union of 2 old Lookups?
If you have the original lists that the lookups came from, it might be easier. It might also be easier if you used Dictionary
s of Lists
instead of lookups. However, it's still possible to merge two lookup objects into a new object. The basic idea is to retrieve the original values from the lookups, and then create a new lookup from the concatenated set of both.
var a = new[] {"apple","aardvark","barn"};
var b = new[] {"baboon", "candy", "cork"};
var al = a.ToLookup (x => x[0]);
var bl = b.ToLookup (x => x[0]);
var cl = al.Concat(bl).SelectMany(x => x).ToLookup(x => x[0]);
If you also don't know the original key selector function, you can use this variant.
var cl = al.Concat(bl)
.SelectMany(lookup => lookup.Select(value => new { lookup.Key, value}))
.ToLookup(x => x.Key, x => x.value);
I wrote this extension method, which takes advantage of the fact that an ILookup<TK,TV>
is an IEnumerable<IGrouping<TK,TV>>
public static ILookup<TK, TV> Union<TK, TV>(this ILookup<TK, TV> self, IEnumerable<IGrouping<TK,TV>> moreGroupings)
{
return self.Concat(moreGroupings)
.SelectMany(grouping => grouping.Select(val => new KeyValuePair<TK, TV>(grouping.Key, val)))
.ToLookup(kvp => kvp.Key, kvp => kvp.Value);
}
Here are a few tests that demonstrate. The lookups here contain strings, keyed by their lengths.
[TestMethod]
public void EmptyLookups_UnionReturnsEmpty()
{
var a = new string[] { }.ToLookup(x => x.Length, x => x);
var b = new string[] { }.ToLookup(x => x.Length, x => x);
var c = a.Union(b);
Assert.AreEqual(0, c.Count);
c = b.Union(a);
Assert.AreEqual(0, c.Count);
}
[TestMethod]
public void OneEmptyLookup_UnionReturnsContentsOfTheOther()
{
var a = new string[] { }.ToLookup(x => x.Length, x => x);
var b = new string[] { "hello", "world" }.ToLookup(x => x.Length, x => x);
var c = a.Union(b);
Assert.AreEqual(1, c.Count);
Assert.AreEqual("helloworld", string.Join("", c[5].OrderBy(x=>x)));
c = b.Union(a);
Assert.AreEqual(1, c.Count);
Assert.AreEqual("helloworld", string.Join("", c[5].OrderBy(x=>x)));
}
[TestMethod]
public void UniqueKeys_UnionAdds()
{
var a = new string[] { "cat", "frog", "elephant"}.ToLookup(x => x.Length, x => x);
var b = new string[] { "hello", "world" }.ToLookup(x => x.Length, x => x);
var c = a.Union(b);
Assert.AreEqual(4, c.Count);
Assert.AreEqual("cat", string.Join("", c[3]));
Assert.AreEqual("frog", string.Join("", c[4]));
Assert.AreEqual("elephant", string.Join("", c[8]));
Assert.AreEqual("helloworld", string.Join("", c[5].OrderBy(x=>x)));
}
[TestMethod]
public void OverlappingKeys_UnionMerges()
{
var a = new string[] { "cat", "frog", "horse", "elephant"}.ToLookup(x => x.Length, x => x);
var b = new string[] { "hello", "world" }.ToLookup(x => x.Length, x => x);
var c = a.Union(b);
Assert.AreEqual(4, c.Count);
Assert.AreEqual("cat", string.Join("", c[3]));
Assert.AreEqual("frog", string.Join("", c[4]));
Assert.AreEqual("elephant", string.Join("", c[8]));
Assert.AreEqual("hellohorseworld", string.Join("", c[5].OrderBy(x=>x)));
}
I also happen to need to handle case-insensitive strings, so I have this overload that takes a custom comparer.
public static ILookup<TK, TV> Union<TK, TV>(this ILookup<TK, TV> self, IEnumerable<IGrouping<TK,TV>> moreGroupings, IEqualityComparer<TK> comparer)
{
return self.Concat(moreGroupings)
.SelectMany(grouping => grouping.Select(val => new KeyValuePair<TK, TV>(grouping.Key, val)))
.ToLookup(kvp => kvp.Key, kvp => kvp.Value, comparer);
}
The lookups in these examples use the first letter as a key:
[TestMethod]
public void OverlappingKeys_CaseInsensitiveUnionAdds()
{
var a = new string[] { "cat", "frog", "HORSE", "elephant"}.ToLookup(x => x.Substring(0,1), x => x);
var b = new string[] { "hello", "world" }.ToLookup(x => x.Substring(0,1), x => x);
var c = a.Union(b, StringComparer.InvariantCulture);
Assert.AreEqual(6, c.Count);
Assert.AreEqual("cat", string.Join("", c["c"]));
Assert.AreEqual("frog", string.Join("", c["f"]));
Assert.AreEqual("elephant", string.Join("", c["e"]));
Assert.AreEqual("hello", string.Join("", c["h"].OrderBy(x=>x)));
Assert.AreEqual("HORSE", string.Join("", c["H"].OrderBy(x=>x)));
Assert.AreEqual("world", string.Join("", c["w"]));
}
[TestMethod]
public void OverlappingKeys_CaseSensitiveUnionMerges()
{
var a = new string[] { "cat", "frog", "HORSE", "elephant"}.ToLookup(x => x.Substring(0,1), x => x);
var b = new string[] { "hello", "world" }.ToLookup(x => x.Substring(0,1), x => x);
var c = a.Union(b, StringComparer.InvariantCultureIgnoreCase);
Assert.AreEqual(5, c.Count);
Assert.AreEqual("cat", string.Join("", c["c"]));
Assert.AreEqual("frog", string.Join("", c["f"]));
Assert.AreEqual("elephant", string.Join("", c["e"]));
Assert.AreEqual("helloHORSE", string.Join("", c["h"].OrderBy(x=>x)));
Assert.AreEqual("helloHORSE", string.Join("", c["H"].OrderBy(x=>x)));
Assert.AreEqual("world", string.Join("", c["w"]));
}