|
32 | 32 | # stdlib
|
33 | 33 | import itertools
|
34 | 34 | import textwrap
|
| 35 | +from operator import itemgetter |
35 | 36 | from typing import Any, Callable, Generator, Iterable, Iterator, List, Optional, Sequence, Tuple, Type, Union
|
36 | 37 |
|
37 | 38 | # 3rd party
|
38 | 39 | from natsort import natsorted, ns # type: ignore
|
39 | 40 |
|
| 41 | +# this package |
| 42 | +from domdf_python_tools.utils import magnitude |
| 43 | + |
40 | 44 | __all__ = [
|
41 | 45 | "chunks",
|
42 | 46 | "permutations",
|
|
47 | 51 | "make_tree",
|
48 | 52 | "natmin",
|
49 | 53 | "natmax",
|
| 54 | + "groupfloats", |
| 55 | + "ranges_from_iterable", |
50 | 56 | ]
|
51 | 57 |
|
52 | 58 |
|
@@ -250,3 +256,69 @@ def natmax(seq: Iterable, key: Optional[Callable] = None, alg: int = ns.DEFAULT)
|
250 | 256 | """
|
251 | 257 |
|
252 | 258 | return natsorted(seq, key=key, alg=alg)[-1]
|
| 259 | + |
| 260 | + |
| 261 | +_group = Tuple[float, ...] |
| 262 | + |
| 263 | + |
| 264 | +def groupfloats( |
| 265 | + iterable: Iterable[float], |
| 266 | + step: float = 1, |
| 267 | + ) -> Iterable[_group]: |
| 268 | + """ |
| 269 | + Returns an iterator over the discrete ranges of values in ``iterable``. |
| 270 | +
|
| 271 | + For example: |
| 272 | +
|
| 273 | + .. code-block:: python |
| 274 | +
|
| 275 | + >>> list(groupfloats([170.0, 170.05, 170.1, 170.15, 171.05, 171.1, 171.15, 171.2], step=0.05)) |
| 276 | + [(170.0, 170.05, 170.1, 170.15), (171.05, 171.1, 171.15, 171.2)] |
| 277 | + >>> list(groupfloats([1, 2, 3, 4, 5, 7, 8, 9, 10])) |
| 278 | + [(1, 2, 3, 4, 5), (7, 8, 9, 10)] |
| 279 | +
|
| 280 | + :param iterable: |
| 281 | + :param step: The step between values in ``iterable``. |
| 282 | +
|
| 283 | + .. seealso:: |
| 284 | +
|
| 285 | + :func:`~.ranges_from_iterable`, which returns an iterator over the min and max values for each range. |
| 286 | +
|
| 287 | + .. versionadded:: 2.0.0 |
| 288 | + """ |
| 289 | + |
| 290 | + # Based on https://stackoverflow.com/a/4629241 |
| 291 | + # By user97370 |
| 292 | + # CC BY-SA 4.0 |
| 293 | + |
| 294 | + modifier = 1 / 10**magnitude(step) |
| 295 | + |
| 296 | + a: float |
| 297 | + b: Iterable[_group] |
| 298 | + |
| 299 | + def key(pair): |
| 300 | + return (pair[1] * modifier) - ((pair[0] * modifier) * step) |
| 301 | + |
| 302 | + for a, b in itertools.groupby(enumerate(iterable), key=key): |
| 303 | + yield tuple(map(itemgetter(1), list(b))) |
| 304 | + |
| 305 | + |
| 306 | +def ranges_from_iterable(iterable: Iterable[float], step: float = 1) -> Iterable[Tuple[float, float]]: |
| 307 | + """ |
| 308 | + Returns an iterator over the minimum and maximum values for each discrete ranges of values in ``iterable``. |
| 309 | +
|
| 310 | + For example: |
| 311 | +
|
| 312 | + .. code-block:: python |
| 313 | +
|
| 314 | + >>> list(ranges_from_iterable([170.0, 170.05, 170.1, 170.15, 171.05, 171.1, 171.15, 171.2], step=0.05)) |
| 315 | + [(170.0, 170.15), (171.05, 171.2)] |
| 316 | + >>> list(ranges_from_iterable([1, 2, 3, 4, 5, 7, 8, 9, 10])) |
| 317 | + [(1, 5), (7, 10)] |
| 318 | +
|
| 319 | + :param iterable: |
| 320 | + :param step: The step between values in ``iterable``. |
| 321 | + """ |
| 322 | + |
| 323 | + for group in groupfloats(iterable, step): |
| 324 | + yield group[0], group[-1] |
0 commit comments