MatchUp and Federator LINQ Methods Tutorial

This tutorial aims to illustrate the usage of the MatchUp and Federate LINQ-style extension methods that are found in the DigitallyCreated.Utilities.Linq assembly. It assumes you know C# 3.0 and LINQ.

MatchUp

MatchUp is great when you've got two IEnumerable<T>s and you need to do a "diff" (ie. what is in both, what is in only the first, what is in only the second).

string[] strs1 = new[] { "A", "B", "C", };
string[] strs2 = new[] { "B", "C", "D", };

IEnumerable<MatchPair<string, string>> matchups = strs1.MatchUp(strs2);

//In first, but not in second ("A")
matchups.Where(m => m.IsFirstSet && m.IsSecondSet == false).Select(m => m.First);

//In second, but not in first ("D")
matchups.Where(m => m.IsSecondSet && m.IsFirstSet == false).Select(m => m.Second);

//In both ("B", "C")
matchups.Where(m => m.IsFirstSet && m.IsSecondSet).Select(m => m.First);

In the above case, MatchUp returns an IEnumerable of MatchPair<string, string>. Each MatchPair contains a match between an element from the first sequence (strs1) and an element from the second sequence (strs2). Here's a table that illustrates what MatchUp returns for the above example:

IEnumerable<MatchPair<string,string>>
First Second IsFirstSet IsSecondSet
A null True False
B B True True
C C True True
null D False True


You can do it for whole objects where you just want to match up on some custom condition (good for matching on a property on the object):

var objs1 = new[] { new { First = "A", Last = "1"}, new { First = "B", Last = "2"}, new { First = "C", Last = "3"}, };
var objs2 = new[] { new { First = "B", Last = "4"}, new { First = "C", Last = "5"}, new { First = "D", Last = "6"}, };

var matchups = objs1.MatchUp(objs2, (f, s) => f.First == s.First);

As you can see above, the second parameter passed into MatchUp is a comparison function that takes a "first" object and a "second" object and returns whether or not they match. In this case, we're comparing their .First property. Here's the matchup table for the above example:

IEnumerable<MatchPair<anonymous type,anonymous type>>
First Second IsFirstSet IsSecondSet
{ First = A, Last = 1 } null True False
{ First = B, Last = 2 } { First = B, Last = 4 } True True
{ First = C, Last = 3 } { First = C, Last = 5 } True True
null { First = D, Last = 6 } False True


With that custom comparison function you can use MatchUp to match up objects of different types, like so:

var objs1 = new[] { new { First = "A", Last = "1"}, new { First = "B", Last = "2"}, new { First = "C", Last = "3"}, };
string[] strs2 = new[] { "B", "C", "D", };

var matchups = objs1.MatchUp(strs2, (f, s) => f.First == s);

In the above example we're matching up that anonymous type against an array of strings. Here's the matchup table for that:

IEnumerable<MatchPair<anonymous type,string>>
First Second IsFirstSet IsSecondSet
{ First = A, Last = 1 } null True False
{ First = B, Last = 2 } B True True
{ First = C, Last = 3 } C True True
null D False True

Federator

The Federate and FederateWith LINQ extension methods perform a similar function to MatchUp, however, are able to do it with more than two sequences. This benefit is traded off with increased complexity, so if you only need to do a matchup between two sequences, use MatchUp. Also, if you need to match up between two different types, use MatchUp.

The name Federate (and the Federator) was used because the meaning of the word federate is "to cause to join into a league or union", which is what the Federator does (joins multiple sequences into a form where they are matched up and you can iterate over them together). Also, "the Federator" sounds cool (like Terminator, except for sequences?!)!

Here's an example similar to the MatchUp examples that illustrates why you should use MatchUp if you have two sequences:

var objs1 = new[] { new { First = "A", Last = "1"}, new { First = "B", Last = "2"}, new { First = "C", Last = "3"}, };
var objs2 = new[] { new { First = "B", Last = "4"}, new { First = "C", Last = "5"}, new { First = "D", Last = "6"}, };

var federated = objs2.Federate(objs2, p => p.First);

//In first but not second
(from g in federated
 where g.Any(p => p.SequenceIndex == 0) && !g.Any(p => p.SequenceIndex == 1)
 select g).Select(s => s.First().Item);
 
//In second but not first
(from g in federated
 where g.Any(p => p.SequenceIndex == 1) && !g.Any(p => p.SequenceIndex == 0)
 select g).Select(s => s.First().Item);
 
//In both
(from g in federated
 where g.Any(p => p.SequenceIndex == 0) && g.Any(p => p.SequenceIndex == 1)
 select g).Select(s => s.First().Item);

The above code uses the Federate extension method, which allows you to easily federate (match up) two sequences. The second parameter is a selector that selects the property on which to perform the matching (in this case the .First property). The Federate method (in this case) returns an IEnumerable of IGrouping<string, FederatedGroupItem<_anonymous type_>> (the IGrouping is because behind the scenes it uses GroupBy). The FederatedGroupItem contains the actual object from the sequence, and the sequence index number. The index number is zero-based, and is numbered in the order that you passed the sequences to the Federator. So for the above Federate call, objs1 is index 0, and objs2 is index 1. Here's an object dump view of the above Federator result:

FederatorOutput.gif

However, the main use case for the Federator is matching up more than two sequences. Here's a simple example of that:

string[] strs1 = new[] { "A", "B", "C", };
string[] strs2 = new[] { "B", "C", "D", };
string[] strs3 = new[] { "C", "D", "E", };

IEnumerable<IGrouping<string,FederatedGroupItem<string>>> result = strs1.FederateWith(strs2).FederateWith(strs3).Federate();

In the above example, the first FederateWith call returns an IFederable<string>, upon which we add another sequence to be federated by calling FederateWith (this time on the IFederable). Once we're done adding sequences to be federated, we call Federate (on the IFederable) and this returns us our expected IEnumerable of IGrouping. In fact, the two sequence federator method used in the first example (.Federate off IEnumerable) is nothing more than a utility method that simple calls FederateWith then Federate (on the returned IFederable).
This is the object dump for the above federation:

FederatorOutputThreeSeqs.gif

Last edited Mar 21, 2010 at 2:42 PM by dchambers, version 3

Comments

No comments yet.