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

1""" 

2Basket_case lib 

3""" 

4 

5from collections import deque 

6import copy 

7from enum import Enum 

8import itertools as it 

9from typing import Iterator 

10 

11 

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 

23 

24 

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. 

32 

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 

39 

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. 

43 

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 

51 

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) 

55 

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