Skip to content

Instantly share code, notes, and snippets.

@ldub
Created August 22, 2015 01:36
Show Gist options
  • Save ldub/48a4e8bf02b03b4e91b1 to your computer and use it in GitHub Desktop.
Save ldub/48a4e8bf02b03b4e91b1 to your computer and use it in GitHub Desktop.
Unzip and Transpose in C# LINQ
// 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)));
}
// 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;
@borisStanojevic
Copy link

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment