problemreductions/models/specialized/
paintshop.rs1use crate::graph_types::SimpleGraph;
9use crate::traits::Problem;
10use crate::types::{EnergyMode, ProblemSize, SolutionSize};
11use serde::{Deserialize, Serialize};
12use std::collections::{HashMap, HashSet};
13
14#[derive(Debug, Clone, Serialize, Deserialize)]
38pub struct PaintShop {
39 sequence_indices: Vec<usize>,
41 car_labels: Vec<String>,
43 is_first: Vec<bool>,
45 num_cars: usize,
47}
48
49impl PaintShop {
50 pub fn new<S: AsRef<str>>(sequence: Vec<S>) -> Self {
54 let sequence: Vec<String> = sequence.iter().map(|s| s.as_ref().to_string()).collect();
55 Self::from_strings(sequence)
56 }
57
58 pub fn from_strings(sequence: Vec<String>) -> Self {
60 let mut car_count: HashMap<String, usize> = HashMap::new();
62 let mut car_to_index: HashMap<String, usize> = HashMap::new();
63 let mut car_labels: Vec<String> = Vec::new();
64
65 for item in &sequence {
66 let count = car_count.entry(item.clone()).or_insert(0);
67 if *count == 0 {
68 car_to_index.insert(item.clone(), car_labels.len());
69 car_labels.push(item.clone());
70 }
71 *count += 1;
72 }
73
74 for (car, count) in &car_count {
76 assert_eq!(
77 *count, 2,
78 "Each car must appear exactly twice, but '{}' appears {} times",
79 car, count
80 );
81 }
82
83 let sequence_indices: Vec<usize> = sequence
85 .iter()
86 .map(|item| car_to_index[item])
87 .collect();
88
89 let mut seen: HashSet<usize> = HashSet::new();
91 let is_first: Vec<bool> = sequence_indices
92 .iter()
93 .map(|&idx| seen.insert(idx))
94 .collect();
95
96 let num_cars = car_labels.len();
97
98 Self {
99 sequence_indices,
100 car_labels,
101 is_first,
102 num_cars,
103 }
104 }
105
106 pub fn sequence_len(&self) -> usize {
108 self.sequence_indices.len()
109 }
110
111 pub fn num_cars(&self) -> usize {
113 self.num_cars
114 }
115
116 pub fn car_labels(&self) -> &[String] {
118 &self.car_labels
119 }
120
121 pub fn get_coloring(&self, config: &[usize]) -> Vec<usize> {
126 self.sequence_indices
127 .iter()
128 .enumerate()
129 .map(|(i, &car_idx)| {
130 let first_color = config.get(car_idx).copied().unwrap_or(0);
131 if self.is_first[i] {
132 first_color
133 } else {
134 1 - first_color }
136 })
137 .collect()
138 }
139
140 pub fn count_switches(&self, config: &[usize]) -> usize {
142 let coloring = self.get_coloring(config);
143 coloring.windows(2).filter(|w| w[0] != w[1]).count()
144 }
145}
146
147impl Problem for PaintShop {
148 const NAME: &'static str = "PaintShop";
149 type GraphType = SimpleGraph;
150 type Weight = i32;
151 type Size = i32;
152
153 fn num_variables(&self) -> usize {
154 self.num_cars
155 }
156
157 fn num_flavors(&self) -> usize {
158 2 }
160
161 fn problem_size(&self) -> ProblemSize {
162 ProblemSize::new(vec![
163 ("num_cars", self.num_cars),
164 ("sequence_length", self.sequence_indices.len()),
165 ])
166 }
167
168 fn energy_mode(&self) -> EnergyMode {
169 EnergyMode::SmallerSizeIsBetter }
171
172 fn solution_size(&self, config: &[usize]) -> SolutionSize<Self::Size> {
173 let switches = self.count_switches(config) as i32;
174 SolutionSize::valid(switches)
176 }
177}
178
179pub fn count_paint_switches(coloring: &[usize]) -> usize {
181 coloring.windows(2).filter(|w| w[0] != w[1]).count()
182}
183
184#[cfg(test)]
185mod tests {
186 use super::*;
187 use crate::solvers::{BruteForce, Solver};
188
189 #[test]
190 fn test_paintshop_creation() {
191 let problem = PaintShop::new(vec!["a", "b", "a", "b"]);
192 assert_eq!(problem.num_cars(), 2);
193 assert_eq!(problem.sequence_len(), 4);
194 assert_eq!(problem.num_variables(), 2);
195 assert_eq!(problem.num_flavors(), 2);
196 }
197
198 #[test]
199 fn test_is_first() {
200 let problem = PaintShop::new(vec!["a", "b", "a", "b"]);
201 assert_eq!(problem.is_first, vec![true, true, false, false]);
204 }
205
206 #[test]
207 fn test_get_coloring() {
208 let problem = PaintShop::new(vec!["a", "b", "a", "b"]);
209 let coloring = problem.get_coloring(&[0, 1]);
212 assert_eq!(coloring, vec![0, 1, 1, 0]);
213
214 let coloring = problem.get_coloring(&[1, 0]);
216 assert_eq!(coloring, vec![1, 0, 0, 1]);
217 }
218
219 #[test]
220 fn test_count_switches() {
221 let problem = PaintShop::new(vec!["a", "b", "a", "b"]);
222
223 assert_eq!(problem.count_switches(&[0, 1]), 2);
225
226 assert_eq!(problem.count_switches(&[0, 0]), 1);
228
229 assert_eq!(problem.count_switches(&[1, 1]), 1);
231 }
232
233 #[test]
234 fn test_solution_size() {
235 let problem = PaintShop::new(vec!["a", "b", "a", "b"]);
236
237 let sol = problem.solution_size(&[0, 0]);
238 assert!(sol.is_valid);
239 assert_eq!(sol.size, 1);
240
241 let sol = problem.solution_size(&[0, 1]);
242 assert!(sol.is_valid);
243 assert_eq!(sol.size, 2);
244 }
245
246 #[test]
247 fn test_brute_force_simple() {
248 let problem = PaintShop::new(vec!["a", "b", "a", "b"]);
249 let solver = BruteForce::new();
250
251 let solutions = solver.find_best(&problem);
252 for sol in &solutions {
254 assert_eq!(problem.count_switches(sol), 1);
255 }
256 }
257
258 #[test]
259 fn test_brute_force_longer() {
260 let problem = PaintShop::new(vec!["a", "b", "a", "c", "c", "b"]);
262 let solver = BruteForce::new();
263
264 let solutions = solver.find_best(&problem);
265 let min_switches = problem.count_switches(&solutions[0]);
267 for sol in &solutions {
268 assert_eq!(problem.count_switches(sol), min_switches);
269 }
270 }
271
272 #[test]
273 fn test_count_paint_switches_function() {
274 assert_eq!(count_paint_switches(&[0, 0, 0]), 0);
275 assert_eq!(count_paint_switches(&[0, 1, 0]), 2);
276 assert_eq!(count_paint_switches(&[0, 0, 1, 1]), 1);
277 assert_eq!(count_paint_switches(&[0, 1, 0, 1]), 3);
278 }
279
280 #[test]
281 fn test_energy_mode() {
282 let problem = PaintShop::new(vec!["a", "a"]);
283 assert!(problem.energy_mode().is_minimization());
284 }
285
286 #[test]
287 fn test_problem_size() {
288 let problem = PaintShop::new(vec!["a", "b", "c", "a", "b", "c"]);
289 let size = problem.problem_size();
290 assert_eq!(size.get("num_cars"), Some(3));
291 assert_eq!(size.get("sequence_length"), Some(6));
292 }
293
294 #[test]
295 fn test_single_car() {
296 let problem = PaintShop::new(vec!["a", "a"]);
297 let solver = BruteForce::new();
298
299 let solutions = solver.find_best(&problem);
300 assert_eq!(solutions.len(), 2);
302 for sol in &solutions {
303 assert_eq!(problem.count_switches(sol), 1);
304 }
305 }
306
307 #[test]
308 fn test_adjacent_same_car() {
309 let problem = PaintShop::new(vec!["a", "a", "b", "b"]);
311 let solver = BruteForce::new();
312
313 let solutions = solver.find_best(&problem);
314 let min_switches = problem.count_switches(&solutions[0]);
318 assert!(min_switches <= 3);
319 }
320
321 #[test]
322 #[should_panic]
323 fn test_invalid_sequence_single_occurrence() {
324 let _ = PaintShop::new(vec!["a", "b", "a", "c"]);
326 }
327
328 #[test]
329 fn test_car_labels() {
330 let problem = PaintShop::new(vec!["car1", "car2", "car1", "car2"]);
331 assert_eq!(problem.car_labels().len(), 2);
332 }
333}