Reverse columns while preserving shape

Introduction

Suppose you have a list of lists of integers (or any objects really, but let's stick to integers for simplicity). The lists may be of different lengths, and some of them may be empty. Let's write the lists in a tabular format:

[[ 1,   2,   3,   4,   5],
[ 6,   7],
[ 8,   9,  10,  11],
[],
[12,  13,  14],
[15,  16,  17,  18]]

This table has 5 vertical columns, containing the numbers 1, 6, 8, 12, 15, 2, 7, 9, 13, 16, 3, 10, 14, 17, 4, 11, 18, and 5. If we reverse each column, we obtain the lists 15, 12, 8, 6, 1, 16, 13, 9, 7, 2, 17, 14, 10, 3, 18, 11, 4, and 5. Let's plug those numbers back into the columns of the table while keeping the lengths of the rows the same as before:

[[15,  16,  17,  18,   5],
[12,  13],
[ 8,   9,  14,  11],
[],
[ 6,   7,  10],
[ 1,   2,   3,   4]]

Your task is to implement this operation.

Input and output

Your input is a list of lists of nonnegative integers, representing the rows. The rows may have different lengths, and some of them may be empty. There will always be at least one row. Your output is the result of reversing each column, as detailed above. Input and output may be in any reasonable format.

The lowest byte count in each language wins. Standard rules apply.

Test cases

[[]] -> [[]]
[[],[]] -> [[],[]]
[[8,5,1]] -> [[8,5,1]]
[[1,200],[0,3]] -> [[0,3],[1,200]]
[[],[3,9],[1],[]] -> [[],[1,9],[3],[]]
[[],[5,8,7],[0,6,5,7,1]] -> [[],[0,6,5],[5,8,7,7,1]]
[[1,8,5],[7,5,4],[],[1]] -> [[1,5,4],[7,8,5],[],[1]]
[[],[],[2],[],[31],[],[5],[],[],[],[7]] -> [[],[],[7],[],[5],[],[31],[],[],[],[2]]
[[1,10,100,1000],[2,20,200],[3,30],[4],[5,50,500],[6,60],[7]] -> [[7,60,500,1000],[6,50,200],[5,30],[4],[3,20,100],[2,10],[1]]
[[8,4],[3,0,4,8,1],[8],[0,8],[9,7,1,6],[3,8,1,9,5]] -> [[3,8],[9,7,1,9,5],[0],[8,8],[3,0,1,6],[8,4,4,8,1]]
[[3,9,3],[5],[1],[3,5],[9,0,6,2],[1,3],[4,9,2],[6,6,7,8,7]] -> [[6,6,7],[4],[1],[9,9],[3,3,2,8],[1,0],[5,5,6],[3,9,3,2,7]]
[[8,5,6],[3,5,2,4,9],[4,3,8,3,7],[6,1,1],[1,8,9,9],[9,1,2],[8,7]] -> [[8,7,2],[9,1,9,9,7],[1,8,1,3,9],[6,1,8],[4,3,2,4],[3,5,6],[8,5]]
[[2,4],[1,4],[0,8,7,3],[4,9,2,5],[2,8,0],[0,8,3],[7,3,1],[],[3,3,7,8]] -> [[3,3],[7,3],[0,8,7,8],[2,8,1,5],[4,9,3],[0,8,0],[1,4,2],[],[2,4,7,3]]
• May we pad the rows of the output with nulls? (e.g. [[1,9],[3],[2,4,5]] -> [[2,4],[3,null],[1,9,5]]) – ETHproductions Jan 28 '18 at 4:07
• @ETHproductions No, the output should contain only numbers. – Zgarb Jan 28 '18 at 8:24
• -1 because it is not general (not allow negative numbers , letters, string and all possible type as row element) + I don't like it (it seems unnecessary difficult) – user58988 Jan 30 '18 at 11:09

Jelly, 16 bytes

ḟṚṁṣj
z-ç€-ZFḟ-ṁ

How it works

z-ç€-ZFḟ-ṁ  Main link. Argument: M (matrix / 2D array)

z-          Zip the rows of M, using -1 as filler.
ç€-       Map the helper link over the result, with right argument -1.
Z      Zip the rows of the result.
F     Flatten the resulting matrix.
ḟ-   Filterfalse -1; remove all occurrences of -1.
ṁ  Mold; shape the result like M.

Left argument: A (row / 1D array). Right argument: -1

ḟ           Filterfalse; remove all occurrences of -1.
Ṛ          Reverse the resulting vector.
ṣ        Split A at occurrences of -1.
ṁ         Mold; shape the vector to the left like the 2D array to the right.
j       Join the resulting 2D array, separating by -1.
• Nice, the on the top line is very clever! (ḟṚṁṣj does ⁸ḟ⁹Ṛṁ⁸ṣ⁹¤j⁹ right?) otherwise I had this for one more byte – Erik the Outgolfer Jan 27 '18 at 16:11
• Yes, that's exactly what it does. – Dennis Jan 27 '18 at 16:25

Japt, 15 13 bytes

saved 2 bytes thanks to @Shaggy

y@=XfÊX£o
®fÄ

Test it online!

The second line can be removed if we are allowed to pad the rows with null values, saving 4 bytes.

Explanation

y@  =XfÊ X£  o      Implicit: U = input array
UyX{U=Xfl Xm{Uo}}    (Ungolfed)
UyX{            }    Map each column X in the input by this function:
U=Xfl              Set U to X filtered to only items whose factorial is truthy;
this just gets rid of the empty slots in the column.
Xm{  }       Map each item in X to
Uo          the last item in U, popping this item from the list.
Due to the way .map works in JS, this is only called on real items
and not empty slots, so this preserves empty slots.
Newline: set U to the resulting column-reversed array
®   fÄ              Due to the way y works, there will now be undefined in some rows.
UmZ{Zf+1}            (Ungolfed)
mZ{    }            Map each row Z in U to
Zf+1               Z filtered to only items where the item + 1 is truthy.
undefined + 1 is NaN, which is falsy, and thus eliminated.
Implicit: output result of last expression
• Nice one! You can get it down to 13 bytes by replacing l; with Ê and mf_Ä with ®fÄ. – Shaggy Jan 28 '18 at 9:45
• Actually, just mf seems to work for the second line. – Shaggy Jan 28 '18 at 10:32
• @Shaggy Thanks, hadn't thought of those! mf would get rid of any zeroes in the result though, unfortunately... – ETHproductions Jan 28 '18 at 13:01
• Ah, yes, wasn't thinking of that. – Shaggy Jan 28 '18 at 13:02

APL (Dyalog Unicode), 2019 16 bytesSBCS

-4 thanks to ngn.

Full program. Prompts for input from STDIN.

⍟0~¨⍨↓⍉⌽@×⍤1⍉↑*⎕

Try it online!

Explanation with example walk-through

prompt for evaluated input
[[1,8,5],[7,5,4],[],[1]]

* raise e to the power of that (en which ensures that there will be no zeros)
[[2.7,2981,148.4],[1096.6,148.4,54.6],[],[2.7]]

mix the lists into a single matrix, padding with zeros:
┌ ┐
│2.7E0 3.0E3 1.5E2│
│1.1E3 1.5E2 5.5E1│
│0.0E0 0.0E0 0.0E0│
│2.7E0 0.0E0 0.0E0│
└ ┘

transpose
┌ ┐
│2.7E0 1.1E3 0.0E0 2.7E0│
│3.0E3 1.5E2 0.0E0 0.0E0│
│1.5E2 5.5E1 0.0E0 0.0E0│
└ ┘

⌽@×⍤1 reverse the positive elements of each row
┌ ┐
│2.7E0 1.1E3 0.0E0 2.7E0│
│1.5E2 3.0E3 0.0E0 0.0E0│
│5.5E1 1.5E2 0.0E0 0.0E0│
└ ┘

transpose
┌ ┐
│2.7E0 1.5E2 5.5E1│
│1.1E3 3.0E3 1.5E2│
│0.0E0 0.0E0 0.0E0│
│2.7E0 0.0E0 0.0E0│
└ ┘

split the matrix into a list of lists
[[2.7,148.4,54.6],[1096.6,2981,148.4],[0,0,0],[2.7,0,0]]

0~¨⍨ remove zeros from each list
[[2.7,148.4,54.6],[1096.6,2981,148.4],[],[2.7]]

natural logarithm
[[1,5,4],[7,8,5],[],[1]]

• What if the input contains -1? – ngn Jan 28 '18 at 9:44
• @ngn Input will never contain negative numbers; see section "Input and output". – Zgarb Jan 28 '18 at 11:28
• @Zgarb That's perfect, thanks. – ngn Jan 28 '18 at 13:42
• @Adám I edited to use rank 1 instead of mix-each-split. – ngn Jan 28 '18 at 13:43
• @Adám also: exp/log instead of +1/-1 covers the tests with ⎕fr←1287 – ngn Jan 28 '18 at 14:00

K4, 36 bytes

Solution:

+{x[w]:|x w:&~^x;x}'x'[::;]@!|/#:'x:

Examples:

q)k)+{x[w]:|x w:&~^x;x}'x'[::;]@!|/#:'x:(1 2 3 4 5;6 7;8 9 10 11;0#0N;12 13 14;15 16 17 18)
15 16 17 18 5
12 13
8  9  14 11

6  7  10
1  2  3  4

q)k)+{x[w]:|x w:&~^x;x}'x'[::;]@!|/#:'x:(0#0N;5 8 7; 0 6 5 7 1)

0 6 5
5 8 7 7 1

Explanation:

This one has been a pain, and I'm still working to simplify the elided indexing.

Instead of indexing in at, for example, x[0] which would return the first row, we want to take the first column, which can be done using x[;0].

However passing variable y into x[;] treats it as doing x[y] not x[;y] hence shoving the :: in there: x[::;].

This is equivalent to flipping the list of lists, but flip requires all lists to be equal length!

+{x[w]:|x w:&~^x;x}'x'[::;]@!|/#:'x: / the solution
x: / save input as variable x
#:'   / count (#:) each (')
|/      / take the max of these lengths
!        / til, range 0..max-1
@         / apply (index into)
[::;]          / :: is a kind of null,
x'               / index into x at each of these
{              ; }'                 / two statement lambda on each (')
^x                     / null x (returns true if entry is null)
~                       / not, so flip true/false
&                        / where, indexes where true
w:                         / save as variable w
x                            / index into w at these indexes
|                             / reverse
x[w]:                              / store this back in variable x at indexes w
x                   / return x from function
+                                    / flip the result

f x=map g.h.map(g.reverse>>=(!)).h$take(maximum$length<$>x).(++z).map pure<$>x
g=concat
h x|g x==[]=x|4>2=foldr(zipWith(:))z x
x!(c:d)|c==[]=c:x!d|a:b<-x=[a]:b!d
_!y=y
z=[]:z

Try it online!

Ungolfed/Explanation

The idea is to wrap all elements in [] and pad the rows with [] (turned out to be shorter than padding with a negative integer, this allows negative inputs as well which is nice), then transpose, reverse all rows and transpose again and flatten each row:

map concat                                   -- flatten each row
. transpose'                               -- transpose (*)
. map (\row-> reverse (concat row) ! row)  -- reverse each row (see below)
. transpose'                               -- tranpose (*)
$take (maximum$ length <$> x) -- only keep up as many as longest row . (++ z) -- pad row with [],[],.. . map (\e-> [e]) -- wrap elements in [] <$> x

* This transpose function (h) simply returns the list if there are no elements at all.

The reverse function has to ignore [] elements (eg. [[],[1],[],[3],[4]] -> [[],[4],[],[3],[1]]), it does so by receiving two arguments: The first one are the elements in reverse order (eg. [4,3,1]) and the second one the original row.

x@(a:b) ! (c:d)
| c == []   = c:x ! d    -- if current element is []: skip it
| otherwise = [a]:b ! d  -- else: replace with new one (a) and continue
_ ! y = y                 -- base case (if no new elements are left): done

Python 2, 111105 92 bytes

def f(l):x=map(lambda*n:[v for v in n if-1<v],*l);return[map(list.pop,x[:len(k)])for k in l]

Try it online!

• You could use print instead of return to save a byte. – Jonathan Frech Jan 28 '18 at 17:27

JavaScript (ES6), 79 76 bytes

(a,d=[],g=s=>a.map(b=>b.map((c,i)=>(d[i]=d[i]||[])[s](c))))=>gpush&&gpop

Edit: Saved 3 bytes thanks to @ETHproductions.

• @ETHproductions Right; I've no idea why I thought it wouldn't, otherwise I would have done that already. – Neil Jan 28 '18 at 10:44

APL (Dyalog Unicode), 27 bytesSBCS

≢¨⍴¨∘↓∘⍉⍉∘↑{⍵\⌽⍵/⍺}⍤1∘⍉∘↑=⍨

Try it online!

• I think you overthought this one. – Adám Jan 28 '18 at 9:23
• I knew I could use @ but I don't have it. – ngn Jan 28 '18 at 9:38

Clojure, 123 bytes

#(map(fn[i R](map(fn[j _](let[V(for[Q %](get Q j))F filter](nth(reverse(F + V))(count(F +(take i V))))))(range)R))(range)%)

I was expecting (+ nil) to throw an exception, but it evaluates to nil :o

This operates without padding, instead it counts how many of the previous rows are at least as long as the current row R.