4

I've written a function that accepts, works and return simple, non-nested tuples.

eg.:

myfun((1,2,3,4)):
... -> logic
return (1,2,3,4) -> the numbers can change, but the shape will be the same

Since the logic works only with mono-dimensional tuples but is conceptually the same for each level of nesting. I was wondering if there's a way to convert a nested tuple like ((1,2,(3,)),(4,)) into the plain (1,2,3,4) and then convert it back to ((1,2,(3,)),(4,)).

Basically what I want is to unpack a generic input tuple, work with it, and then pack the results in the same shape of the given one.

Is there a Pythonic way to accomplish such a task?

Probably the unpacking could be solved with recursion, however I'm not sure about the "re-packing" part.

darkpirate
  • 712
  • 3
  • 10
  • 27
  • The flattening part: https://stackoverflow.com/questions/2158395/flatten-an-irregular-list-of-lists. On the re-packing side, just construct the tuple: `((t[0],t[1],(t[2],)),(t[3],))`. – DYZ Sep 04 '17 at 23:54
  • ty, it would appear that the flattening part was indeed recursive. but as it is it would not retain informations about the original structure of the tuple :/ – darkpirate Sep 04 '17 at 23:59

3 Answers3

3

The unpacking is not that hard:

def unpack(parent):
    for child in parent:
        if type(child) == tuple:
            yield from unpack(child)
        else:
            yield child

for example, can do the trick.

Repacking is a bit trickier. I came up with the following, which works but is not very pythonic, I'm afraid:

def repack(structured, flat):
    output = []
    global flatlist
    flatlist = list(flat)
    for child in structured:
        if type(child) == tuple:
            output.append(repack(child, flatlist))
        else:
            output.append(flatlist.pop(0))

    return tuple(output)

Example usage is:

nested = ((1, 2, (3,)), (4,))
plain = tuple(unpack(nested))
renested = repack(nested, plain)

Hope this helps!

Bart Van Loon
  • 1,430
  • 8
  • 18
  • it surely does! Thank you. – darkpirate Sep 05 '17 at 00:27
  • My pleasure. There might be a way to record the structure while unpacking it and then reusing that recorded structure to repack it without recursion. could be a fun exercise one day, but now it's bedtime here. :-) – Bart Van Loon Sep 05 '17 at 00:30
  • yeah, it's just a matter of saving the level of recursion... maybe i'll think a bit more about it – darkpirate Sep 05 '17 at 00:34
  • I think there's more to it than simply saving the level of recursion, since both 1, 2 and 4 in the given example have the same level, but don't belong together. Unless of course you also save the intermittent 'jump' back in between. – Bart Van Loon Sep 05 '17 at 09:20
2

This should work for the repacking:

x = (1,(2,3),(4,(5,6)))
y = (9,8,7,6,5,4)

def map_shape(x, y, start=0):
    if type(x) == tuple:
        l = []
        for item in x:
            mapped, n_item = map_shape(item, y[start:])
            start += n_item
            l.append(mapped)
        return tuple(l), start
    else:
        return y[start], start+1

map_shape(x,y)[0]

Output:

(9, (8, 7), (6, (5, 4)))
Julien
  • 13,986
  • 5
  • 29
  • 53
1

I submit my version. It uses the same function to flat and reconstruct the list. If flat is None it flattens, otherwise it reconstructs by yielding a tuple.

import collections


def restructure(original, flat=None):
    for el in original:
        if isinstance(el, collections.Iterable) and not isinstance(el, (str, bytes)):
            if flat:
                yield tuple(restructure(el, flat))
            else:
                yield from restructure(el)
        else:
            yield next(flat) if flat else el


def gen():
    i = 0
    while True:
        yield i
        i += 1


def myfun(iterable):
    flat = tuple(restructure(iterable))
    # your transformation ..
    flat = gen()  # assigning infinite number generator for testing
    return restructure(iterable, flat=iter(flat))


x = (1, (2, 3), (4, (5, 6)))
print(tuple(y for y in myfun(x)))  # (0, (1, 2), (3, (4, 5)))
EsotericVoid
  • 2,306
  • 2
  • 16
  • 25
  • very interesting idea! – darkpirate Sep 05 '17 at 01:28
  • but... what is it that yield from at line 10 ? Never saw the from used after a yield. – darkpirate Sep 05 '17 at 01:31
  • is this python 3.x ? Damn... i forgot to mention i'm using 2.x – darkpirate Sep 05 '17 at 01:34
  • is there a way to convert that "yield from" into 2.x equivalent? – darkpirate Sep 05 '17 at 01:38
  • Yes, the `yield from` is the expression introduced in [PEP 380](https://www.python.org/dev/peps/pep-0380/) that delegates the yielding to a subgenerator. You can check [Converting “yield from” statement to Python 2.7 code](https://stackoverflow.com/questions/17581332/converting-yield-from-statement-to-python-2-7-code). Also, the flattening part is taken from [Flatten (an irregular) list of lists](https://stackoverflow.com/questions/2158395/flatten-an-irregular-list-of-lists) posted in the comment section and is version specific as well. – EsotericVoid Sep 05 '17 at 10:04