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)]
53#[serde(try_from = "PaintShopData")]
54pub struct PaintShop {
55 sequence_indices: Vec<usize>,
57 car_labels: Vec<String>,
59 is_first: Vec<bool>,
61 num_cars: usize,
63}
64
65#[derive(Deserialize)]
66struct PaintShopData {
67 sequence_indices: Vec<usize>,
68 car_labels: Vec<String>,
69}
70
71impl TryFrom<PaintShopData> for PaintShop {
72 type Error = crate::registry::ConstructionError;
73
74 fn try_from(data: PaintShopData) -> Result<Self, Self::Error> {
75 let sequence = data
76 .sequence_indices
77 .into_iter()
78 .map(|index| {
79 data.car_labels.get(index).ok_or_else(|| {
80 crate::registry::ConstructionError::from(format!(
81 "car index {index} is outside car_labels"
82 ))
83 })
84 })
85 .collect::<Result<Vec<_>, _>>()?;
86 Self::try_new(sequence)
87 }
88}
89
90impl PaintShop {
91 pub fn new<S: AsRef<str>>(sequence: Vec<S>) -> Self {
95 Self::try_new(sequence).unwrap_or_else(|error| panic!("{error}"))
96 }
97
98 fn try_new<S: AsRef<str>>(
99 sequence: Vec<S>,
100 ) -> Result<Self, crate::registry::ConstructionError> {
101 let mut car_count: HashMap<&str, usize> = HashMap::new();
103 let mut car_to_index: HashMap<&str, usize> = HashMap::new();
104 let mut car_labels: Vec<String> = Vec::new();
105
106 for item in &sequence {
107 let item = item.as_ref();
108 let count = car_count.entry(item).or_insert(0);
109 if *count == 0 {
110 car_to_index.insert(item, car_labels.len());
111 car_labels.push(item.to_owned());
112 }
113 *count += 1;
114 }
115
116 for (car, count) in &car_count {
118 if *count != 2 {
119 return Err(format!(
120 "each car must appear exactly twice, but '{car}' appears {count} times"
121 )
122 .into());
123 }
124 }
125
126 let sequence_indices: Vec<usize> = sequence
128 .iter()
129 .map(|item| car_to_index[item.as_ref()])
130 .collect();
131
132 let mut seen: HashSet<usize> = HashSet::new();
134 let is_first: Vec<bool> = sequence_indices
135 .iter()
136 .map(|&idx| seen.insert(idx))
137 .collect();
138
139 let num_cars = car_labels.len();
140
141 Ok(Self {
142 sequence_indices,
143 car_labels,
144 is_first,
145 num_cars,
146 })
147 }
148
149 pub fn sequence_len(&self) -> usize {
151 self.sequence_indices.len()
152 }
153
154 pub fn num_sequence(&self) -> usize {
156 self.sequence_len()
157 }
158
159 pub fn num_cars(&self) -> usize {
161 self.num_cars
162 }
163
164 pub fn car_labels(&self) -> &[String] {
166 &self.car_labels
167 }
168
169 pub fn sequence_indices(&self) -> &[usize] {
171 &self.sequence_indices
172 }
173
174 pub fn is_first(&self) -> &[bool] {
176 &self.is_first
177 }
178
179 pub fn get_coloring(
184 &self,
185 config: &[bool],
186 ) -> Result<Vec<bool>, crate::traits::EvaluationError> {
187 if config.len() != self.num_cars {
188 return Err(crate::traits::EvaluationError::InvalidConfiguration(
189 "paint assignment length does not match the cars".into(),
190 ));
191 }
192 Ok(self
193 .sequence_indices
194 .iter()
195 .enumerate()
196 .map(|(i, &car_idx)| {
197 let first_color = config[car_idx];
198 if self.is_first[i] {
199 first_color
200 } else {
201 !first_color }
203 })
204 .collect())
205 }
206
207 pub fn count_switches(&self, config: &[bool]) -> Result<i64, crate::traits::EvaluationError> {
209 let coloring = self.get_coloring(config)?;
210 let count = coloring.windows(2).filter(|w| w[0] != w[1]).count();
211 i64::try_from(count).map_err(|_| {
212 crate::traits::EvaluationError::IntegerOverflow(
213 "converting paint-switch count to i64".into(),
214 )
215 })
216 }
217
218 pub fn from_strings(sequence: Vec<String>) -> Self {
220 Self::new(sequence)
221 }
222}
223
224#[cfg(test)]
226pub(crate) fn count_paint_switches(coloring: &[bool]) -> usize {
227 coloring.windows(2).filter(|w| w[0] != w[1]).count()
228}
229
230impl Problem for PaintShop {
231 const NAME: &'static str = "PaintShop";
232 type Solution = Vec<bool>;
233 type Value = Min<i64>;
234
235 crate::problem_parameters![("num_cars", num_cars), ("num_sequence", num_sequence),];
236
237 fn evaluate(
238 &self,
239 config: &Self::Solution,
240 ) -> Result<Min<i64>, crate::traits::EvaluationError> {
241 Ok({
242 Min(Some(self.count_switches(config)?))
244 })
245 }
246
247 fn variant() -> Vec<(&'static str, &'static str)> {
248 crate::variant_params![]
249 }
250}
251
252impl crate::solvers::BruteForceProblem for PaintShop {
253 fn dimensions(&self) -> Vec<usize> {
254 vec![2; self.num_cars]
255 }
256}
257
258crate::declare_variants! {
259 default PaintShop => "2^num_cars",
260}
261
262crate::register_brute_force! {
263 PaintShop decode |_, indices: Vec<usize>| crate::config::config_to_bits(&indices),
264}
265
266#[cfg(feature = "example-db")]
267pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
268 vec![crate::example_db::specs::ModelExampleSpec {
269 id: "paintshop",
270 instance: Box::new(PaintShop::new(vec!["A", "B", "A", "C", "B", "C"])),
271 optimal_config: serde_json::json!(vec![false, false, true]),
272 optimal_value: serde_json::json!(2),
273 }]
274}
275
276#[cfg(test)]
277#[path = "../../unit_tests/models/misc/paintshop.rs"]
278mod tests;