Blog Arolla

Cartesian products in LINQ (fluent syntax)

Have you ever tried to combine sequences in order to build Cartesian products in LINQ ? This is really easily achieved using the query expressions syntax, writing for instance :

var ints = Enumerable.Range(1, 4);
var longs = Enumerable.Range(1, 3).Select(i => (long)i);
var products = from i in ints
    from l in longs
    select i * l;

When I write LINQ queries, I tend to use as often as possible the fluent syntax, as it is more powerful, and allows me to use a wider range of operators. I also try not to mix both syntaxes, because I find it quite confusing. When I want to perform, inside of a more complex query, a simple cross-product as the previous one, the “fluent” equivalent syntax is the following :

var products = ints.SelectMany(i => longs.Select(l => i * l));

In fact, this syntax works very well, but I don’t find it as expressive as the first one. And if you go one step further and add a third sequence to the party…

var ints = Enumerable.Range(1, 4);
var longs = Enumerable.Range(1, 3).Select(i => (long)i);
var strings = new[] { "a", "b", "c" };
var classic = from i in ints
    from l in longs
    from s in strings
    select (i * l).ToString() + s;

The “fluent” syntax gets even more confusing (at least to me) :

var fluent = ints.SelectMany(
    i => longs.SelectMany(
    l => strings.Select(s => (i * l).ToString() + s)));

I think that what bothers me might be the ever-increasing number of unclosed parenthesis… Anyway, as usual, I played with extensions methods and here is what I came up with, using a new “Cross” method, for the 2 sequences cross-product :

var results = ints
    .Cross(longs)
    .Select((i, l) => i * l);

And the same syntax for the 3 sequences cross product :

var results = ints
    .Cross(longs)
    .Cross(strings)
    .Select((i, l, s) => (i * l).ToString() + s);

Don’t you find it more expressive ?

Finally, here are the extensions methods implementations :

public static IEnumerable<Tuple<TLeft, TRight>> Cross<TLeft, TRight>(
    this IEnumerable<TLeft> left, IEnumerable<TRight> right)
{
    return left.SelectMany(l => right.Select(r => Tuple.Create(l, r)));
}
public static IEnumerable<Tuple<TLeft1, TLeft2, TRight>> Cross<TLeft1, TLeft2, TRight>(
    this IEnumerable<Tuple<TLeft1, TLeft2>> left, IEnumerable<TRight> right)
{
    return left.SelectMany(l => right.Select(r => Tuple.Create(l.Item1, l.Item2, r)));
}
public static IEnumerable<TResult> Select<T1, T2, TResult>(
    this IEnumerable<Tuple<T1, T2>> source, Func<T1, T2, TResult> selector)
{
    return source.Select(t => selector(t.Item1, t.Item2));
}
public static IEnumerable<TResult> Select<T1, T2, T3, TResult>(
    this IEnumerable<Tuple<T1, T2, T3>> source, Func<T1, T2, T3, TResult> selector)
{
    return source.Select(t => selector(t.Item1, t.Item2, t.Item3));
}

I must admit that those generic methods signatures are horrible, though…

Site Web | Plus de publications

Contributeur enthousiaste

Comments are closed.