7

I have a recursive tuple of strings that looks like this:

('text', ('othertext', ('moretext', ('yetmoretext'))))

(it's actually a tuple of tuples of strings - it's constructed recursively)

And I'd like to flatten it into a list of strings, whereby foo[1] would contain "text", foo[2] "othertext" and so forth.

How do I do this in Python?

The duplicate is about a 2D list of lists, but here I'm dealing with a recursive tuple.

Malcolm Tucker
  • 329
  • 2
  • 17
  • 1
    It looks like the duplicate that is linked to only deals with 2d lists... – John May 12 '15 at 15:05
  • Is the level of recursion fixed? Also `('yetmoretext')` should be `('yetmoretext',)` – SiHa May 12 '15 at 15:20
  • Actually, no, there's no comma after 'yetmoretext'. The commas separate elements of the tuples, so there is none after the last element. The level of recursion isn't fixed, but the input is relatively small. Anyway, I made it work, see edit. – Malcolm Tucker May 12 '15 at 15:57
  • *I'll provide it here for future reference:* please don't answer your question in the question itself. Instead, just post it as answer – Tim May 12 '15 at 16:22
  • 2
    Actually, without a comma after ('yetmoretext') what you have is a string. It's the comma which makes it a tuple. Try it: `type(('a string'))` vs `type(('a tuple',))` – LexyStardust May 12 '15 at 16:24
  • 1
    @MalcolmTucker: As noted above, the innermost element isn't a tuple without the trailing comma, it's a string. That's what enables your answer, below, to work. – SiHa May 13 '15 at 07:07

3 Answers3

3

I've found the answer myself, I'll provide it here for future reference:

stringvar = []
while type(tuplevar) is tuple:
        stringvar.append(tuplevar[0])
        tuplevar=tuplevar[1]
stringvar.append(tuplevar)  # to get the last element. 

Might not be the cleanest/shortest/most elegant solution, but it works and it seems quite "Pythonic".

Malcolm Tucker
  • 329
  • 2
  • 17
  • 1
    This is definitely a good approach. However, take a look at the answers posted both by @LexyStardust and me. Both answers are more general and will work for more than just the single use case of nested tuples. Lexy's will work for tuple subclasses, such as [`namedtuple`](https://docs.python.org/3.3/library/collections.html#collections.namedtuple). Mine will work for any iterable class, such as lists, or custom classes which subclass an iterable class. That being said, +1 for answering your own question and posting the answer. – Deacon May 12 '15 at 18:39
1

If you're happy that the level of recursion isn't going to get horrible (and you're using an up to date version of Python):

def unpack(obj):
    for x in obj:
        if isinstance(x, str):
            yield x
        elif isinstance(x, tuple):
            yield from unpack(x)
        else:
            raise TypeError

x = ('text', ('othertext', ('moretext', ('yetmoretext',))))
result = list(unpack(x))
print(result)

Will give you:

['text', 'othertext', 'moretext', 'yetmoretext']

This will also work if there are more than 1 strings before the next tuple, or if there are tuples directly in tuples, or strings after tuples etc. You can also easily modify it to work with other types if you need, I've probably unnecessarily erred on the side of caution.

LexyStardust
  • 1,018
  • 5
  • 18
1

This is how I would approach it. This is very similar to a previous answer, however it's more general in application, as it allows any type of iterable to be flattened, except for string-type objects (i.e., lists and tuples), and it also allows for the flattening of lists of non-string objects.

# Python 3.
from collections import abc

def flatten(obj):
    for o in obj:
        # Flatten any iterable class except for strings.
        if isinstance(o, abc.Iterable) and not isinstance(o, str):
            yield from flatten(o)
        else:
            yield o

data = ('a', ('b', 'c'), [1, 2, (3, 4.0)], 'd')
result = list(flatten(data))
assert result == ['a', 'b', 'c', 1, 2, 3, 4.0, 'd']
Deacon
  • 3,615
  • 2
  • 31
  • 52