problemreductions/models/misc/
paintshop.rs1use crate::registry::{FieldInfo, ProblemSchemaEntry};
9use crate::traits::{OptimizationProblem, Problem};
10use crate::types::{Direction, SolutionSize};
11use serde::{Deserialize, Serialize};
12use std::collections::{HashMap, HashSet};
13
14inventory::submit! {
15 ProblemSchemaEntry {
16 name: "PaintShop",
17 module_path: module_path!(),
18 description: "Minimize color changes in paint shop sequence",
19 fields: &[
20 FieldInfo { name: "sequence", type_name: "Vec<String>", description: "Car labels (each must appear exactly twice)" },
21 ],
22 }
23}
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct PaintShop {
50 sequence_indices: Vec<usize>,
52 car_labels: Vec<String>,
54 is_first: Vec<bool>,
56 num_cars: usize,
58}
59
60impl PaintShop {
61 pub fn new<S: AsRef<str>>(sequence: Vec<S>) -> Self {
65 let sequence: Vec<String> = sequence.iter().map(|s| s.as_ref().to_string()).collect();
66 Self::from_strings(sequence)
67 }
68
69 pub fn from_strings(sequence: Vec<String>) -> Self {
71 let mut car_count: HashMap<String, usize> = HashMap::new();
73 let mut car_to_index: HashMap<String, usize> = HashMap::new();
74 let mut car_labels: Vec<String> = Vec::new();
75
76 for item in &sequence {
77 let count = car_count.entry(item.clone()).or_insert(0);
78 if *count == 0 {
79 car_to_index.insert(item.clone(), car_labels.len());
80 car_labels.push(item.clone());
81 }
82 *count += 1;
83 }
84
85 for (car, count) in &car_count {
87 assert_eq!(
88 *count, 2,
89 "Each car must appear exactly twice, but '{}' appears {} times",
90 car, count
91 );
92 }
93
94 let sequence_indices: Vec<usize> = sequence.iter().map(|item| car_to_index[item]).collect();
96
97 let mut seen: HashSet<usize> = HashSet::new();
99 let is_first: Vec<bool> = sequence_indices
100 .iter()
101 .map(|&idx| seen.insert(idx))
102 .collect();
103
104 let num_cars = car_labels.len();
105
106 Self {
107 sequence_indices,
108 car_labels,
109 is_first,
110 num_cars,
111 }
112 }
113
114 pub fn sequence_len(&self) -> usize {
116 self.sequence_indices.len()
117 }
118
119 pub fn num_sequence(&self) -> usize {
121 self.sequence_len()
122 }
123
124 pub fn num_cars(&self) -> usize {
126 self.num_cars
127 }
128
129 pub fn car_labels(&self) -> &[String] {
131 &self.car_labels
132 }
133
134 pub fn get_coloring(&self, config: &[usize]) -> Vec<usize> {
139 self.sequence_indices
140 .iter()
141 .enumerate()
142 .map(|(i, &car_idx)| {
143 let first_color = config.get(car_idx).copied().unwrap_or(0);
144 if self.is_first[i] {
145 first_color
146 } else {
147 1 - first_color }
149 })
150 .collect()
151 }
152
153 pub fn count_switches(&self, config: &[usize]) -> usize {
155 let coloring = self.get_coloring(config);
156 coloring.windows(2).filter(|w| w[0] != w[1]).count()
157 }
158}
159
160#[cfg(test)]
162pub(crate) fn count_paint_switches(coloring: &[usize]) -> usize {
163 coloring.windows(2).filter(|w| w[0] != w[1]).count()
164}
165
166impl Problem for PaintShop {
167 const NAME: &'static str = "PaintShop";
168 type Metric = SolutionSize<i32>;
169
170 fn dims(&self) -> Vec<usize> {
171 vec![2; self.num_cars]
172 }
173
174 fn evaluate(&self, config: &[usize]) -> SolutionSize<i32> {
175 SolutionSize::Valid(self.count_switches(config) as i32)
177 }
178
179 fn variant() -> Vec<(&'static str, &'static str)> {
180 crate::variant_params![]
181 }
182}
183
184impl OptimizationProblem for PaintShop {
185 type Value = i32;
186
187 fn direction(&self) -> Direction {
188 Direction::Minimize
189 }
190}
191
192crate::declare_variants! {
193 PaintShop => "2^num_cars",
194}
195
196#[cfg(test)]
197#[path = "../../unit_tests/models/misc/paintshop.rs"]
198mod tests;