Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to reshape a list without numpy

Tags:

python

How do I reshape a list into a n-dimensional list

Input: list = [1, 2, 3, 4, 5, 6, 7, 8]
shape = [2, 2, 2]

output = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]]

like image 689
Bo Peng Avatar asked May 14 '19 01:05

Bo Peng


3 Answers

This recursive approach should work.

lst = [1, 2, 3, 4, 5, 6, 7, 8]
shape = [2, 2, 2]

from functools import reduce 
from operator import mul

def reshape(lst, shape):
    if len(shape) == 1:
        return lst
    n = reduce(mul, shape[1:])
    return [reshape(lst[i*n:(i+1)*n], shape[1:]) for i in range(len(lst)//n)]

reshape(lst, shape)

You probably want to wrap that with a check that your dimensions make sense... e.g.

assert reduce(mul, shape) == len(lst)
like image 83
Julien Avatar answered Sep 19 '22 16:09

Julien


Here is an approach using the grouper once on each dimension except the first:

import functools as ft

# example
L = list(range(2*3*4))
S = 2,3,4

# if tuples are acceptable 
tuple(ft.reduce(lambda x, y: zip(*y*(x,)), (iter(L), *S[:0:-1])))
# (((0, 1, 2, 3), (4, 5, 6, 7), (8, 9, 10, 11)), ((12, 13, 14, 15), (16, 17, 18, 19), (20, 21, 22, 23)))

# if it must be lists
list(ft.reduce(lambda x, y: map(list, zip(*y*(x,))), (iter(L), *S[:0:-1])))
# [[[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11]], [[12, 13, 14, 15], [16, 17, 18, 19], [20, 21, 22, 23]]]
like image 38
Paul Panzer Avatar answered Sep 19 '22 16:09

Paul Panzer


The code below should do the trick.

The solution given below very general. The input list can be a nested list of lists of an any old/undesired shape; it need not be a list of integers.

Also, there are separate re-usable tools. For example the all_for_one function is very handy.

EDIT:
I failed to note something important. If you put 1s inside of the shape parameter, then you can get superfluous list nestings (only one list inside of a list instead of five or six lists inside of a list)

For example, if shape is [1, 1, 2]
then the return value might be [[[0.1, 0.2]]] instead of [0.1, 0.2]

the length of shape is the number of valid subscripts in the output list.
For example,

shape = [1, 2] # length 2
lyst = [[0.1, 0.2]]
print(lyst[0][0])  # valid.... no KeyError raised

If you want a true column or row vector, then len(shape) must be 1.
For example, shape = [49] will give you a row/column vector of length 49.

shape = [2] # length 2
output = [0.1, 0.2]
print(lyst[0])  

Here's the code:

from operator import mul
import itertools as itts
import copy
import functools

one_for_all =  lambda one: itts.repeat(one, 1)

def all_for_one(lyst):
   """
    EXAMPLE:
        INPUT:
            [[[1, 2], [3, 4]], [[5, 6], [7, 8]]]
        OUTPUT:
            iterator to [1, 2, 3, 4, 5, 6, 7, 8]

    IN GENERAL:
        Gets iterator to all nested elements
        of a list of lists of ... of lists of lists.
    """
    # make an iterator which **IMMEDIATELY**
    # raises a `StopIteration` exception
    its = itts.repeat("", 0)

    for sublyst in lyst:

        if hasattr(sublyst, "__iter__") and id(sublyst) != id(lyst):
            # Be careful ....
            #
            # "string"[0] == "s"[0] == "s"[0][0][0][0][0][0]...
            #
            # do not drill down while `sublyst` has an "__iter__" method
            # do not drill down while `sublyst` has a `__getitem__` method
            #
            it = all_for_one(sublyst)
        else:
            it = one_for_all(sublyst)

        # concatenate results to what we had previously
        its = itts.chain(its, it)

    return its



merged = list(all_for_one([[[1, 2], [3, 4]], [[5, 6], [7, 8]]]))
print("merged == ", merged)




def reshape(xread_lyst, xshape):
    """
    similar to `numpy.reshape`

    EXAMPLE:         

        lyst  = [1, 2, 3, 4, 5, 6, 7, 8]
        shape = [2, 2, 2]        
        result = reshape(lyst)
        print(result)

         result ==
         [[[1, 2], [3, 4]], [[5, 6], [7, 8]]]


    For this function, input parameter `xshape` can be
    any iterable containing at least one element.
    `xshape` is not required to be a tuple, but it can be.

    The length of xshape should be equal to the number
    of desired list nestings

    If you want a list of integers: len(xshape) == 1
    If you want a list of lists:    len(xshape) == 2
    If you want a list of lists of lists: len(xshape) == 3

    If xshape = [1, 2],
    outermost list has 1 element
    that one element is a list of 2 elements.
    result == [[1, 2]]

    If xshape == [2]
    outermost list has 2 elements
    those 2 elements are non-lists:
    result: [1, 2] 

    If xshape = [2, 2],
    outermost list has 2 elements
    each element is a list of 2 elements.
    result == [[1, 2] [3, 4]]


    """
    # BEGIN SANITIZING INPUTS

    # unfortunately, iterators are not re-usable
    # Also, they don't have `len` methods

    iread_lyst = [x for x in ReshapeTools.unnest(xread_lyst)]
    ishape = [x for x in self.unnest(xshape)]

    number_of_elements = functools.reduce(mul, ishape, 1)

    if(number_of_elements != len(iread_lyst)):
        msg = [str(x) for x in [
            "\nAn array having dimensions ", ishape,
            "\nMust contain ", number_of_elements, " element(s).",
            "\nHowever, we were only given ", len(iread_lyst), " element(s)."
        ]]
        if len(iread_lyst) < 10:
             msg.append('\nList before reshape: ')
             msg.append(str([str(x)[:5] for x in iread_lyst]))
        raise TypeError(''.join(msg))
    ishape = iter(ishape)
    iread_lyst = iter(iread_lyst)
    # END SANITATIZATION OF INPUTS
    write_parent = list()
    parent_list_len = next(ishape)
    try:
        child_list_len = next(ishape)
        for _ in range(0, parent_list_len):
            write_child = []
            write_parent.append(write_child)
            i_reshape(write_child, iread_lyst, child_list_len, copy.copy(ishape))
    except StopIteration:
        for _ in range(0, parent_list_len):
            write_child = next(iread_lyst)
            write_parent.append(write_child)
    return write_parent


def ilyst_reshape(write_parent, iread_lyst, parent_list_len, ishape):
    """
    You really shouldn't call this function directly.
    Try calling `reshape` instead

    The `i` in the name of this function stands for "internal"
    """
    try:
        child_list_len = next(ishape)
        for _ in range(0, parent_list_len):
            write_child = []
            write_parent.append(write_child)
            ilyst_reshape(write_child, iread_lyst, child_list_len, copy.copy(ishape))
    except StopIteration:
        for _ in range(0, parent_list_len):
            write_child = next(iread_lyst)
            write_parent.append(write_child)
    return None

three_dee_mat = reshape(merged, [2, 2, 2])
print("three_dee_mat == ", three_dee_mat)
like image 29
Toothpick Anemone Avatar answered Sep 19 '22 16:09

Toothpick Anemone