Created
August 22, 2015 01:36
-
-
Save ldub/48a4e8bf02b03b4e91b1 to your computer and use it in GitHub Desktop.
Unzip and Transpose in C# LINQ
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Transpose does this | |
// {1, 2, 3, 4} {1, 5, 9} | |
// {5, 6, 7, 8} ==> {2, 6, 10} | |
// {9,10,11,12} {3, 7, 11} | |
// {4, 8, 12} | |
void Main() | |
{ | |
List<List<int>> list = new List<List<int>>() { | |
new List<int>() {1, 2, 3, 4}, | |
new List<int>() {5, 6, 7, 8}, | |
new List<int>() {9,10,11,12} | |
}; | |
//meant to be executed in LINQPad | |
list.Dump(); | |
Transpose1(list).Dump(); | |
Transpose2(list).Dump(); | |
Transpose3(list).Dump(); | |
} | |
//recursive solution | |
IEnumerable<IEnumerable<T>> Transpose1<T>(IEnumerable<IEnumerable<T>> list) { | |
return | |
list.First().Any() | |
// generate list of heads of lists | 'concat' it with the rest of the matrix transposed | |
? (new []{list.Select(x => x.First())}).Concat(Transpose1(list.Select(x => x.Skip(1)))) | |
: (IEnumerable<IEnumerable<T>>) new List<List<T>>(); | |
} | |
//The above in haskell: | |
// transpose1 :: [[a]] -> [[a]] | |
// transpose1 ([]:xs) = [] | |
// transpose1 xs = (map head xs) : transpose (map tail xs) | |
//abuses the fact that Select gives us the element alongside it's index | |
IEnumerable<IEnumerable<T>> Transpose2<T>(IEnumerable<IEnumerable<T>> list) { | |
return list | |
//maps to list of elems and their indices in original list [(element, index)] | |
.SelectMany(x => x.Select((e, i) => Tuple.Create(e, i))) | |
//groups tuples by their second element, their index | |
.GroupBy(x => x.Item2) | |
//this line is unnecessary but GroupBy doesn't guarantee an ordering | |
.OrderBy(x => x.Key) | |
//remove indices | |
.Select(x => x.Select(y => y.Item1)); | |
} | |
//don't need the element alongside it's index. We can pre-generate the indices | |
IEnumerable<IEnumerable<T>> Transpose3<T>(IEnumerable<IEnumerable<T>> list) { | |
return | |
//generate the list of top-level indices of transposed list | |
Enumerable.Range(0, list.First().Count()) | |
//selects elements at list[y][x] for each x, for each y | |
.Select(x => list.Select(y => y.ElementAt(x))); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Unzip does this | |
// [(1,2),(3,4),(5,6)] => ([1,3,5], [2,4,6]) | |
// Converts a list of tuples into a tuple of lists | |
// Type signature in haskell would be [(a, b)] => ([a], [b]) | |
void Main() { | |
List<Tuple<int,int>> listOfTuples = new List<Tuple<int,int>>() { | |
Tuple.Create(1,2), | |
Tuple.Create(3,4), | |
Tuple.Create(5,6) | |
}; | |
//meant to be executed in LINQPad | |
listOfTuples.Dump(); | |
Unzip1(listOfTuples).Dump(); | |
Unzip2(listOfTuples).Dump(); | |
} | |
Tuple<List<int>, List<int>> Unzip1(List<Tuple<int,int>> list) { | |
return Foldr( | |
(Tuple<int,int> x, Tuple<List<int>,List<int>> y) | |
=> Tuple.Create((new []{x.Item1}).Concat(y.Item1).ToList(), (new []{x.Item2}).Concat(y.Item2).ToList()) | |
, Tuple.Create(new List<int>(), new List<int>()) | |
, list); | |
} | |
Tuple<List<int>, List<int>> Unzip2(List<Tuple<int,int>> list) { | |
return Tuple.Create(list.Select(x => x.Item1).ToList(), list.Select(x => x.Item2).ToList()); | |
} | |
//Not possible to define this in-line using the Func foldr = ... => ... notation | |
//see http://stackoverflow.com/questions/2404993/is-it-possible-to-define-a-generic-lambda | |
//foldr :: (a -> b -> b) -> b -> [a] -> b | |
TResult Foldr<TArg, TResult>(Func<TArg, TResult, TResult> f, TResult acc, IEnumerable<TArg> en) { | |
return en.Any() ? f(en.First(), Foldr(f, acc, en.Skip(1))) : acc; | |
} | |
//however, C# 6 would allow for | |
//TResult Foldr<TArg, TResult>(Func<TArg, TResult, TResult> func, TResult b, IEnumerable<TArg> en) => en.Any() ? func(en.First(), Foldr(func, b, en.Skip(1))) : b; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hey man, it's been 7 years, but these are pretty cool! Especially liked the first, recursive solution, although it might not be as readable and declarative as the second one.