problemreductions/models/misc/
paintshop.rs1use crate::registry::{FieldInfo, ProblemSchemaEntry};
9use crate::traits::Problem;
10use crate::types::Min;
11use serde::{Deserialize, Serialize};
12use std::collections::{HashMap, HashSet};
13
14inventory::submit! {
15 ProblemSchemaEntry {
16 name: "PaintShop",
17 display_name: "Paint Shop",
18 aliases: &[],
19 dimensions: &[],
20 category: crate::registry::ProblemCategory::Misc,
21 module_path: module_path!(),
22 description: "Minimize color changes in paint shop sequence",
23 fields: &[
24 FieldInfo { name: "sequence", type_name: "Vec<String>", description: "Car labels (each must appear exactly twice)" },
25 ],
26 }
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct PaintShop {
54 sequence_indices: Vec<usize>,
56 car_labels: Vec<String>,
58 is_first: Vec<bool>,
60 num_cars: usize,
62}
63
64impl PaintShop {
65 pub fn new<S: AsRef<str>>(sequence: Vec<S>) -> Self {
69 let sequence: Vec<String> = sequence.iter().map(|s| s.as_ref().to_string()).collect();
70 Self::from_strings(sequence)
71 }
72
73 pub fn from_strings(sequence: Vec<String>) -> Self {
75 let mut car_count: HashMap<String, usize> = HashMap::new();
77 let mut car_to_index: HashMap<String, usize> = HashMap::new();
78 let mut car_labels: Vec<String> = Vec::new();
79
80 for item in &sequence {
81 let count = car_count.entry(item.clone()).or_insert(0);
82 if *count == 0 {
83 car_to_index.insert(item.clone(), car_labels.len());
84 car_labels.push(item.clone());
85 }
86 *count += 1;
87 }
88
89 for (car, count) in &car_count {
91 assert_eq!(
92 *count, 2,
93 "Each car must appear exactly twice, but '{}' appears {} times",
94 car, count
95 );
96 }
97
98 let sequence_indices: Vec<usize> = sequence.iter().map(|item| car_to_index[item]).collect();
100
101 let mut seen: HashSet<usize> = HashSet::new();
103 let is_first: Vec<bool> = sequence_indices
104 .iter()
105 .map(|&idx| seen.insert(idx))
106 .collect();
107
108 let num_cars = car_labels.len();
109
110 Self {
111 sequence_indices,
112 car_labels,
113 is_first,
114 num_cars,
115 }
116 }
117
118 pub fn sequence_len(&self) -> usize {
120 self.sequence_indices.len()
121 }
122
123 pub fn num_sequence(&self) -> usize {
125 self.sequence_len()
126 }
127
128 pub fn num_cars(&self) -> usize {
130 self.num_cars
131 }
132
133 pub fn car_labels(&self) -> &[String] {
135 &self.car_labels
136 }
137
138 pub fn sequence_indices(&self) -> &[usize] {
140 &self.sequence_indices
141 }
142
143 pub fn is_first(&self) -> &[bool] {
145 &self.is_first
146 }
147
148 pub fn get_coloring(
153 &self,
154 config: &[bool],
155 ) -> Result<Vec<bool>, crate::traits::EvaluationError> {
156 if config.len() != self.num_cars {
157 return Err(crate::traits::EvaluationError::InvalidConfiguration(
158 "paint assignment length does not match the cars".into(),
159 ));
160 }
161 Ok(self
162 .sequence_indices
163 .iter()
164 .enumerate()
165 .map(|(i, &car_idx)| {
166 let first_color = config[car_idx];
167 if self.is_first[i] {
168 first_color
169 } else {
170 !first_color }
172 })
173 .collect())
174 }
175
176 pub fn count_switches(&self, config: &[bool]) -> Result<i64, crate::traits::EvaluationError> {
178 let coloring = self.get_coloring(config)?;
179 let count = coloring.windows(2).filter(|w| w[0] != w[1]).count();
180 i64::try_from(count).map_err(|_| {
181 crate::traits::EvaluationError::IntegerOverflow(
182 "converting paint-switch count to i64".into(),
183 )
184 })
185 }
186}
187
188#[cfg(test)]
190pub(crate) fn count_paint_switches(coloring: &[bool]) -> usize {
191 coloring.windows(2).filter(|w| w[0] != w[1]).count()
192}
193
194impl Problem for PaintShop {
195 const NAME: &'static str = "PaintShop";
196 type Solution = Vec<bool>;
197 type Value = Min<i64>;
198
199 crate::problem_parameters![("num_cars", num_cars), ("num_sequence", num_sequence),];
200
201 fn evaluate(
202 &self,
203 config: &Self::Solution,
204 ) -> Result<Min<i64>, crate::traits::EvaluationError> {
205 Ok({
206 Min(Some(self.count_switches(config)?))
208 })
209 }
210
211 fn variant() -> Vec<(&'static str, &'static str)> {
212 crate::variant_params![]
213 }
214}
215
216impl crate::solvers::BruteForceProblem for PaintShop {
217 fn dimensions(&self) -> Vec<usize> {
218 vec![2; self.num_cars]
219 }
220}
221
222crate::declare_variants! {
223 default PaintShop => "2^num_cars",
224}
225
226crate::register_brute_force! {
227 PaintShop decode |_, indices: Vec<usize>| crate::config::config_to_bits(&indices),
228}
229
230#[cfg(feature = "example-db")]
231pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
232 vec![crate::example_db::specs::ModelExampleSpec {
233 id: "paintshop",
234 instance: Box::new(PaintShop::new(vec!["A", "B", "A", "C", "B", "C"])),
235 optimal_config: serde_json::json!(vec![false, false, true]),
236 optimal_value: serde_json::json!(2),
237 }]
238}
239
240#[cfg(test)]
241#[path = "../../unit_tests/models/misc/paintshop.rs"]
242mod tests;