1#[derive(Debug, Clone, Copy)]
11pub struct Edge {
12 pub size: Option<usize>,
14 pub ratio: usize,
16 pub minimum_size: usize,
18}
19
20impl Edge {
21 pub fn new(size: Option<usize>, ratio: usize, minimum_size: usize) -> Self {
22 Edge {
23 size,
24 ratio,
25 minimum_size,
26 }
27 }
28}
29
30pub fn ratio_resolve(total: usize, edges: &[Edge]) -> Vec<usize> {
37 let mut sizes: Vec<Option<usize>> = edges
39 .iter()
40 .map(|e| e.size.filter(|&size| size > 0))
41 .collect();
42
43 while sizes.iter().any(Option::is_none) {
45 let flexible: Vec<usize> = sizes
46 .iter()
47 .enumerate()
48 .filter(|(_, s)| s.is_none())
49 .map(|(i, _)| i)
50 .collect();
51
52 let fixed_sum: i128 = sizes.iter().flatten().map(|&s| s as i128).sum();
53 let remaining = total as i128 - fixed_sum;
54 if remaining <= 0 {
55 return sizes
57 .iter()
58 .zip(edges)
59 .map(|(size, edge)| match size {
60 Some(s) => *s,
61 None => edge.minimum_size.max(1),
62 })
63 .collect();
64 }
65
66 let remaining = remaining as u128;
69 let ratio_sum: u128 = flexible
70 .iter()
71 .map(|&i| edges[i].ratio.max(1) as u128)
72 .fold(0, u128::saturating_add);
73
74 let mut pinned = false;
78 for &i in &flexible {
79 let edge = &edges[i];
80 if remaining * edge.ratio as u128
81 <= (edge.minimum_size as u128).saturating_mul(ratio_sum)
82 {
83 sizes[i] = Some(edge.minimum_size);
84 pinned = true;
85 break;
86 }
87 }
88 if !pinned {
89 let mut remainder = 0u128;
92 for &i in &flexible {
93 let value = (remaining * edges[i].ratio as u128).saturating_add(remainder);
94 sizes[i] = Some(usize::try_from(value / ratio_sum).unwrap_or(usize::MAX));
95 remainder = value % ratio_sum;
96 }
97 break;
98 }
99 }
100
101 sizes.into_iter().map(|s| s.unwrap_or(0)).collect()
102}
103
104pub fn ratio_reduce(total: i64, ratios: &[i64], maximums: &[i64], values: &[i64]) -> Vec<i64> {
108 let ratios: Vec<i64> = ratios
109 .iter()
110 .zip(maximums)
111 .map(|(&r, &m)| if m != 0 { r } else { 0 })
112 .collect();
113 let mut total_ratio: i128 = ratios.iter().map(|&r| i128::from(r)).sum();
114 if total_ratio == 0 {
115 return values.to_vec();
116 }
117 let mut total_remaining = total;
118 let mut result = Vec::with_capacity(values.len());
119 for ((&ratio, &maximum), &value) in ratios.iter().zip(maximums).zip(values) {
120 if ratio != 0 && total_ratio > 0 {
121 let distributed = maximum.min(
124 (ratio as f64 * total_remaining as f64 / total_ratio as f64).round_ties_even()
125 as i64,
126 );
127 result.push(value - distributed);
128 total_remaining -= distributed;
129 total_ratio -= i128::from(ratio);
130 } else {
131 result.push(value);
132 }
133 }
134 result
135}
136
137pub fn ratio_distribute(total: i64, ratios: &[i64], minimums: Option<&[i64]>) -> Vec<i64> {
142 let ratios: Vec<i64> = match minimums {
144 Some(mins) => ratios
145 .iter()
146 .zip(mins)
147 .map(|(&r, &m)| if m != 0 { r } else { 0 })
148 .collect(),
149 None => ratios.to_vec(),
150 };
151 let mut total_ratio: i128 = ratios.iter().map(|&r| i128::from(r)).sum();
154 let mut total_remaining = i128::from(total);
155 let mut result = Vec::with_capacity(ratios.len());
156 for (index, &ratio) in ratios.iter().enumerate() {
157 let ratio = i128::from(ratio);
158 let minimum = i128::from(minimums.map_or(0, |m| m[index]));
159 let distributed = if total_ratio > 0 {
160 let numerator = ratio * total_remaining;
163 minimum.max(-(-numerator).div_euclid(total_ratio))
164 } else {
165 total_remaining
166 };
167 result.push(i64::try_from(distributed).unwrap_or(if distributed < 0 {
168 i64::MIN
169 } else {
170 i64::MAX
171 }));
172 total_ratio -= ratio;
173 total_remaining -= distributed;
174 }
175 result
176}
177
178#[cfg(test)]
179mod tests {
180 use super::*;
181
182 fn edges(specs: &[(Option<usize>, usize)]) -> Vec<Edge> {
183 specs
184 .iter()
185 .map(|&(size, ratio)| Edge::new(size, ratio, 1))
186 .collect()
187 }
188
189 #[test]
190 fn even_split_carries_remainder() {
191 assert_eq!(
193 ratio_resolve(23, &edges(&[(None, 1), (None, 1)])),
194 vec![11, 12]
195 );
196 }
197
198 #[test]
199 fn even_split_exact() {
200 assert_eq!(
201 ratio_resolve(24, &edges(&[(None, 1), (None, 1)])),
202 vec![12, 12]
203 );
204 }
205
206 #[test]
207 fn fixed_and_flex() {
208 assert_eq!(
210 ratio_resolve(24, &edges(&[(None, 3), (Some(5), 1)])),
211 vec![19, 5]
212 );
213 }
214
215 #[test]
216 fn ratio_weighting() {
217 assert_eq!(
219 ratio_resolve(24, &edges(&[(None, 3), (None, 1)])),
220 vec![18, 6]
221 );
222 }
223}