Monday, May 20, 2024

Flatten list of generators with Python

"""Flatten list of generators"""

from typing import Generator, Any

listofgenerators1: list[Generator[int, None, None]] = [
    (_ for _ in range(10)),
    (_ for _ in range(10, 20)),
]
print(listofgenerators1, type(listofgenerators1), type(listofgenerators1[0]))
listofgenerators2: list[Generator[int, None, None]] = [
    (_ for _ in range(10)),
    (_ for _ in range(10, 20)),
]
print(listofgenerators2, type(listofgenerators2), type(listofgenerators2[0]))

# Check if object is a generator with isinstance and types
import types

print(
    type(listofgenerators1[0]), isinstance(listofgenerators1[0], (types.GeneratorType))
)
# or simply
print(
    type(listofgenerators1[0]), str(type(listofgenerators1[0])) == "<class 'generator'>"
)


def combine_generators(listofgenerators) -> Generator[Any, Any, None]:
    """Yield from list of generators"""
    for generator in listofgenerators:
        yield from generator


# Flatten with yield from function
flattened_function: list[Any] | list[None] = list(combine_generators(listofgenerators1))
print(flattened_function, "Flattened with yield from function")

# Flatten with list comprehension
flattened_comprehension: list[Any] | list[None] = [
    item for generator in listofgenerators2 for item in generator
]
print(flattened_comprehension, "Flattened with list comprehension")

'''Generator expressions are similar to list comprehensions,
but use parentheses instead of square brackets.
They generate items on-the-fly instead of creating a full list in memory.
'''
def numbers(start, end):
    for i in range(start, end):
        yield i

def combined():
    yield from (numbers(start, start + 5) for start in range(1, 16, 5))

for item in combined():
    print(item)
 
# flatten list with variable depth

from typing import Callable, Any, TypeAlias
Flatten: TypeAlias = Callable[[list], list]
flatten: Flatten = lambda target: sum((flatten(sub)
if isinstance(sub, list)
else [sub] for sub in target), [])

# Example usage
nested_list: list[Any] = [1, [2, [3, 4], 5], ['a', 7], 8, [[[[[[5.3, 10], 11]]]]]]
print(flatten(nested_list))