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 module_path: module_path!(),
21 description: "Minimize color changes in paint shop sequence",
22 fields: &[
23 FieldInfo { name: "sequence", type_name: "Vec<String>", description: "Car labels (each must appear exactly twice)" },
24 ],
25 }
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
52pub struct PaintShop {
53 sequence_indices: Vec<usize>,
55 car_labels: Vec<String>,
57 is_first: Vec<bool>,
59 num_cars: usize,
61}
62
63impl PaintShop {
64 pub fn new<S: AsRef<str>>(sequence: Vec<S>) -> Self {
68 let sequence: Vec<String> = sequence.iter().map(|s| s.as_ref().to_string()).collect();
69 Self::from_strings(sequence)
70 }
71
72 pub fn from_strings(sequence: Vec<String>) -> Self {
74 let mut car_count: HashMap<String, usize> = HashMap::new();
76 let mut car_to_index: HashMap<String, usize> = HashMap::new();
77 let mut car_labels: Vec<String> = Vec::new();
78
79 for item in &sequence {
80 let count = car_count.entry(item.clone()).or_insert(0);
81 if *count == 0 {
82 car_to_index.insert(item.clone(), car_labels.len());
83 car_labels.push(item.clone());
84 }
85 *count += 1;
86 }
87
88 for (car, count) in &car_count {
90 assert_eq!(
91 *count, 2,
92 "Each car must appear exactly twice, but '{}' appears {} times",
93 car, count
94 );
95 }
96
97 let sequence_indices: Vec<usize> = sequence.iter().map(|item| car_to_index[item]).collect();
99
100 let mut seen: HashSet<usize> = HashSet::new();
102 let is_first: Vec<bool> = sequence_indices
103 .iter()
104 .map(|&idx| seen.insert(idx))
105 .collect();
106
107 let num_cars = car_labels.len();
108
109 Self {
110 sequence_indices,
111 car_labels,
112 is_first,
113 num_cars,
114 }
115 }
116
117 pub fn sequence_len(&self) -> usize {
119 self.sequence_indices.len()
120 }
121
122 pub fn num_sequence(&self) -> usize {
124 self.sequence_len()
125 }
126
127 pub fn num_cars(&self) -> usize {
129 self.num_cars
130 }
131
132 pub fn car_labels(&self) -> &[String] {
134 &self.car_labels
135 }
136
137 pub fn sequence_indices(&self) -> &[usize] {
139 &self.sequence_indices
140 }
141
142 pub fn is_first(&self) -> &[bool] {
144 &self.is_first
145 }
146
147 pub fn get_coloring(&self, config: &[usize]) -> Vec<usize> {
152 self.sequence_indices
153 .iter()
154 .enumerate()
155 .map(|(i, &car_idx)| {
156 let first_color = config.get(car_idx).copied().unwrap_or(0);
157 if self.is_first[i] {
158 first_color
159 } else {
160 1 - first_color }
162 })
163 .collect()
164 }
165
166 pub fn count_switches(&self, config: &[usize]) -> usize {
168 let coloring = self.get_coloring(config);
169 coloring.windows(2).filter(|w| w[0] != w[1]).count()
170 }
171}
172
173#[cfg(test)]
175pub(crate) fn count_paint_switches(coloring: &[usize]) -> usize {
176 coloring.windows(2).filter(|w| w[0] != w[1]).count()
177}
178
179impl Problem for PaintShop {
180 const NAME: &'static str = "PaintShop";
181 type Value = Min<i32>;
182
183 fn dims(&self) -> Vec<usize> {
184 vec![2; self.num_cars]
185 }
186
187 fn evaluate(&self, config: &[usize]) -> Min<i32> {
188 Min(Some(self.count_switches(config) as i32))
190 }
191
192 fn variant() -> Vec<(&'static str, &'static str)> {
193 crate::variant_params![]
194 }
195}
196
197crate::declare_variants! {
198 default PaintShop => "2^num_cars",
199}
200
201#[cfg(feature = "example-db")]
202pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
203 vec![crate::example_db::specs::ModelExampleSpec {
204 id: "paintshop",
205 instance: Box::new(PaintShop::new(vec!["A", "B", "A", "C", "B", "C"])),
206 optimal_config: vec![0, 0, 1],
207 optimal_value: serde_json::json!(2),
208 }]
209}
210
211#[cfg(test)]
212#[path = "../../unit_tests/models/misc/paintshop.rs"]
213mod tests;