Coverage for /run/media/veganeco/water/status600.com/pypi/reptilian_climates/modules_series_2/ships/fields/gardens_pip/rich/_ratio.py: 19%

1 import sys

2 from fractions import Fraction

3 from math import ceil

4 from typing import cast, List, Optional, Sequence

5

6 if sys.version_info >= (3, 8):

7 from typing import Protocol

8 else:

9 from typing_extensions import Protocol # pragma: no cover

10

11

12 class Edge(Protocol):

13 """Any object that defines an edge (such as Layout)."""

14

15 size: Optional[int] = None

16 ratio: int = 1

17 minimum_size: int = 1

18

19

20 def ratio_resolve(total: int, edges: Sequence[Edge]) -> List[int]:

21 """Divide total space to satisfy size, ratio, and minimum_size, constraints.

22

23 The returned list of integers should add up to total in most cases, unless it is

24 impossible to satisfy all the constraints. For instance, if there are two edges

25 with a minimum size of 20 each and `total` is 30 then the returned list will be

26 greater than total. In practice, this would mean that a Layout object would

27 clip the rows that would overflow the screen height.

28

29 Args:

30 total (int): Total number of characters.

31 edges (List[Edge]): Edges within total space.

32

33 Returns:

34 List[int]: Number of characters for each edge.

35 """

36 # Size of edge or None for yet to be determined

37 sizes = [(edge.size or None) for edge in edges]

38

39 _Fraction = Fraction

40

41 # While any edges haven't been calculated

42 while None in sizes:

43 # Get flexible edges and index to map these back on to sizes list

44 flexible_edges = [

45 (index, edge)

46 for index, (size, edge) in enumerate(zip(sizes, edges))

47 if size is None

48 ]

49 # Remaining space in total

50 remaining = total - sum(size or 0 for size in sizes)

51 if remaining <= 0:

52 # No room for flexible edges

53 return [

54 ((edge.minimum_size or 1) if size is None else size)

55 for size, edge in zip(sizes, edges)

56 ]

57 # Calculate number of characters in a ratio portion

58 portion = _Fraction(

59 remaining, sum((edge.ratio or 1) for _, edge in flexible_edges)

60 )

61

62 # If any edges will be less than their minimum, replace size with the minimum

63 for index, edge in flexible_edges:

64 if portion * edge.ratio <= edge.minimum_size:

65 sizes[index] = edge.minimum_size

66 # New fixed size will invalidate calculations, so we need to repeat the process

67 break

68 else:

69 # Distribute flexible space and compensate for rounding error

70 # Since edge sizes can only be integers we need to add the remainder

71 # to the following line

72 remainder = _Fraction(0)

73 for index, edge in flexible_edges:

74 size, remainder = divmod(portion * edge.ratio + remainder, 1)

75 sizes[index] = size

76 break

77 # Sizes now contains integers only

78 return cast(List[int], sizes)

79

80

81 def ratio_reduce(

82 total: int, ratios: List[int], maximums: List[int], values: List[int]

83) -> List[int]:

84 """Divide an integer total in to parts based on ratios.

85

86 Args:

87 total (int): The total to divide.

88 ratios (List[int]): A list of integer ratios.

89 maximums (List[int]): List of maximums values for each slot.

90 values (List[int]): List of values

91

92 Returns:

93 List[int]: A list of integers guaranteed to sum to total.

94 """

95 ratios = [ratio if _max else 0 for ratio, _max in zip(ratios, maximums)]

96 total_ratio = sum(ratios)

97 if not total_ratio:

98 return values[:]

99 total_remaining = total

100 result: List[int] = []

101 append = result.append

102 for ratio, maximum, value in zip(ratios, maximums, values):

103 if ratio and total_ratio > 0:

104 distributed = min(maximum, round(ratio * total_remaining / total_ratio))

105 append(value - distributed)

106 total_remaining -= distributed

107 total_ratio -= ratio

108 else:

109 append(value)

110 return result

111

112

113 def ratio_distribute(

114 total: int, ratios: List[int], minimums: Optional[List[int]] = None

115) -> List[int]:

116 """Distribute an integer total in to parts based on ratios.

117

118 Args:

119 total (int): The total to divide.

120 ratios (List[int]): A list of integer ratios.

121 minimums (List[int]): List of minimum values for each slot.

122

123 Returns:

124 List[int]: A list of integers guaranteed to sum to total.

125 """

126 if minimums:

127 ratios = [ratio if _min else 0 for ratio, _min in zip(ratios, minimums)]

128 total_ratio = sum(ratios)

129 assert total_ratio > 0, "Sum of ratios must be > 0"

130

131 total_remaining = total

132 distributed_total: List[int] = []

133 append = distributed_total.append

134 if minimums is None:

135 _minimums = [0] * len(ratios)

136 else:

137 _minimums = minimums

138 for ratio, minimum in zip(ratios, _minimums):

139 if total_ratio > 0:

140 distributed = max(minimum, ceil(ratio * total_remaining / total_ratio))

141 else:

142 distributed = total_remaining

143 append(distributed)

144 total_ratio -= ratio

145 total_remaining -= distributed

146 return distributed_total

147

148

149 if __name__ == "__main__":

150 from dataclasses import dataclass

151

152 @dataclass

153 class E:

154 size: Optional[int] = None

155 ratio: int = 1

156 minimum_size: int = 1

157

158 resolved = ratio_resolve(110, [E(None, 1, 1), E(None, 1, 1), E(None, 1, 1)])

159 print(sum(resolved))