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