r"""
    Inverse opertation of flatten.

    Given a flat (1d) array, and a list of shapes (represented as tuples),
    return a list of ndarrays with the specified shapes.

    Parameters
    ----------
    ary : a 1d array
        A flat (1d) array.

    shapes : list of tuples
        A list of ndarray shapes (tuple of array dimensions)

    Returns
    -------
    list of ndarrays
        A list of ndarrays with the specified shapes.

    See Also
    --------
    revrand.utils.flatten : its inverse

    Notes
    -----
    Equivalent to::

        lambda ary, shapes, order='C': \
            map(partial(custom_reshape, order=order),
                np.hsplit(ary, np.cumsum(map(partial(np.prod, dtype=int),
                                             shapes))), shapes)

    Examples
    --------

    >>> a = np.array([7, 4, 5, 8, 9, 1, 4, 2, 5, 3, 4, 3])

    >>> list(unflatten(a, [(1,), (1,), (4,), (2, 3)]))
    ... # doctest: +NORMALIZE_WHITESPACE
    [array([7]), array([4]), array([5, 8, 9, 1]), array([[4, 2, 5],
        [3, 4, 3]])]

    >>> list(unflatten(a, [(), (1,), (4,), (2, 3)]))
    ... # doctest: +NORMALIZE_WHITESPACE
    [7, array([4]), array([5, 8, 9, 1]), array([[4, 2, 5], [3, 4, 3]])]

    >>> list(unflatten(a, [(), (1,), (3,), (2, 3)]))
    ... # doctest: +NORMALIZE_WHITESPACE
    [7, array([4]), array([5, 8, 9]), array([[1, 4, 2], [5, 3, 4]])]

    >>> list(unflatten(a, [(), (1,), (5,), (2, 3)]))
    ... # doctest: +NORMALIZE_WHITESPACE +IGNORE_EXCEPTION_DETAIL
    Traceback (most recent call last):
        ...
    ValueError: total size of new array must be unchanged

    >>> flatten(list(unflatten(a, [(), (1,), (4,), (2, 3)])))
    ... # doctest: +NORMALIZE_WHITESPACE
    (array([7, 4, 5, 8, 9, 1, 4, 2, 5, 3, 4, 3]),
        [(), (1,), (4,), (2, 3)])

    >>> list(unflatten(a, [[(1,), (1,)], (4,), (2, 3)]))
    ... # doctest: +NORMALIZE_WHITESPACE
    [[array([7]), array([4])], array([5, 8, 9, 1]), array([[4, 2, 5],
        [3, 4, 3]])]

    >>> flatten(list(unflatten(a, [(), (1,), [(4,), (2, 3)]])))
    ... # doctest: +NORMALIZE_WHITESPACE
    (array([7, 4, 5, 8, 9, 1, 4, 2, 5, 3, 4, 3]),
        [(), (1,), [(4,), (2, 3)]])

def unflatten(ary, shapes, reshape=scalar_reshape):
    r"""
    Inverse opertation of flatten.

    Given a flat (1d) array, and a list of shapes (represented as tuples),
    return a list of ndarrays with the specified shapes.

    Parameters
    ----------
    ary : a 1d array
        A flat (1d) array.

    shapes : list of tuples
        A list of ndarray shapes (tuple of array dimensions)

    Returns
    -------
    list of ndarrays
        A list of ndarrays with the specified shapes.

    See Also
    --------
    revrand.utils.flatten : its inverse

    Notes
    -----
    Equivalent to::

        lambda ary, shapes, order='C': \
            map(partial(custom_reshape, order=order),
                np.hsplit(ary, np.cumsum(map(partial(np.prod, dtype=int),
                                             shapes))), shapes)

    Examples
    --------

    >>> a = np.array([7, 4, 5, 8, 9, 1, 4, 2, 5, 3, 4, 3])

    >>> list(unflatten(a, [(1,), (1,), (4,), (2, 3)]))
    ... # doctest: +NORMALIZE_WHITESPACE
    [array([7]), array([4]), array([5, 8, 9, 1]), array([[4, 2, 5],
        [3, 4, 3]])]

    >>> list(unflatten(a, [(), (1,), (4,), (2, 3)]))
    ... # doctest: +NORMALIZE_WHITESPACE
    [7, array([4]), array([5, 8, 9, 1]), array([[4, 2, 5], [3, 4, 3]])]

    >>> list(unflatten(a, [(), (1,), (3,), (2, 3)]))
    ... # doctest: +NORMALIZE_WHITESPACE
    [7, array([4]), array([5, 8, 9]), array([[1, 4, 2], [5, 3, 4]])]

    >>> list(unflatten(a, [(), (1,), (5,), (2, 3)]))
    ... # doctest: +NORMALIZE_WHITESPACE +IGNORE_EXCEPTION_DETAIL
    Traceback (most recent call last):
        ...
    ValueError: total size of new array must be unchanged

    >>> flatten(list(unflatten(a, [(), (1,), (4,), (2, 3)])))
    ... # doctest: +NORMALIZE_WHITESPACE
    (array([7, 4, 5, 8, 9, 1, 4, 2, 5, 3, 4, 3]),
        [(), (1,), (4,), (2, 3)])

    >>> list(unflatten(a, [[(1,), (1,)], (4,), (2, 3)]))
    ... # doctest: +NORMALIZE_WHITESPACE
    [[array([7]), array([4])], array([5, 8, 9, 1]), array([[4, 2, 5],
        [3, 4, 3]])]

    >>> flatten(list(unflatten(a, [(), (1,), [(4,), (2, 3)]])))
    ... # doctest: +NORMALIZE_WHITESPACE
    (array([7, 4, 5, 8, 9, 1, 4, 2, 5, 3, 4, 3]),
        [(), (1,), [(4,), (2, 3)]])
    """
    if isinstance(shapes, list):
        sizes = list(map(sumprod, shapes))
        ends = np.cumsum(sizes)
        begs = np.concatenate(([0], ends[:-1]))
        struct_arys = [unflatten(ary[b:e], s, reshape=reshape)
                       for b, e, s in zip(begs, ends, shapes)]
        return struct_arys
    else:
        struct_ary = reshape(ary, shapes)
        return struct_ary