1#[derive(Debug, Clone, Copy)]
10pub struct Edge {
11 pub size: Option<usize>,
13 pub ratio: usize,
15 pub minimum_size: usize,
17}
18
19impl Edge {
20 pub fn new(size: Option<usize>, ratio: usize, minimum_size: usize) -> Self {
21 Edge {
22 size,
23 ratio,
24 minimum_size,
25 }
26 }
27}
28
29pub fn ratio_resolve(total: usize, edges: &[Edge]) -> Vec<usize> {
33 let total = total as f64;
34 let mut sizes: Vec<Option<usize>> = edges.iter().map(|e| e.size).collect();
35
36 while sizes.iter().any(Option::is_none) {
38 let flexible: Vec<usize> = sizes
39 .iter()
40 .enumerate()
41 .filter(|(_, s)| s.is_none())
42 .map(|(i, _)| i)
43 .collect();
44
45 let fixed_sum: f64 = sizes.iter().flatten().map(|&s| s as f64).sum();
46 let remaining = total - fixed_sum;
47 if remaining <= 0.0 {
48 return sizes
50 .iter()
51 .zip(edges)
52 .map(|(size, edge)| match size {
53 Some(s) => *s,
54 None => edge.minimum_size.max(1),
55 })
56 .collect();
57 }
58
59 let ratio_sum: f64 = flexible.iter().map(|&i| edges[i].ratio.max(1) as f64).sum();
60 let portion = remaining / ratio_sum;
61
62 let mut pinned = false;
65 for &i in &flexible {
66 if portion * edges[i].ratio.max(1) as f64 <= edges[i].minimum_size as f64 {
67 sizes[i] = Some(edges[i].minimum_size);
68 pinned = true;
69 break;
70 }
71 }
72 if !pinned {
73 let mut remainder = 0.0;
76 for &i in &flexible {
77 let value = portion * edges[i].ratio.max(1) as f64 + remainder;
78 let size = value.floor();
79 remainder = value - size;
80 sizes[i] = Some(size as usize);
81 }
82 break;
83 }
84 }
85
86 sizes.into_iter().map(|s| s.unwrap_or(0)).collect()
87}
88
89#[cfg(test)]
90mod tests {
91 use super::*;
92
93 fn edges(specs: &[(Option<usize>, usize)]) -> Vec<Edge> {
94 specs
95 .iter()
96 .map(|&(size, ratio)| Edge::new(size, ratio, 1))
97 .collect()
98 }
99
100 #[test]
101 fn even_split_carries_remainder() {
102 assert_eq!(
104 ratio_resolve(23, &edges(&[(None, 1), (None, 1)])),
105 vec![11, 12]
106 );
107 }
108
109 #[test]
110 fn even_split_exact() {
111 assert_eq!(
112 ratio_resolve(24, &edges(&[(None, 1), (None, 1)])),
113 vec![12, 12]
114 );
115 }
116
117 #[test]
118 fn fixed_and_flex() {
119 assert_eq!(
121 ratio_resolve(24, &edges(&[(None, 3), (Some(5), 1)])),
122 vec![19, 5]
123 );
124 }
125
126 #[test]
127 fn ratio_weighting() {
128 assert_eq!(
130 ratio_resolve(24, &edges(&[(None, 3), (None, 1)])),
131 vec![18, 6]
132 );
133 }
134}