Search code examples
pythontuplesgeneratorstdtuple

Clean dynamically generated nested Tuples


I have a class whose __iter__() method return a itertools.product() of a dynamically generated data. This data do a Cartesian product of arbitrarily nested dicts, and now I need to flatten it, but in a streamlined way, aggregating intermediate iterators.

I'm trying either of these:

  1. Modify __iter__() to handle the internal tuples:

     class Explosion:
         ...
         def __iter__(self):
             return product(*self.fragments)
    
  2. Encapsulate it in another object to handle the conversion, but this is less desirable:

     class CleanOutput:
         def __init__(self, it):
             self.it = it
    
         def next(self):
             for x in self.it:
                 yield ?
    
     class Explosion:
         ...
         def __iter__(self):
             return CleanOutput(product(*self.fragments))
    

Well, the algorithm does work, but the problem is the unpacking at the final, for example:

(11, ..., (10.7, 104.75, ('N', True, False, 'B2B'), 99.01, ...), 1, 'SP', 7).

Look at all the nesting! How to remove it in real-time? While it is being generated... I'm looking for a way retrieve:

(11, ..., 10.7, 104.75, 'N', True, False, 'B2B', 99.01, ..., 1, 'SP', 7).

What is the best and fastest way to do it? Thank you!

EDIT

Actually, what I'd really like was a list comprehension or a generator expression or even another generator, because I needed to include it in a callable, intercepting the output of the itertools.product() itself. I don't simply need a way to clean these tuples. So it isn't a duplicate.


Solution

  • That wasn't easy, the recursion has to be used, but separated from the main __iter__ method. That's how I ended up doing. Now also with a recursive generator _merge, called by another generator _flatten:

    class Explosion:
        # ...
    
        def __iter__(self):
            def _flatten(container):
                def _merge(t):
                    for te in t:
                        if isinstance(te, tuple):
                            for ite in _merge(te):
                                yield ite
                        else:
                            yield te
    
                for t in container:
                    yield tuple(_merge(t))
    
            return _flatten(product(*self.fragments))
    

    See an example of utilization of the _flatten() function:

    >>> list(itertools.product([1,2],[3,(4,(5,6))]))
    [(1, 3), (1, (4, (5, 6))), (2, 3), (2, (4, (5, 6)))]
    >>> list(_flatten(itertools.product([1,2],[3,(4,(5,6))])))
    [(1, 3), (1, 4, 5, 6), (2, 3), (2, 4, 5, 6)]