problemreductions/models/misc/
paintshop.rs

1//! Paint Shop problem implementation.
2//!
3//! In the Paint Shop problem, we have a sequence of cars to paint.
4//! Each car appears exactly twice in the sequence and must be painted
5//! one color at its first occurrence and another at its second.
6//! The goal is to minimize color switches between adjacent positions.
7
8use 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/// The Paint Shop problem.
26///
27/// Given a sequence where each car appears exactly twice, assign colors
28/// (0 or 1) to each car to minimize color switches in the sequence.
29///
30/// # Example
31///
32/// ```
33/// use problemreductions::models::misc::PaintShop;
34/// use problemreductions::{Problem, Solver, BruteForce};
35///
36/// // Sequence: a, b, a, c, c, b
37/// let problem = PaintShop::new(vec!["a", "b", "a", "c", "c", "b"]);
38///
39/// let solver = BruteForce::new();
40/// let solutions = solver.find_all_best(&problem);
41///
42/// // The minimum number of color switches
43/// for sol in &solutions {
44///     let switches = problem.count_switches(sol);
45///     println!("Switches: {}", switches);
46/// }
47/// ```
48#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct PaintShop {
50    /// The sequence of car labels (as indices into unique cars).
51    sequence_indices: Vec<usize>,
52    /// Original car labels.
53    car_labels: Vec<String>,
54    /// Which positions are the first occurrence of each car.
55    is_first: Vec<bool>,
56    /// Number of unique cars.
57    num_cars: usize,
58}
59
60impl PaintShop {
61    /// Create a new Paint Shop problem from string labels.
62    ///
63    /// Each element in the sequence must appear exactly twice.
64    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    /// Create from a vector of strings.
70    pub fn from_strings(sequence: Vec<String>) -> Self {
71        // Build car-to-index mapping and count occurrences
72        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        // Verify each car appears exactly twice
86        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        // Convert sequence to indices
95        let sequence_indices: Vec<usize> = sequence.iter().map(|item| car_to_index[item]).collect();
96
97        // Determine which positions are first occurrences
98        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    /// Get the sequence length.
115    pub fn sequence_len(&self) -> usize {
116        self.sequence_indices.len()
117    }
118
119    /// Get the sequence length (alias for `sequence_len()`).
120    pub fn num_sequence(&self) -> usize {
121        self.sequence_len()
122    }
123
124    /// Get the number of unique cars.
125    pub fn num_cars(&self) -> usize {
126        self.num_cars
127    }
128
129    /// Get the car labels.
130    pub fn car_labels(&self) -> &[String] {
131        &self.car_labels
132    }
133
134    /// Get the coloring of the sequence from a configuration.
135    ///
136    /// Config assigns a color (0 or 1) to each car for its first occurrence.
137    /// The second occurrence gets the opposite color.
138    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 // Opposite color for second occurrence
148                }
149            })
150            .collect()
151    }
152
153    /// Count the number of color switches in the sequence.
154    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/// Count color switches in a painted sequence.
161#[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        // All configurations are valid (no hard constraints).
176        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;