LINQ and extending it with cross product

Linq to object is really useful in a lot of places around my daily code. This afternoon I have to setup some routine to do crossproduct of some objects. With the term cross product I mean having two set, one made by elements of type X and the other made by elements of type Y, all that I need is creating a new set that contains an element of type Z from every combination of these two set. As an example I want to write this simple piece of code.

1
2
3
4
Int32[] first = new Int32[] {1, 2, 3, 4};
Int32[] second = new Int32[] { 10, 20, 30, 40 };
foreach (Int32 num in first.CrossProduct(second, (l, r) => l * r))
    Console.WriteLine(num);

And have this output

1
10 20 30 40 20 40 60 80 30 60 90 120 40 80 120 160

But I need also to express conditions on both the sets, because I want to exclude some elements, so I want to be able to write also this code.

1
2
3
4
Int32[] first = new Int32[] { 1, 2, 3, 4 };
Int32[] second = new Int32[] { 10, 20, 30, 40 };
foreach (Int32 num in first.CrossProduct(second, f => f > 2, s => s < 30, (l, r) => l * r))
    Console.Write(num + " ");

and have it output 30 60 40 80. As you can see I setup a condition for the first set to take elements greater than 2 and for the second set I want to take only elements that are less than thirty.

Here is my implementation, I did it in few minutes

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
public static IEnumerable<K> CrossProduct<T, U, K>(
    this IEnumerable<T> left,
    IEnumerable<U> right,
    Func<T, U, K> selector)
{
    return new CrossProductImpl<T, U, K>(left, right, selector);
}

public static IEnumerable<K> CrossProduct<T, U, K>(
    this IEnumerable<T> left,
    IEnumerable<U> right,
    Func<T, Boolean> leftFilter,
    Func<U, Boolean> rightFilter,
    Func<T, U, K> selector)
{
    return new CrossProductImpl<T, U, K>(left, right, leftFilter, rightFilter, selector);
}

Both the extension methods return a CrossProductImpl class, here is the full code.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class CrossProductImpl<T, U, K> : IEnumerable<K>
{
    private Func<T, U, K> selector;
    private IEnumerable<T> left;
    private IEnumerable<U> right;
    private Func<T, Boolean> leftFilter = e => true;
    private Func<U, Boolean> rightFilter = e => true;

    public CrossProductImpl(IEnumerable<T> left, IEnumerable<U> right, Func<T, U, K> selector)
    {
        this.selector = selector;
        this.left = left;
        this.right = right;
    }

    public CrossProductImpl(IEnumerable<T> left, IEnumerable<U> right, Func<T, bool> leftFilter, Func<U, bool> rightFilter, Func<T, U, K> selector)
    {
        this.selector = selector;
        this.left = left;
        this.right = right;
        this.leftFilter = leftFilter;
        this.rightFilter = rightFilter;
    }

    #region IEnumerable<K> Members

    public IEnumerator<K> GetEnumerator()
    {
        foreach (T leftElement in left.Where(leftFilter))
            foreach (U rightElement in right.Where(rightFilter))
                yield return selector(leftElement, rightElement);
    }

    #endregion

    #region IEnumerable Members

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    #endregion
}

This technique is used to make lazy evaluation, all the code is really executed only when the caller iterates on the result of the method, this feature is known as *deferred execution.*To verify it you can execute this piece of code

1
2
3
4
5
6
7
8
9
Int32[] first = new Int32[] { 1, 2, 3, 4 };
Int32[] second = new Int32[] { 10, 20, 30, 40 };
IEnumerable<Int32> result = first.CrossProduct(second, f => f > 2, s => s < 30, (l, r) => l*r);
foreach (Int32 num in result)
    Console.Write(num + " ");
Console.WriteLine();
first[0] = 10;
foreach (Int32 num in result)
    Console.Write(num + " ");

And the output shows that each time I iterate in result, the code gets executed again.

1
2
30 60 40 80 
100 200 30 60 40 80 

I added also a couple of overload extension methods that instead of accepting a Func<T, U, K> as the last argument accepts an Action<T, U> and returns void. This helps me in situation when I do not need to generate another set, but I’m interested only in knowing all the permutation of the two original set.

alk.

Tags: LINQ, Cross Product