Search code examples
python-3.xpython-itertoolsiterable-unpacking

Condensing tuple of tuple of tuples of tuples of... into a list of reduced items


I have an iterable with a lot of nesting - not always to equal depth, but guarantees that each element of the original is at least once-iterable - I define this in the block quote below.

An item is more than once iterable if all its elements are iterable. An item is once iterable if none of its elements are iterable An item is twice iterable if its elements are iterables of non iterable elements, e.g. ((1,),(1,2,),(1,2,3,)) is twice iterable

Below, I have a few examples of these such iterables that I consider to be input.

trie_like= ( ((((('a','b','c','d'),), (('a','b','c','e'),),), ((('a','b','d','e'),),),), (((('a','c','d','e'),),),),), ((((('b','c','d','e'),),),),), )
uneven = (((1,),(2,),(3,)),((1,2,3),), (12,34),)

I aim to write function that reduces the level of nesting such that each element in the condensed iterable is a tuple that is once iterable. That is to say the function unnests to the nearest twice iterable object.

trie_like_condensed = (('a','b','c','d'),('a','b','c','e'),('a','b','d','e'), ('a','c','d','e'),('b','c','d','e'),)
uneven_condensed = ((1,),(2,),(3,),(1,2,3),(12,34),)

I thought I might be able to repeatedly call itertools.chain.from_iterable on it tuple until I can map it, but I can't quite get the end condition right.

import itertools
from collections.abc import Iterable

t = ( ((((('a','b','c','d'),), (('a','b','c','e'),),), ((('a','b','d','e'),),),), (((('a','c','d','e'),),),),), ((((('b','c','d','e'),),),),),)

count = 0 # just to avoid inf loops
while isinstance(t,Iterable) and count<20:
    count = count + 1
    t,p = itertools.tee(itertools.chain.from_iterable(t))
    print(list(p))
print(*map("".join, t))

For this I think I've got the depth query wrong, AND it would only work for the variable I called trie_like which has depth 4 at every deepest tuple. I thought maybe then I could use something like itertools.takewhile but I also can't get that to work, my attempt below.

def unnest_iterables(t,count=0):
    iter_part = itertools.takewhile(lambda x: isinstance(x,Iterable),t)
    niter_part = itertools.takewhile(lambda x: not isinstance(x,Iterable),t)
    # rest of t processed by recursion
    iter_part,p = itertools.tee(iter_part)
    niter_part,pn = itertools.tee(niter_part)
    print(f'Depth = {count}: {list(p)} : {list(pn)}')
    if count > 10:
        print('TEST: Big oof')
        return []
    if iter_part is None or niter_part is None:
        return itertools.chain(itertools.chain.from_iterable(iter_part),niter_part)
    try:
        return unnest_iterables(itertools.chain(itertools.chain.from_iterable(iter_part),niter_part),count+1)
    except StopIteration:
        return itertools.chain(itertools.chain.from_iterable(iter_part),niter_part)




t = ( (((((1,2,3,4),), ((1,2,3,5),),), (((1,2,4,5),),),), ((((1,3,4,5),),),),), (((((2,3,4,5),),),),),)
t = ( (((((1,2,3,4),), ((1,2,3,5),),), (((1,2,4,5),),),), ((((1,3,4,5),),),),), ((((2,3,4,5),),),),)
print(*unnest_iterables(t))

Solution

  • Based on your sample input and output, you only need to collect all once-iterable items in the input and put them into a tuple, which make the output a twice-iterable tuple. This can be achieved without the itertools module:

    def is_once_iterable(items: tuple):
        return any(not isinstance(item, tuple) for item in items)
    
    # Collect all once-iterable items and put them into a list
    def _simplify_nesting(items: tuple, condensed: list):
        if is_once_iterable(items):
            condensed.append(items)
            return
    
        for item in items:
            if isinstance(item, tuple):
                _simplify_nesting(item, condensed)
    
    # Wrapper function for _simplify_nesting
    def simplify_nesting(items: tuple):
        condensed = []
        _simplify_nesting(items, condensed)
        return tuple(condensed)
    
    inp = (((1,),(2,),(3,)),((1,2,3),), (12,34),) 
    print(simplify_nesting(inp)) # ((1,), (2,), (3,), (1, 2, 3), (12, 34))