Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rearranging a (list of lists) matrix using list comprehensions only

Consider the following 3 x 4 matrix implemented as a list of 3 lists of length 4 in Python:

>>> matrix = [[1, 2, 3, 4],[5, 6, 7, 8],[9, 10, 11, 12]]

The following list comprehension will rearrange the matrix transposing rows and columns:

>>> [[row[i] for row in matrix] for i in range(4)]
[[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]

BUT suppose I need this as result:

[[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]]

that is a 4 x 3 matrix rearranged so to obtain a sequential "scrolling" of the original matrix that "breaks" to a new row every 3 elements.

I know it is possible to draw out an algorithm to achieve the task but would it be possible to obtain that using only list comprehensions? (And, if yes, how?)


EDIT:

The accepted answer shall meet the following requirements:

  • must work on a base/clean Python installation (no additional libraries);
  • must be (similarly as for matrix transposition) a "one-liner".

2nd EDIT + accepted answer motivation:

Here's what I did when I had to find a solution for this (based upon the suggestions I gave in my own comments below):

mat = [[1,2,3,4],[5,6,7,8],[9,10,11,12]]

[[mat[(3*(i-1)+j -1)//4+1][(3*(i-1)+j -1)%4] for j in range(3)] for i in range(4)]

NOTE that the solution I wrote is specific to this case but, as Clodion noticed too, the "formula" can be "generalized" so to re-arrange the initial (list of lists) matrix to different "shapes".

like image 731
danicotra Avatar asked Jul 27 '15 09:07

danicotra


1 Answers

How about something like this:

>>> matrix = [[1, 2, 3, 4],[5, 6, 7, 8],[9, 10, 11, 12]]
>>> it = (y for x in matrix for y in x)
>>> list(zip(*[it]*3))
[(1, 2, 3), (4, 5, 6), (7, 8, 9), (10, 11, 12)]

If you want list of lists:

>>> matrix = [[1, 2, 3, 4],[5, 6, 7, 8],[9, 10, 11, 12]]
>>> it = (y for x in matrix for y in x)
>>> list(map(list, zip(*[it]*3)))
[[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]]

Explanation:

  1. First we create a generator that gives the elements of the list (as if it were flattened to a single list - that is 1,2,3,4,5,6,...,12) - we store that in it.

  2. Then we call zip() giving it it three times; since the generator is the same, it provides a next element in it every time.

FURTHER NOTE: you can even re-arrange the initial matrix in different "shapes" than the 4 x 3, if you want, by simply changing the 3 to the desired number of columns. (For example, change it to 2 or to 6 and you'll obtain, respectively, a 6 x 2 or a 2 x 6 re-arranged matrix instead).


A method without using zip , and only using list comprehension, but it requires two lines -

>>> matrix = [[1, 2, 3, 4],[5, 6, 7, 8],[9, 10, 11, 12]]
>>> it = (y for x in matrix for y in x)
>>> [[next(it) for _ in range(3)] for _ in range(4)]
[[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]]
like image 61
Anand S Kumar Avatar answered Sep 25 '22 05:09

Anand S Kumar