Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/rich/_ratio.py: 16%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

70 statements  

1from fractions import Fraction 

2from math import ceil 

3from typing import cast, List, Optional, Sequence, Protocol 

4 

5 

6class Edge(Protocol): 

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

8 

9 size: Optional[int] = None 

10 ratio: int = 1 

11 minimum_size: int = 1 

12 

13 

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

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

16 

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

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

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

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

21 clip the rows that would overflow the screen height. 

22 

23 Args: 

24 total (int): Total number of characters. 

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

26 

27 Returns: 

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

29 """ 

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

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

32 

33 _Fraction = Fraction 

34 

35 # While any edges haven't been calculated 

36 while None in sizes: 

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

38 flexible_edges = [ 

39 (index, edge) 

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

41 if size is None 

42 ] 

43 # Remaining space in total 

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

45 if remaining <= 0: 

46 # No room for flexible edges 

47 return [ 

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

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

50 ] 

51 # Calculate number of characters in a ratio portion 

52 portion = _Fraction( 

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

54 ) 

55 

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

57 for index, edge in flexible_edges: 

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

59 sizes[index] = edge.minimum_size 

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

61 break 

62 else: 

63 # Distribute flexible space and compensate for rounding error 

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

65 # to the following line 

66 remainder = _Fraction(0) 

67 for index, edge in flexible_edges: 

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

69 sizes[index] = size 

70 break 

71 # Sizes now contains integers only 

72 return cast(List[int], sizes) 

73 

74 

75def ratio_reduce( 

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

77) -> List[int]: 

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

79 

80 Args: 

81 total (int): The total to divide. 

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

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

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

85 

86 Returns: 

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

88 """ 

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

90 total_ratio = sum(ratios) 

91 if not total_ratio: 

92 return values[:] 

93 total_remaining = total 

94 result: List[int] = [] 

95 append = result.append 

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

97 if ratio and total_ratio > 0: 

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

99 append(value - distributed) 

100 total_remaining -= distributed 

101 total_ratio -= ratio 

102 else: 

103 append(value) 

104 return result 

105 

106 

107def ratio_distribute( 

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

109) -> List[int]: 

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

111 

112 Args: 

113 total (int): The total to divide. 

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

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

116 

117 Returns: 

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

119 """ 

120 if minimums: 

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

122 total_ratio = sum(ratios) 

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

124 

125 total_remaining = total 

126 distributed_total: List[int] = [] 

127 append = distributed_total.append 

128 if minimums is None: 

129 _minimums = [0] * len(ratios) 

130 else: 

131 _minimums = minimums 

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

133 if total_ratio > 0: 

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

135 else: 

136 distributed = total_remaining 

137 append(distributed) 

138 total_ratio -= ratio 

139 total_remaining -= distributed 

140 return distributed_total 

141 

142 

143if __name__ == "__main__": 

144 from dataclasses import dataclass 

145 

146 @dataclass 

147 class E: 

148 size: Optional[int] = None 

149 ratio: int = 1 

150 minimum_size: int = 1 

151 

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

153 print(sum(resolved))