problemreductions/models/misc/
grouping_by_swapping.rs1use crate::registry::{FieldInfo, 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 module_path: module_path!(),
18 description: "Group equal symbols into contiguous blocks using at most K adjacent swaps",
19 fields: &[
20 FieldInfo { name: "alphabet_size", type_name: "usize", description: "Size of the alphabet" },
21 FieldInfo { name: "string", type_name: "Vec<usize>", description: "Input string over {0, ..., alphabet_size-1}" },
22 FieldInfo { name: "budget", type_name: "usize", description: "Maximum number of adjacent swaps allowed" },
23 ],
24 }
25}
26
27#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct GroupingBySwapping {
34 alphabet_size: usize,
35 string: Vec<usize>,
36 budget: usize,
37}
38
39impl GroupingBySwapping {
40 pub fn new(alphabet_size: usize, string: Vec<usize>, budget: usize) -> Self {
47 assert!(
48 alphabet_size > 0 || string.is_empty(),
49 "alphabet_size must be > 0 when string is non-empty"
50 );
51 assert!(
52 string.iter().all(|&symbol| symbol < alphabet_size),
53 "input symbols must be less than alphabet_size"
54 );
55 assert!(
56 !string.is_empty() || budget == 0,
57 "budget must be 0 when string is empty"
58 );
59 Self {
60 alphabet_size,
61 string,
62 budget,
63 }
64 }
65
66 pub fn alphabet_size(&self) -> usize {
68 self.alphabet_size
69 }
70
71 pub fn string(&self) -> &[usize] {
73 &self.string
74 }
75
76 pub fn budget(&self) -> usize {
78 self.budget
79 }
80
81 pub fn string_len(&self) -> usize {
83 self.string.len()
84 }
85
86 pub fn apply_swap_program(&self, config: &[usize]) -> Option<Vec<usize>> {
91 if config.len() != self.budget {
92 return None;
93 }
94 if self.string.is_empty() {
95 return if config.is_empty() {
96 Some(Vec::new())
97 } else {
98 None
99 };
100 }
101
102 let no_op = self.string.len() - 1;
103 let mut current = self.string.clone();
104 for &slot in config {
105 if slot >= self.string.len() {
106 return None;
107 }
108 if slot != no_op {
109 current.swap(slot, slot + 1);
110 }
111 }
112 Some(current)
113 }
114
115 pub fn is_grouped(&self, candidate: &[usize]) -> bool {
117 if candidate.iter().any(|&symbol| symbol >= self.alphabet_size) {
118 return false;
119 }
120 if candidate.is_empty() {
121 return true;
122 }
123
124 let mut closed = vec![false; self.alphabet_size];
125 let mut current_symbol = candidate[0];
126
127 for &symbol in candidate.iter().skip(1) {
128 if symbol == current_symbol {
129 continue;
130 }
131 closed[current_symbol] = true;
132 if closed[symbol] {
133 return false;
134 }
135 current_symbol = symbol;
136 }
137
138 true
139 }
140}
141
142impl Problem for GroupingBySwapping {
143 const NAME: &'static str = "GroupingBySwapping";
144 type Value = crate::types::Or;
145
146 fn dims(&self) -> Vec<usize> {
147 vec![self.string_len(); self.budget]
148 }
149
150 fn evaluate(&self, config: &[usize]) -> crate::types::Or {
151 crate::types::Or({
152 self.apply_swap_program(config)
153 .is_some_and(|candidate| self.is_grouped(&candidate))
154 })
155 }
156
157 fn variant() -> Vec<(&'static str, &'static str)> {
158 crate::variant_params![]
159 }
160}
161
162crate::declare_variants! {
163 default GroupingBySwapping => "string_len ^ budget",
164}
165
166#[cfg(feature = "example-db")]
167pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
168 vec![crate::example_db::specs::ModelExampleSpec {
169 id: "grouping_by_swapping",
170 instance: Box::new(GroupingBySwapping::new(3, vec![0, 1, 2, 0, 1, 2], 5)),
171 optimal_config: vec![2, 1, 3, 5, 5],
172 optimal_value: serde_json::json!(true),
173 }]
174}
175
176#[cfg(test)]
177#[path = "../../unit_tests/models/misc/grouping_by_swapping.rs"]
178mod tests;