problemreductions/models/misc/
grouping_by_swapping.rs1use crate::registry::{CreateSpec, ProblemSchemaEntry};
8use crate::traits::Problem;
9use serde::{Deserialize, Serialize};
10
11inventory::submit! {
12 ProblemSchemaEntry {
13 name: "GroupingBySwapping",
14 display_name: "Grouping by Swapping",
15 aliases: &[],
16 dimensions: &[],
17 category: crate::registry::ProblemCategory::Misc,
18 module_path: module_path!(),
19 description: "Group equal symbols into contiguous blocks using at most K adjacent swaps",
20 fields: GroupingBySwappingCreateSpec::FIELDS,
21 }
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
30#[serde(try_from = "GroupingBySwappingData")]
31pub struct GroupingBySwapping {
32 alphabet_size: usize,
33 string: Vec<usize>,
34 budget: usize,
35}
36
37#[derive(Deserialize)]
38struct GroupingBySwappingData {
39 alphabet_size: usize,
40 string: Vec<usize>,
41 budget: usize,
42}
43
44impl TryFrom<GroupingBySwappingData> for GroupingBySwapping {
45 type Error = crate::registry::ConstructionError;
46
47 fn try_from(data: GroupingBySwappingData) -> Result<Self, Self::Error> {
48 Self::try_new(data.alphabet_size, data.string, data.budget)
49 }
50}
51
52#[derive(Debug, Deserialize, crate::CreateSpec)]
53struct GroupingBySwappingCreateSpec {
54 alphabet_size: Option<usize>,
56 #[create(codec = "comma-separated")]
58 string: Vec<usize>,
59 bound: usize,
61}
62
63impl TryFrom<GroupingBySwappingCreateSpec> for GroupingBySwapping {
64 type Error = crate::registry::ConstructionError;
65
66 fn try_from(spec: GroupingBySwappingCreateSpec) -> Result<Self, Self::Error> {
67 let inferred_alphabet_size = spec
68 .string
69 .iter()
70 .copied()
71 .max()
72 .map(|symbol| {
73 symbol
74 .checked_add(1)
75 .ok_or_else(|| "inferred alphabet size overflows usize".to_string())
76 })
77 .transpose()?
78 .unwrap_or(0);
79 let alphabet_size = spec.alphabet_size.unwrap_or(inferred_alphabet_size);
80 Self::try_new(alphabet_size, spec.string, spec.bound)
81 }
82}
83
84impl GroupingBySwapping {
85 pub fn new(alphabet_size: usize, string: Vec<usize>, budget: usize) -> Self {
92 Self::try_new(alphabet_size, string, budget).unwrap_or_else(|error| panic!("{error}"))
93 }
94
95 fn try_new(
96 alphabet_size: usize,
97 string: Vec<usize>,
98 budget: usize,
99 ) -> Result<Self, crate::registry::ConstructionError> {
100 if !(alphabet_size > 0 || string.is_empty()) {
101 return Err("alphabet_size must be > 0 when string is non-empty".into());
102 }
103 if !(string.iter().all(|&symbol| symbol < alphabet_size)) {
104 return Err("input symbols must be less than alphabet_size".into());
105 }
106 if string.is_empty() && budget != 0 {
107 return Err("budget must be 0 when string is empty".into());
108 }
109 Ok(Self {
110 alphabet_size,
111 string,
112 budget,
113 })
114 }
115
116 pub fn alphabet_size(&self) -> usize {
118 self.alphabet_size
119 }
120
121 pub fn string(&self) -> &[usize] {
123 &self.string
124 }
125
126 pub fn budget(&self) -> usize {
128 self.budget
129 }
130
131 pub fn string_len(&self) -> usize {
133 self.string.len()
134 }
135
136 pub fn apply_swap_program(&self, config: &[usize]) -> Option<Vec<usize>> {
141 if config.len() != self.budget {
142 return None;
143 }
144 if self.string.is_empty() {
145 return if config.is_empty() {
146 Some(Vec::new())
147 } else {
148 None
149 };
150 }
151
152 let no_op = self.string.len() - 1;
153 let mut current = self.string.clone();
154 for &slot in config {
155 if slot >= self.string.len() {
156 return None;
157 }
158 if slot != no_op {
159 current.swap(slot, slot + 1);
160 }
161 }
162 Some(current)
163 }
164
165 pub fn is_grouped(&self, candidate: &[usize]) -> bool {
167 if candidate.iter().any(|&symbol| symbol >= self.alphabet_size) {
168 return false;
169 }
170 if candidate.is_empty() {
171 return true;
172 }
173
174 let mut closed = vec![false; self.alphabet_size];
175 let mut current_symbol = candidate[0];
176
177 for &symbol in candidate.iter().skip(1) {
178 if symbol == current_symbol {
179 continue;
180 }
181 closed[current_symbol] = true;
182 if closed[symbol] {
183 return false;
184 }
185 current_symbol = symbol;
186 }
187
188 true
189 }
190}
191
192impl Problem for GroupingBySwapping {
193 const NAME: &'static str = "GroupingBySwapping";
194 type Solution = Vec<usize>;
195 type Value = crate::types::Or;
196
197 crate::problem_parameters![
198 ("alphabet_size", alphabet_size),
199 ("string_len", string_len),
200 ("budget", budget),
201 ];
202
203 fn evaluate(
204 &self,
205 config: &Self::Solution,
206 ) -> Result<crate::types::Or, crate::traits::EvaluationError> {
207 if config.len() != self.budget {
208 return Err(crate::traits::EvaluationError::InvalidConfiguration(
209 "swap-program length does not match the budget".into(),
210 ));
211 }
212 if config.iter().any(|&slot| slot >= self.string.len()) {
213 return Err(crate::traits::EvaluationError::InvalidConfiguration(
214 "swap program contains an out-of-range slot".into(),
215 ));
216 }
217 Ok({
218 crate::types::Or({
219 self.apply_swap_program(config)
220 .is_some_and(|candidate| self.is_grouped(&candidate))
221 })
222 })
223 }
224
225 fn variant() -> Vec<(&'static str, &'static str)> {
226 crate::variant_params![]
227 }
228}
229
230impl crate::solvers::BruteForceProblem for GroupingBySwapping {
231 fn dimensions(&self) -> Vec<usize> {
232 vec![self.string_len(); self.budget]
233 }
234}
235
236crate::declare_variants! {
237 default GroupingBySwapping => "string_len ^ budget" create GroupingBySwappingCreateSpec,
238}
239
240crate::register_brute_force! {
241 GroupingBySwapping,
242}
243
244#[cfg(feature = "example-db")]
245pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
246 vec![crate::example_db::specs::ModelExampleSpec {
247 id: "grouping_by_swapping",
248 instance: Box::new(GroupingBySwapping::new(3, vec![0, 1, 2, 0, 1, 2], 5)),
249 optimal_config: serde_json::json!(vec![2, 1, 3, 5, 5]),
250 optimal_value: serde_json::json!(true),
251 }]
252}
253
254#[cfg(test)]
255#[path = "../../unit_tests/models/misc/grouping_by_swapping.rs"]
256mod tests;