def groupby_many(keys: Callable[[Any], Iterable], reducer: Reducer, initial): """Given a `keys` function, that maps an element into multiple keys, transduces the collection into a dictionary of key to group of matching elements. >>> transducer.transduce( transducer.groupby_many( lambda x: ("even",) if x % 2 == 0 else ("odd",), lambda s, x: (*s, x), (), ), lambda s, _: s, {}, [1, 2, 3, 4, 5], ) {"even": (2, 4), "odd": (1, 3, 5)} """ return functional_generic.compose( mapcat( functional_generic.compose_left( functional_generic.juxt(keys, functional.wrap_tuple), sync.star(itertools.product), ), ), lambda step: lambda s, x: step( toolz.assoc(s, x[0], reducer(s.get(x[0], initial), x[1])), x, ), )
def explode(*positions: Collection[int]): """Flattens a non homogeneous iterable. For an iterable where some positions are iterable and some are not, "explodes" the iterable, so that each element appears in a single row, and duplicates the non iterable. >>> functional_generic.pipe( [ "x", [ "y1", "y2", "y3", ], "z", ], data.explode(1), tuple, ) ( ("x", "y1", "z"), ("x", "y2", "z"), ("x", "y3", "z"), ) """ return functional_generic.compose_left( _do_on_positions( functional.wrap_tuple, functional_generic.complement(functional.contains(positions)), ), sync.star(itertools.product), )
def groupby_many(f: Callable, it: Iterable) -> Dict[Text, Any]: """Return a mapping `{y: {x s.t. y in f(x)}}, where x in it. ` Parameters: Key function (gets an object in collection and outputs tuple of keys). A Collection. Returns a dictionary where key has been computed by the `f` key function. >>> names = ['alice', 'bob', 'charlie', 'dan', 'edith', 'frank'] >>> groupby_many(lambda name: (name[0], name[-1]), names) {'a': frozenset({'alice'}), 'e': frozenset({'alice', 'charlie', 'edith'}), 'b': frozenset({'bob'}), 'c': frozenset({'charlie'}), 'd': frozenset({'dan'}), 'n': frozenset({'dan'}), 'h': frozenset({'edith'}), 'f': frozenset({'frank'}), 'k': frozenset({'frank'})}""" return functional_generic.pipe( it, functional_generic.mapcat( functional_generic.compose_left( lambda element: (f(element), [element]), sync.star(itertools.product), ), ), edges_to_graph, )
async def test_itemfilter_async_sync_mixed(): assert (await functional_generic.pipe( { 1: 1, 2: 1, 3: 3 }, functional_generic.itemfilter(functional_generic.star(_equals)), functional_generic.itemfilter(sync.star(lambda _, val: val == 1)), ) == { 1: 1 })
async def test_async_bifurcate(): async def async_sum(x): await asyncio.sleep(0.01) return sum(x) def gen(): yield 1 yield 2 yield 3 average = await functional_generic.pipe( gen(), functional_generic.bifurcate(async_sum, functional.count), sync.star(operator.truediv), ) assert average == 2
def _get_children(element): return functional_generic.case_dict( { _is_terminal: functional.just(()), functional.is_instance(tuple): functional.identity, functional.is_instance(list): functional.identity, functional.is_instance(dict): functional_generic.compose_left( dict.items, functional.curried_map_sync(sync.star(KeyValue)), ), functional.is_instance(KeyValue): functional_generic.compose_left( lambda x: x.value, sync.ternary( _is_terminal, functional.wrap_tuple, _get_children, ), ), }, )(element)
#: Combines transducers in a `dict` into a transducer that produces a `dict`. #: >>> transducer.transduce( #: transducer.apply_spec( # This will combine the inner stuff into one new transducer. #: { #: "incremented": _increment(_append_to_tuple), # This is a transducer. #: "sum": lambda s, x: x + s, # This is another transducer. #: }, #: ), #: lambda s, _: s, #: {"incremented": (), "sum": 0}, #: [1, 2, 3], #: ) #: {"incremented": (2, 3, 4), "sum": 6} apply_spec = functional_generic.compose_left( dict.items, sync.map(sync.star(_transform_by_key(toolz.assoc))), sync.star(functional_generic.compose), ) #: Combines transducers in a `tuple` into a transducer that produces a `tuple`. #: transducer.transduce( #: transducer.juxt( # This will combine the inner stuff into one new transducer. #: _increment(_append_to_tuple), # This is a transducer. #: lambda s, x: x + s, # This is another transducer. #: ), #: lambda s, _: s, #: [(), 0], #: [1, 2, 3], #: ) #: ((2, 3, 4), 6) juxt = functional_generic.compose_left(
_MATCHED = "matched" _UNMATCHED = "unmatched" _get_matched = dict_utils.itemgetter(_MATCHED) _get_unmatched = dict_utils.itemgetter(_UNMATCHED) def _make_matched_unmatched(matched, unmatched): return {_MATCHED: matched, _UNMATCHED: unmatched} _merge_children_as_matched = functional_generic.compose_left( sync.mapcat(sync.juxtcat(_get_matched, _get_unmatched)), tuple, functional_generic.pair_right(functional.just(())), sync.star(_make_matched_unmatched), ) _merge_children = sync.compose_left( functional_generic.bifurcate( sync.compose_left(sync.mapcat(_get_matched), tuple), sync.compose_left( sync.mapcat(_get_unmatched), tuple, ), ), sync.star(_make_matched_unmatched), ) @currying.curry
lambda applier: map_dict(functional.identity, applier), apply_utils.apply(spec), ) #: Construct a function that applies the i'th function in an iterable # on the i'th element of a given iterable #: #: Note: Number of functions should be equal to the number of elements in the given iterable #: #: >>> stack([lambda x:x+1, lambda x:x-1])((5, 5)) #: (6, 4) stack = compose_left( enumerate, functional.curried_map_sync( sync.star(lambda i, f: compose(f, lambda x: x[i])), ), sync.star(juxt), ) def bifurcate(*funcs): """Serially run each function on tee'd copies of a sequence. If the sequence is a generator, it is duplicated so it will not be exhausted (which may incur a substantial memory signature in some cases). >>> f = bifurcate(sum, gamla.count) >>> seq = map(gamla.identity, [1, 2, 3, 4, 5]) >>> f(seq) (15, 5) """ return compose_left(iter, lambda it: itertools.tee(it, len(funcs)), stack(funcs))
edges_to_graph = functional_generic.compose( functional_generic.valmap( functional_generic.compose( frozenset, functional_generic.curried_map(functional.second), ), ), sync.groupby(functional.head), ) #: Gets a graph and returns an iterator of all edges in it. #: #: >>> list(graph_to_edges({'1': ['2', '3'], '2': ['3'], '3': ['4'], '4': []})) #: [('1', '2'), ('1', '3'), ('2', '3'), ('3', '4')] graph_to_edges = functional_generic.compose_left( sync.keymap(functional.wrap_tuple), dict.items, sync.mapcat(sync.star(itertools.product)), ) #: Gets a graph and returns the graph with its edges reversed #: #: >>> reverse_graph({'1': ['2', '3'], '2': ['3'], '3': ['4'], '4': []}) #: {'2': frozenset({'1'}), '3': frozenset({'1', '2'}), '4': frozenset({'3'})} reverse_graph = functional_generic.compose_left( graph_to_edges, functional_generic.curried_map( functional_generic.compose_left(reversed, tuple)), edges_to_graph, ) #: Gets a sequence of nodes (cliques) and returns the bidirectional graph they represent #:
def _debug_generic(f): return functional_generic.compose_left( lambda *funcs: toolz.interleave([funcs, [debug] * len(funcs)]), sync.star(f), )