Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get a Lookup as Union of 2 old Lookups?

Tags:

c#

.net

linq

How can you get a Lookup from a Union of 2 old Lookups? Seems like a = a.Union(b) does not work for them.

like image 220
myWallJSON Avatar asked Sep 18 '12 14:09

myWallJSON


2 Answers

If you have the original lists that the lookups came from, it might be easier. It might also be easier if you used Dictionarys 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);
like image 149
recursive Avatar answered Oct 13 '22 23:10

recursive


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"]));
    }
like image 2
solublefish Avatar answered Oct 14 '22 00:10

solublefish