Coverage for src/basket_case/basket.py: 100%
34 statements
« prev ^ index » next coverage.py v7.4.1, created at 2024-02-18 17:55 +0100
« prev ^ index » next coverage.py v7.4.1, created at 2024-02-18 17:55 +0100
1"""
2Basket_case lib
3"""
5from collections import deque
6import copy
7from enum import Enum
8import itertools as it
9from typing import Iterator
12class PredefinedSizes(Enum):
13 """Helper values for common basket sizes
14 """
15 cd = 737280000
16 dvd = 4707319808
17 dvd_dl = 8543666176
18 kb = 2**10
19 mb = 2**20
20 gb = 2**30
21 tb = 2**40
22 pb = 2**50
25def fit_objects_into_baskets(
26 objects: dict[str: int],
27 basket_size: int,
28 ignore_oversize: bool=False,
29 preserve_input: bool=True,
30) -> Iterator[dict[str, int]]:
31 """Group the given objects into several baskets, maximising the room taken in each basket.
33 Args:
34 objects (dict[str: int]): each object expressed as name: size.
35 Note: the size of an object can be zero (useful for files of zero length)
36 basket_size (int): the size of the basket. All baskets have the same size.
37 preserve_input (bool, optional): if True (default) do not corrupt the input (slower, more memory)
38 ignore_oversize (bool, optional): if True ignore the oversize objects instead of raising ValueError
40 Returns:
41 Iterator[dict[str, int]]: each dict yielded represents a basket with its objects
42 The number of resulting baskets depends on the cumulative size of the objects.
44 Raises:
45 ValueError if any object is too big to fit in the basket. All such objects are made
46 available in the exception's args attibute, as a dict.
47 Can be suppressed with ignore_oversize=True
48 """
49 if not objects:
50 return
52 oversize_objects = {name: size for name, size in objects.items() if size>basket_size}
53 if oversize_objects and not ignore_oversize:
54 raise ValueError(oversize_objects)
56 consume = deque(maxlen=0).extend
57 if preserve_input:
58 objects = copy.copy(objects) # make a shallow copy of the input
59 consume(objects.pop(name, None) for name in oversize_objects) # remove the oversize objs
60 while objects:
61 best_comb_size = 0
62 for k in range(1, len(objects)+1):
63 for comb in it.combinations(objects, k):
64 comb_size = sum(objects[name] for name in comb)
65 if best_comb_size <= comb_size <= basket_size:
66 best_comb_size = comb_size # update max size found
67 best_comb = comb # save combination
68 yield {name: objects[name] for name in best_comb} # yield a basket
69 consume(objects.pop(name, None) for name in best_comb) # remove the objs in the comb