dev4py.utils.collectors
The collectors
module provides a set of collectors inspired by java (java.util.stream.Collectors)
1"""The `collectors` module provides a set of collectors inspired by java (java.util.stream.Collectors)""" 2 3# Copyright 2022 the original author or authors (i.e.: St4rG00se for Dev4py). 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# https://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17from dataclasses import dataclass 18from functools import partial 19from typing import Generic, Any, Final, Optional, cast 20 21from dev4py.utils import lists, dicts, objects, tuples 22from dev4py.utils.objects import require_non_none, is_none, to_self 23from dev4py.utils.types import BiConsumer, BiFunction, Supplier, T, R, Function, K, V 24 25 26############################## 27# MODULE CLASSES/FUNCTIONS # 28############################## 29@dataclass(frozen=True) 30class Collector(Generic[T, R]): 31 """ 32 Collector class: A generic dataclass to describe a collector[T,R] 33 34 Args: 35 supplier (Supplier[R]): A function that creates and returns a new result container 36 accumulator (BiFunction[R, T, R]): A function that folds a value into a result container 37 combiner (BiFunction[R, R, R]): A function that accepts two partial results and merges them 38 """ 39 supplier: Supplier[R] 40 accumulator: BiFunction[R, T, R] 41 combiner: BiFunction[R, R, R] 42 43 def __post_init__(self): 44 """ 45 Post init values checker 46 Raises: 47 TypeError: Raises a TypeError at least one parameter is None 48 """ 49 require_non_none(self.supplier) 50 require_non_none(self.accumulator) 51 require_non_none(self.combiner) 52 53 54def of(supplier: Supplier[R], accumulator: BiFunction[R, T, R], combiner: BiFunction[R, R, R]) -> Collector[T, R]: 55 """ 56 Returns a Collector by using the given parameters 57 58 Args: 59 supplier (Supplier[R]): A function that creates and returns a new result container 60 accumulator (BiFunction[R, T, R]): A function that folds a value into a result container 61 combiner (BiFunction[R, R, R]): A function that accepts two partial results and merges them 62 63 Returns: 64 collector: a collector[T,R] built with given parameters 65 66 Raises: 67 TypeError: Raises a TypeError at least one parameter is None 68 """ 69 return Collector( 70 supplier=require_non_none(supplier), 71 accumulator=require_non_none(accumulator), 72 combiner=require_non_none(combiner) 73 ) 74 75 76def of_biconsumers(supplier: Supplier[R], accumulator: BiConsumer[R, T], combiner: BiConsumer[R, R]) -> Collector[T, R]: 77 """ 78 Returns a Collector by using the given parameters (biconsumer instead of bifunction) 79 80 Args: 81 supplier (Supplier[R]): A function that creates and returns a new result container 82 accumulator (BiConsumer[R, T]): A function that folds a value into a result container 83 combiner (BiConsumer[R, R]): A function that accepts two partial results and merges them 84 85 Returns: 86 collector: a collector[T,R] built with given parameters 87 88 Raises: 89 TypeError: Raises a TypeError at least one parameter is None 90 """ 91 return of( 92 supplier=require_non_none(supplier), 93 accumulator=_to_bifunction(accumulator), 94 combiner=_to_bifunction(combiner) 95 ) 96 97 98def to_list() -> Collector[T, list[T]]: 99 """ 100 Returns a Collector that accumulates the input elements into a new list 101 102 Returns: 103 Collector[T, list[T]]: a Collector that accumulates the input elements into a new list 104 """ 105 return of( 106 supplier=lists.empty_list, 107 accumulator=lists.append, 108 combiner=lists.extend 109 ) 110 111 112def to_dict(key_mapper: Function[T, K], value_mapper: Function[T, V]) -> Collector[T, dict[K, V]]: 113 """ 114 Returns a Collector that accumulates elements into a dict whose keys and values are the result of applying the 115 provided mapping functions to the input elements 116 117 Args: 118 key_mapper: a mapping function to produce keys 119 value_mapper: a mapping function to produce values 120 121 Returns: 122 Collector[T, dict[K, V]]: a Collector which collects elements into a dict whose keys and values are the result 123 of applying mapping functions to the input elements 124 """ 125 require_non_none(key_mapper) 126 require_non_none(value_mapper) 127 return of_biconsumers( 128 supplier=dicts.empty_dict, 129 accumulator=partial(_to_dict_accumulator, key_mapper=key_mapper, value_mapper=value_mapper), 130 combiner=_to_dict_combiner 131 ) 132 133 134def to_none() -> Collector[T, None]: 135 """ 136 Returns a Collector that always returns None 137 138 Returns: 139 Collector[T, None]: A collector that always returns None 140 """ 141 return of( 142 supplier=objects.to_none, 143 accumulator=objects.to_none, 144 combiner=objects.to_none 145 ) 146 147 148def to_tuple() -> Collector[T, tuple[T, ...]]: 149 """ 150 Returns a Collector that accumulates the input elements into a new tuple 151 152 Returns: 153 Collector[T, tuple[T, ...]]: a Collector that accumulates the input elements into a new tuple 154 """ 155 return of( 156 supplier=tuples.empty_tuple, 157 accumulator=tuples.append, 158 combiner=tuples.extend 159 ) 160 161 162def to_counter() -> Collector[T, int]: 163 """ 164 Returns a Collector accepting elements of type T that counts the number of input elements 165 166 Returns: 167 Collector[T, int]: a Collector accepting elements of type T that counts the number of input elements 168 """ 169 return of( 170 supplier=_to_counter_supplier, 171 accumulator=_to_counter_accumulator, 172 combiner=_to_counter_combiner 173 ) 174 175 176def grouping_by( 177 key_mapper: Function[T, K], value_mapper: Function[T, V] = to_self # type: ignore 178) -> Collector[T, dict[K, list[V]]]: 179 """ 180 Returns a collector that groups elements into a dictionary based on the provided key mapper and value mapper 181 functions. 182 183 Args: 184 key_mapper (Function[T, K]): A function that maps an element to its key. 185 value_mapper (Function[T, V], optional): A function that maps an element to its value. Defaults to to_self. 186 187 Returns: 188 Collector[T, dict[K, list[V]]]: A collector that groups elements into a dictionary. 189 """ 190 require_non_none(key_mapper) 191 require_non_none(value_mapper) 192 return of_biconsumers( 193 supplier=dicts.empty_dict, 194 accumulator=partial(_grouping_by_accumulator, key_mapper=key_mapper, value_mapper=value_mapper), 195 combiner=_grouping_by_combiner 196 ) 197 198 199############################## 200# PRIVATE MODULE FUNCTIONS # 201############################## 202def _bifunction_from_biconsumer(t: T, r: R, biconsumer: BiConsumer[T, R]) -> T: 203 """ 204 private function to create a BiFunction from a BiConsumer by using `partial` function 205 Note: lambda or inner function are not used in order to be compatible with multiprocessing (lambda are not 206 serializable) 207 """ 208 require_non_none(biconsumer)(t, r) 209 return t 210 211 212def _to_bifunction(biconsumer: BiConsumer[T, R]) -> BiFunction[T, R, T]: 213 """ 214 private function to create a BiFunction from a BiConsumer 215 Note: lambda are not used in order to be compatible with multiprocessing (lambda are not serializable) 216 """ 217 return partial(_bifunction_from_biconsumer, biconsumer=require_non_none(biconsumer)) 218 219 220def _to_dict_accumulator(d: dict[K, V], value: T, key_mapper: Function[T, K], value_mapper: Function[T, V]) -> None: 221 """ 222 private function to represent a dict accumulator 223 Note: lambda are not used in order to be compatible with multiprocessing (lambda are not serializable) 224 """ 225 dicts.put_value(d, key_mapper(value), value_mapper(value)) 226 227 228def _to_dict_combiner(d1: dict[K, V], d2: dict[K, V]) -> None: 229 """ 230 private function to represent a dict combiner 231 Note: lambda are not used in order to be compatible with multiprocessing (lambda are not serializable) 232 """ 233 dicts.update(d1, d2) 234 235 236def _to_counter_supplier() -> int: 237 """ 238 private function to represent a counter supplier 239 Note: lambda are not used in order to be compatible with multiprocessing (lambda are not serializable) 240 """ 241 return 0 242 243 244def _to_counter_accumulator(i: int, value: Any) -> int: # pylint: disable=W0613 245 """ 246 private function to represent a counter accumulator 247 Note: lambda are not used in order to be compatible with multiprocessing (lambda are not serializable) 248 """ 249 return i + 1 250 251 252def _to_counter_combiner(i1: int, i2: int) -> int: 253 """ 254 private function to represent a counter combiner 255 Note: lambda are not used in order to be compatible with multiprocessing (lambda are not serializable) 256 """ 257 return i1 + i2 258 259 260def _grouping_by_accumulator( 261 dictionary: dict[K, list[V]], value: T, key_mapper: Function[T, K], value_mapper: Function[T, V] 262) -> None: 263 """ 264 private function to represent a grouping by accumulator 265 Note: lambda are not used in order to be compatible with multiprocessing (lambda are not serializable) 266 """ 267 key: Final[K] = key_mapper(value) 268 val: Final[V] = value_mapper(value) 269 values: Final[Optional[list[V]]] = dictionary.get(key) 270 if is_none(values): 271 dictionary[key] = [val] 272 else: 273 cast(list[V], values).append(val) 274 275 276def _grouping_by_combiner(dictionary_1: dict[K, list[V]], dictionary_2: dict[K, list[V]]) -> None: 277 """ 278 private function to represent a grouping by combiner 279 Note: lambda are not used in order to be compatible with multiprocessing (lambda are not serializable) 280 """ 281 require_non_none(dictionary_1) 282 for key, dictionary_2_values in require_non_none(dictionary_2).items(): 283 dictionary_1_values: Optional[list[V]] = dictionary_1.get(key) 284 if is_none(dictionary_1_values): 285 dictionary_1[key] = dictionary_2_values # no need new list ref because internal function 286 else: 287 cast(list[V], dictionary_1_values).extend(dictionary_2_values)
30@dataclass(frozen=True) 31class Collector(Generic[T, R]): 32 """ 33 Collector class: A generic dataclass to describe a collector[T,R] 34 35 Args: 36 supplier (Supplier[R]): A function that creates and returns a new result container 37 accumulator (BiFunction[R, T, R]): A function that folds a value into a result container 38 combiner (BiFunction[R, R, R]): A function that accepts two partial results and merges them 39 """ 40 supplier: Supplier[R] 41 accumulator: BiFunction[R, T, R] 42 combiner: BiFunction[R, R, R] 43 44 def __post_init__(self): 45 """ 46 Post init values checker 47 Raises: 48 TypeError: Raises a TypeError at least one parameter is None 49 """ 50 require_non_none(self.supplier) 51 require_non_none(self.accumulator) 52 require_non_none(self.combiner)
Collector class: A generic dataclass to describe a collector[T,R]
Arguments:
- supplier (Supplier[R]): A function that creates and returns a new result container
- accumulator (BiFunction[R, T, R]): A function that folds a value into a result container
- combiner (BiFunction[R, R, R]): A function that accepts two partial results and merges them
55def of(supplier: Supplier[R], accumulator: BiFunction[R, T, R], combiner: BiFunction[R, R, R]) -> Collector[T, R]: 56 """ 57 Returns a Collector by using the given parameters 58 59 Args: 60 supplier (Supplier[R]): A function that creates and returns a new result container 61 accumulator (BiFunction[R, T, R]): A function that folds a value into a result container 62 combiner (BiFunction[R, R, R]): A function that accepts two partial results and merges them 63 64 Returns: 65 collector: a collector[T,R] built with given parameters 66 67 Raises: 68 TypeError: Raises a TypeError at least one parameter is None 69 """ 70 return Collector( 71 supplier=require_non_none(supplier), 72 accumulator=require_non_none(accumulator), 73 combiner=require_non_none(combiner) 74 )
Returns a Collector by using the given parameters
Arguments:
- supplier (Supplier[R]): A function that creates and returns a new result container
- accumulator (BiFunction[R, T, R]): A function that folds a value into a result container
- combiner (BiFunction[R, R, R]): A function that accepts two partial results and merges them
Returns:
collector: a collector[T,R] built with given parameters
Raises:
- TypeError: Raises a TypeError at least one parameter is None
77def of_biconsumers(supplier: Supplier[R], accumulator: BiConsumer[R, T], combiner: BiConsumer[R, R]) -> Collector[T, R]: 78 """ 79 Returns a Collector by using the given parameters (biconsumer instead of bifunction) 80 81 Args: 82 supplier (Supplier[R]): A function that creates and returns a new result container 83 accumulator (BiConsumer[R, T]): A function that folds a value into a result container 84 combiner (BiConsumer[R, R]): A function that accepts two partial results and merges them 85 86 Returns: 87 collector: a collector[T,R] built with given parameters 88 89 Raises: 90 TypeError: Raises a TypeError at least one parameter is None 91 """ 92 return of( 93 supplier=require_non_none(supplier), 94 accumulator=_to_bifunction(accumulator), 95 combiner=_to_bifunction(combiner) 96 )
Returns a Collector by using the given parameters (biconsumer instead of bifunction)
Arguments:
- supplier (Supplier[R]): A function that creates and returns a new result container
- accumulator (BiConsumer[R, T]): A function that folds a value into a result container
- combiner (BiConsumer[R, R]): A function that accepts two partial results and merges them
Returns:
collector: a collector[T,R] built with given parameters
Raises:
- TypeError: Raises a TypeError at least one parameter is None
99def to_list() -> Collector[T, list[T]]: 100 """ 101 Returns a Collector that accumulates the input elements into a new list 102 103 Returns: 104 Collector[T, list[T]]: a Collector that accumulates the input elements into a new list 105 """ 106 return of( 107 supplier=lists.empty_list, 108 accumulator=lists.append, 109 combiner=lists.extend 110 )
Returns a Collector that accumulates the input elements into a new list
Returns:
Collector[T, list[T]]: a Collector that accumulates the input elements into a new list
113def to_dict(key_mapper: Function[T, K], value_mapper: Function[T, V]) -> Collector[T, dict[K, V]]: 114 """ 115 Returns a Collector that accumulates elements into a dict whose keys and values are the result of applying the 116 provided mapping functions to the input elements 117 118 Args: 119 key_mapper: a mapping function to produce keys 120 value_mapper: a mapping function to produce values 121 122 Returns: 123 Collector[T, dict[K, V]]: a Collector which collects elements into a dict whose keys and values are the result 124 of applying mapping functions to the input elements 125 """ 126 require_non_none(key_mapper) 127 require_non_none(value_mapper) 128 return of_biconsumers( 129 supplier=dicts.empty_dict, 130 accumulator=partial(_to_dict_accumulator, key_mapper=key_mapper, value_mapper=value_mapper), 131 combiner=_to_dict_combiner 132 )
Returns a Collector that accumulates elements into a dict whose keys and values are the result of applying the provided mapping functions to the input elements
Arguments:
- key_mapper: a mapping function to produce keys
- value_mapper: a mapping function to produce values
Returns:
Collector[T, dict[K, V]]: a Collector which collects elements into a dict whose keys and values are the result of applying mapping functions to the input elements
135def to_none() -> Collector[T, None]: 136 """ 137 Returns a Collector that always returns None 138 139 Returns: 140 Collector[T, None]: A collector that always returns None 141 """ 142 return of( 143 supplier=objects.to_none, 144 accumulator=objects.to_none, 145 combiner=objects.to_none 146 )
Returns a Collector that always returns None
Returns:
Collector[T, None]: A collector that always returns None
149def to_tuple() -> Collector[T, tuple[T, ...]]: 150 """ 151 Returns a Collector that accumulates the input elements into a new tuple 152 153 Returns: 154 Collector[T, tuple[T, ...]]: a Collector that accumulates the input elements into a new tuple 155 """ 156 return of( 157 supplier=tuples.empty_tuple, 158 accumulator=tuples.append, 159 combiner=tuples.extend 160 )
Returns a Collector that accumulates the input elements into a new tuple
Returns:
Collector[T, tuple[T, ...]]: a Collector that accumulates the input elements into a new tuple
163def to_counter() -> Collector[T, int]: 164 """ 165 Returns a Collector accepting elements of type T that counts the number of input elements 166 167 Returns: 168 Collector[T, int]: a Collector accepting elements of type T that counts the number of input elements 169 """ 170 return of( 171 supplier=_to_counter_supplier, 172 accumulator=_to_counter_accumulator, 173 combiner=_to_counter_combiner 174 )
Returns a Collector accepting elements of type T that counts the number of input elements
Returns:
Collector[T, int]: a Collector accepting elements of type T that counts the number of input elements
177def grouping_by( 178 key_mapper: Function[T, K], value_mapper: Function[T, V] = to_self # type: ignore 179) -> Collector[T, dict[K, list[V]]]: 180 """ 181 Returns a collector that groups elements into a dictionary based on the provided key mapper and value mapper 182 functions. 183 184 Args: 185 key_mapper (Function[T, K]): A function that maps an element to its key. 186 value_mapper (Function[T, V], optional): A function that maps an element to its value. Defaults to to_self. 187 188 Returns: 189 Collector[T, dict[K, list[V]]]: A collector that groups elements into a dictionary. 190 """ 191 require_non_none(key_mapper) 192 require_non_none(value_mapper) 193 return of_biconsumers( 194 supplier=dicts.empty_dict, 195 accumulator=partial(_grouping_by_accumulator, key_mapper=key_mapper, value_mapper=value_mapper), 196 combiner=_grouping_by_combiner 197 )
Returns a collector that groups elements into a dictionary based on the provided key mapper and value mapper functions.
Arguments:
- key_mapper (Function[T, K]): A function that maps an element to its key.
- value_mapper (Function[T, V], optional): A function that maps an element to its value. Defaults to to_self.
Returns:
Collector[T, dict[K, list[V]]]: A collector that groups elements into a dictionary.