Apply SelectMany to three or more sequences - c#

What is the best way to apply SelectMany to get a cross join of three or more sequences using only extension methods? Is there any other way to get a cross join?
Test Data
var a = Enumerable.Range(11, 2);
var b = Enumerable.Range(21, 2);
var c = Enumerable.Range(31, 2);
Expected Result
X Y Z
11 21 31
11 21 32
11 22 31
11 22 32
12 21 31
12 21 32
12 22 31
12 22 32
What I tried
Here's the code that works but I wonder if there's any alternative that'd be easier to read and understand:
var d = a
.SelectMany(rb => b
.SelectMany(rc => c, (y, z) => new { Y = y, Z = z}),
(x, yz) => new { X = x, Y = yz.Y, Z = yz.Z });
The equivalent query expression is good but not what I'm looking for:
var e = from x in a
from y in b
from z in c
select new { X = x, Y = y, Z = z };

You can simplify (even if not much) your SelectMany query in this way:
var res = a.SelectMany(X => b.SelectMany(Y => c.Select(Z => new { X, Y, Z })));

You could use a Join that projects keys that always match.
var e = a.Join(b, x => true, y => true, (x, y) => new { A = x, B = y })
.Join(c, x => true, y => true, (x, y) => new { x.A, x.B, C = y });
Admittedly, it's probably less efficient than your SelectMany version.

I am still not sure if it's ok to make a custom method. But here it is anyway:
public static IEnumerable<IEnumerable<T>> CrossJoin<T>(params IEnumerable<T>[] sequences)
{
IEnumerable<IEnumerable<T>> result = new[] { Enumerable.Empty<T>() };
foreach (var sequence in sequences)
{
result = result.SelectMany(i => sequence.Select(s => i.Concat(new[] { s })));
}
return result;
}
If you add such a method, then the code that matters will become very readable:
var d = CrossJoin(
Enumerable.Range(11, 2),
Enumerable.Range(21, 2),
Enumerable.Range(31, 2)
);
Result:
Console.WriteLine("X Y Z");
foreach( var item in d ) {
Console.WriteLine(String.Join( ",", item ));
}
/*
X Y Z
11,21,31
11,21,32
11,22,31
11,22,32
12,21,31
12,21,32
12,22,31
12,22,32
*/

Related

LINQ - Join both lists by last digit

A sequence of positive integers integerList1 and integerList2 are given. All values in each sequence are different.
Get a set (list of NumberPair values) of all value pairs that satisfy the following conditions:
the first element of the pair belongs to the sequence integerList1,
the second belongs to
integerList2
both elements end with the same digit.
The NumberPair type includes
Value 1, Value 2 fields.
The resulting NumberPair list must be sorted in ascending order
by the first field, and if they are equal, by the second.
Here is an example:
integerList1: new[] { 1, 12, 4, 5, 78 }
integerList2: new[] { 1, 42, 75, 65, 8, 97 }
Expected result:
expected: new[]
{
new NumberPair{Item1 = 1, Item2 = 1},
new NumberPair{Item1 = 5, Item2 = 65},
new NumberPair{Item1 = 5, Item2 = 75},
new NumberPair{Item1 = 12, Item2 = 42},
new NumberPair{Item1 = 78, Item2 = 8}
}
I tried to solve like this
var lastDigitsGroups1 = integerList1.GroupBy(num => num % 10).ToDictionary(kvp => kvp.Key, kvp => kvp.ToList());
var lastDigitsGroups2 = integerList2.GroupBy(num => num % 10).ToDictionary(kvp => kvp.Key, kvp => kvp.ToList());
var intersection = lastDigitsGroups1.Keys.Intersect(lastDigitsGroups2.Keys);
foreach (var item in intersection)
{
var np = new NumberPair { Item1 = lastDigitsGroups1[item].FirstOrDefault(), Item2 = lastDigitsGroups2[item].FirstOrDefault() };
yield return np;
}
However, it should be done only using LINQ and even with one LINQ query.
Join both lists by keys as below:
var result = (from a in integerList1
join b in integerList2 on (a % 10) equals (b % 10)
select new NumberPair { Item1 = a, Item2 = b }
)
.OrderBy(x => x.Item1)
.ThenBy(x => x.Item2)
.ToList();
Or
var result = integerList1.Join(integerList2,
x => x % 10,
y => y % 10,
(x, y) => new { x, y })
.Select(x => new NumberPair { Item1 = x.x, Item2 = x.y })
.OrderBy(x => x.Item1)
.ThenBy(x => x.Item2)
.ToList();
Demo # .NET Fiddle
I honestly don't understand your approach, it does not seem to do what you have mentioned in your conditions. If i just take them as requirement, use Enumerable.Zip:
var result = integerList1.Zip(integerList2, (i1, i2) => new NumberPair{Item1 = i1, Item2 = i2} )
.OrderBy(np => np.Item1)
.ThenBy(np => np.Item2);

Multiply column major matrix linq

As you can see, multiply is pretty easy for row major matrices.
But in my case i have column major matrix, how can i multiply column major matrices using linq?
Row major matrix
double[][] M1 = { {a1, b1}, {a2, b2}, {a3, b3} };
double[] M2 = { a, b };
double[] M3 = M1.Select(inner => inner.Zip(M2, (x, y) => x*y).Sum()).ToArray();
Column major matrix
double[][] M1 = {{a1,a2,a3},{b1,b2,b3}};
double[] M2 = { a, b };
double[] M3 = ?;
These are expected results. (Sorry too lazy for writing a sample).
A = a1*a + b1*b;
B = a2*a + b2*b;
C = a3*a + b3*b;
I missed groupby solution.
double[] M3 = M1.SelectMany(inner => inner.Select((x, i) => (x, i))
.GroupBy(t => t.i, t => t.x, (t, e) =>
e.Zip(M2, (x, y) => x * y).Sum()))
.ToArray();

LINQ group by sequence and count with sorting

I am searching a best performance method to group and count sequences with sorting using LINQ. I will be processing files even bigger than 500 MBs so performance is most important key in that task.
List<int[]> num2 = new List<int[]>();
num2.Add(new int[] { 35, 44 });
num2.Add(new int[] { 200, 22 });
num2.Add(new int[] { 35, 33 });
num2.Add(new int[] { 35, 44 });
num2.Add(new int[] { 3967, 11 });
num2.Add(new int[] { 200, 22 });
num2.Add(new int[] { 200, 2 });
The result have to be like this:
[35, 44] => 2
[200, 22] => 2
[35, 33] => 1
[35, 44] => 1
[3967, 11] => 1
[200, 2 ] => 1
I have done something like this:
Dictionary<int[], int> result2 = (from i in num2
group i by i into g
orderby g.Count() descending
select new { Key = g.Key, Freq = g.Count() })
.ToDictionary(x => x.Key, x => x.Freq);
SetRichTextBox("\n\n Second grouping\n");
foreach (var i in result2)
{
SetRichTextBox("\nKey: ");
foreach (var r in i.Key)
{
SetRichTextBox(r.ToString() + " ");
}
SetRichTextBox("\n Value: " + i.Value.ToString());
}
But it is not working properly. Any help?
For arrays of length 2, this will work.
num2.GroupBy(a => a[0])
.Select(g => new { A0 = g.Key, A1 = g.GroupBy(a => a[1]) })
.SelectMany(a => a.A1.Select(a1 => new { Pair = new int[] { a.A0, a1.Key }, Count = a1.Count() }));
I think that should give you optimal performance; you could also try an .AsParallel() clause after your first Select statement.
This strategy (grouping successively by the n-th element of the arrays) generalises to arrays of arbitrary length:
var dim = 2;
var tuples = num2.GroupBy(a => a[0])
.Select(g => new Tuple<int[], List<int[]>>(new [] { g.Count(), g.Key }, g.Select(a => a.Skip(1).ToArray()).ToList()));
for (int n = 1; n < dim; n++)
{
tuples = tuples.SelectMany(t => t.Item2.GroupBy(list => list[0])
.Select(g => new Tuple<int[], List<int[]>>(new[] { g.Count() }.Concat(t.Item1.Skip(1)).Concat(new [] { g.Key }).ToArray(), g.Select(a => a.Skip(1).ToArray()).ToList())));
}
var output = tuples.Select(t => new { Arr = string.Join(",", t.Item1.Skip(1)), Count = t.Item1[0] })
.OrderByDescending(o => o.Count)
.ToList();
which generates an output of
Arr = "35, 44", Count = 2
Arr = "200, 22", Count = 2
Arr = "35, 33", Count = 1
Arr = "200, 2", Count = 1
Arr = "3967, 11", Count = 1
in your example. I'll let you test it for higher dimensions. :)
You should be able to parallelise these queries without too much difficulties, as the successive groupings are independent.
You can do something like this:
var results = from x in nums
group x by new { a = x[0], b = x[1] } into g
orderby g.Count() descending
select new
{
Key = g.Key,
Count = g.Count()
};
foreach (var result in results)
Console.WriteLine(String.Format("[{0},{1}]=>{2}", result.Key.a, result.Key.b, result.Count));
The trick is to come up with a way to compare the values in the array, instead of the arrays themselves.
The alternative (and possibly better option) would be to transform your data from int[] to some custom type, override the equality operator on that custom type, then just group x by x into g, but if you're really stuck with int[] then this works.

Nice, clean cross join in Linq using only extension methods [duplicate]

This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
Nested “from” LINQ query expressed with extension methods
I'm sure this has been asked before, but I honestly couldn't find anything.
I'm curious what the equivalent syntax would be for the following using only built-in Linq extension methods:
var z1 =
from x in xs
from y in ys
select new { x, y };
I can get the same results with this:
var z2 = xs.SelectMany(x => ys.Select(y => new { x, y }));
But it produces different IL code, and the code is a bit convoluted and hard to understand. Is there a cleaner way to do this with extension methods?
Here's my entire test method as written:
private void Test()
{
var xs = new[] { 1D, 2D, 3D };
var ys = new[] { 4D, 5D, 6D };
var z1 =
from x in xs
from y in ys
select new { x, y };
var z2 = xs.SelectMany(x => ys.Select(y => new { x, y }));
}
Here's the [Edit: C# interp of the] IL code (using ILSpy):
private void Test()
{
double[] xs = new double[]
{
1.0,
2.0,
3.0
};
double[] ys = new double[]
{
4.0,
5.0,
6.0
};
var z =
from x in xs
from y in ys
select new
{
x = x,
y = y
};
var z2 = xs.SelectMany((double x) =>
from y in ys
select new
{
x = x,
y = y
});
}
One way would be:
var z2 = xs.SelectMany(x => ys, (x, y) => new {x, y});
If you really want to use a single LINQ extension method, then another candidate would be Join, with the outerKeySelector and innerKeySelector functions defined such that they will always produce equal values.
var z3 = xs.Join(ys, x => true, y => true, (x, y) => new { x, y });
This will, however, probably give more convoluted IL code than the nested from solution. Incidentally, MSDN uses the nested from in its example for a cross join; look at the first code snippet in How to: Perform Custom Join Operations (C# Programming Guide).

Easy way to GroupBySubElement on a collection in LINQ?

I have a normal GroupBy operation on an enumerable:
e.GroupBy(i => i.Property)
But if i.Property is really a collection, how would I break apart the collection and use the list's elements as grouping keys?
For example let's say I have two objects (Z, Y) that each have a list:
Z: { List = { A, B, C }}
Y: { List = { B, C, D }}
Now running the GroupBySubelement(o => o.List) would not group by the list itself, but would iterate over the list and generate the following Groupings.
{A, {Z}}
{B, {Z, Y}}
{C, {Z, Y}}
{D, {Y}
Is this possible?
Thanks!
Here's some example code that achieves what you want:
//This is just temporary data. Has the similar structure to what you want.
var parts = new[]
{
new
{
Name = "X",
Property = new[] {'A', 'B', 'C'}
},
new
{
Name = "Y",
Property = new[] {'B', 'C', 'D'}
},
new
{
Name = "Z",
Property = new char[] { }
}
};
var groupedBySub = from part in parts
from sub in part.Property
group part by sub;
foreach(var group in groupedBySub)
{
Console.WriteLine("{0} - {1}", group.Key, string.Join(", ", group.Select(x => x.Name)));
}
Which outputs:
A - X
B - X, Y
C - X, Y
D - Y
You can also achieve this in the method chain fashion:
var groupedBySub = parts.SelectMany(part => part.Property, (part, sub) => new {part, sub}).GroupBy(t => t.sub, t => t.part);
If you want to capture it with the list being empty:
var groupedBySub = from part in parts
from sub in part.Property.DefaultIfEmpty()
group part by sub;
Which when substituted for the code above, gives output:
A - X
B - X, Y
C - X, Y
D - Y
- Z
This would do:
var combinations = e.SelectMany(i => i.List.Select(x => new { x, i }));
var groups = combinations.GroupBy(c => c.x, c => c.i);
Part of the problem here is that you don't have a good data structure:
var z = new List<T>(); // I'm using T here, so let's pretend this is in a generic method
var y = new List<T>();
// add a bunch of stuff
There isn't really any algorithm that can get you what you want, because the variables Z and Y are not really known to the data structure, just the comiler.
But what if you had a data structure like this:
var allOfTheLists = new Dictionary<T, List<T>>();
You could then break it out using something like this:
var explodedList = allOfTheLists.SelectMany((pair) => pair.Value.Select((item) => new { pair.Key, item}));
var grouping = explodedList.GroupBy((explodedItem) => explodedItem.item);

Categories

Resources