PWC 110 › Transpose CSV File

This post is part of a series on Mohammad Anwar’s excellent Weekly Challenge, where hackers submit solutions in Perl, Raku, or any other language, to two different challenges every week. (It’s a lot of fun, if you’re into that sort of thing.)

The second task this week is simple: given a (simplified) comma-separated-value (CSV) file, transpose its rows and columns. For example:

\(
\begin{bmatrix}
A & B & C & D \\
1 & 2 & 3 & 4 \\
w & x & y & z
\end{bmatrix}^T
\Rightarrow
\begin{bmatrix}
A & 1 & w \\
B & 2 & x \\
C & 3 & y \\
D & 4 & z
\end{bmatrix}
\)

The challenge task does not actually refer to the input as CSV, so I’m using that term loosely, with simplified parsing to match the input specification. If more compliant parsing is needed, one could use the usual Text::CSV module in Perl, or ports in Raku.

There are a couple of ways to transpose files, which I’ll explore in Raku and Perl.

Perl

Since we’re only asked to output the result, we don’t need to store it. Solutions which work in-place or on a copy of an input array could be made to work, but we don’t need the added effort in this case.

The general algorithm for printing an array transposition is to loop with $col from 0 to the width of the original array, and then print the $col-th element of every row.

# Read input into an array of arrays
my @rows = map { chomp; [ split ',' ] } <>;

for my $col (0..$#{$rows[0]}) {
    say join ',', map { $_->[$col] } @rows
}

This runs in O(m×n) time, and uses O(m×n) total memory (importing the original array into @rows), which is the best we can do.

There is also the zip6 function from List::MoreUtils, but it only works on arrays, not array refs, so it would not be immediately helpful here.

Raku

Raku gives us a built-in zip operator ([Z]), which does just what we need.

sub MAIN( Str $file ) {
    ([Z] $file.IO.lines.map(*.split(','))).map(*.join(','))».say;
}

Here, we split() each input line on commas, and then [Z] (zip) that result, and our file is already transposed. We simply need to join() each of the resulting rows back up with commas, and output each string with say().

Both Perl and Raku solutions give the same result on the example input:

[pwc/rjt_110] challenge-110/⋯/raku %> cat ../perl/ch-2.csv
name,age,sex
Mohammad,45,m
Joe,20,m
Julie,35,f
Cristina,10,f
[pwc/rjt_110] challenge-110/⋯/raku %> ./ch-2.raku ../perl/ch-2.csv
name,Mohammad,Joe,Julie,Cristina
age,45,20,35,10
sex,m,m,f,f

Full Code

The full code for this week’s solutions is available on GitHub.

Leave a Reply

Your email address will not be published. Required fields are marked *