problemreductions/models/misc/
shortest_common_superstring.rs1use crate::registry::{FieldInfo, ProblemSchemaEntry};
19use crate::traits::Problem;
20use crate::types::Min;
21use serde::{Deserialize, Serialize};
22
23inventory::submit! {
24 ProblemSchemaEntry {
25 name: "ShortestCommonSuperstring",
26 display_name: "Shortest Common Superstring",
27 aliases: &["SCSS"],
28 dimensions: &[],
29 category: crate::registry::ProblemCategory::Misc,
30 module_path: module_path!(),
31 description: "Find a shortest string that contains every input string as a contiguous substring",
32 fields: &[
33 FieldInfo { name: "alphabet_size", type_name: "usize", description: "Size of the alphabet" },
34 FieldInfo { name: "strings", type_name: "Vec<Vec<usize>>", description: "Input strings over the alphabet {0, ..., alphabet_size-1}" },
35 FieldInfo { name: "max_length", type_name: "usize", description: "Maximum possible superstring length (sum of all string lengths)" },
36 ],
37 }
38}
39
40#[derive(Debug, Clone, Serialize, Deserialize)]
66pub struct ShortestCommonSuperstring {
67 alphabet_size: usize,
68 strings: Vec<Vec<usize>>,
69 max_length: usize,
70}
71
72impl ShortestCommonSuperstring {
73 pub fn new(alphabet_size: usize, strings: Vec<Vec<usize>>) -> Self {
83 assert!(!strings.is_empty(), "must have at least one string");
84 let max_length: usize = strings.iter().map(|s| s.len()).sum();
85 assert!(
86 alphabet_size > 0 || strings.iter().all(|s| s.is_empty()),
87 "alphabet_size must be > 0 when any input string is non-empty"
88 );
89 Self {
90 alphabet_size,
91 strings,
92 max_length,
93 }
94 }
95
96 pub fn alphabet_size(&self) -> usize {
98 self.alphabet_size
99 }
100
101 pub fn strings(&self) -> &[Vec<usize>] {
103 &self.strings
104 }
105
106 pub fn max_length(&self) -> usize {
108 self.max_length
109 }
110
111 pub fn num_strings(&self) -> usize {
113 self.strings.len()
114 }
115
116 pub fn total_length(&self) -> usize {
118 self.strings.iter().map(|s| s.len()).sum()
119 }
120}
121
122fn is_substring(needle: &[usize], haystack: &[usize]) -> bool {
124 if needle.is_empty() {
125 return true;
126 }
127 if needle.len() > haystack.len() {
128 return false;
129 }
130 haystack
131 .windows(needle.len())
132 .any(|window| window == needle)
133}
134
135impl Problem for ShortestCommonSuperstring {
136 const NAME: &'static str = "ShortestCommonSuperstring";
137 type Solution = Vec<Option<usize>>;
138 type Value = Min<i64>;
139
140 crate::problem_parameters![
141 ("alphabet_size", alphabet_size),
142 ("num_strings", num_strings),
143 ("max_length", max_length),
144 ("total_length", total_length),
145 ];
146
147 fn variant() -> Vec<(&'static str, &'static str)> {
148 crate::variant_params![]
149 }
150
151 fn evaluate(
152 &self,
153 config: &Self::Solution,
154 ) -> Result<Min<i64>, crate::traits::EvaluationError> {
155 if config.len() != self.max_length {
156 return Err(crate::traits::EvaluationError::InvalidConfiguration(
157 "superstring representation length does not match the bound".into(),
158 ));
159 }
160 if config
161 .iter()
162 .any(|symbol| symbol.is_some_and(|value| value >= self.alphabet_size))
163 {
164 return Err(crate::traits::EvaluationError::InvalidConfiguration(
165 "superstring contains an out-of-range symbol".into(),
166 ));
167 }
168 let config = config
169 .iter()
170 .map(|symbol| symbol.unwrap_or(self.alphabet_size))
171 .collect::<Vec<_>>();
172 Ok({
173 let pad = self.alphabet_size;
174
175 let effective_length = config
177 .iter()
178 .position(|&v| v == pad)
179 .unwrap_or(self.max_length);
180
181 for &v in &config[effective_length..] {
183 if v != pad {
184 return Ok(Min(None));
185 }
186 }
187
188 let prefix = &config[..effective_length];
189
190 if !self.strings.iter().all(|s| is_substring(s, prefix)) {
192 return Ok(Min(None));
193 }
194
195 Min(Some(i64::try_from(effective_length).map_err(|_| {
196 crate::traits::EvaluationError::IntegerOverflow(
197 "converting superstring length to i64".into(),
198 )
199 })?))
200 })
201 }
202}
203
204impl crate::solvers::BruteForceProblem for ShortestCommonSuperstring {
205 fn dimensions(&self) -> Vec<usize> {
206 vec![self.alphabet_size + 1; self.max_length]
207 }
208}
209
210crate::declare_variants! {
211 default ShortestCommonSuperstring => "num_strings ^ 2 * 2 ^ num_strings",
212}
213
214crate::register_brute_force! {
215 ShortestCommonSuperstring decode |problem: &ShortestCommonSuperstring, indices: Vec<usize>| indices.into_iter().map(|value| (value != problem.alphabet_size()).then_some(value)).collect(),
216}
217
218#[cfg(feature = "example-db")]
219pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
220 vec![crate::example_db::specs::ModelExampleSpec {
225 id: "shortest_common_superstring",
226 instance: Box::new(ShortestCommonSuperstring::new(
227 2,
228 vec![vec![0, 1], vec![1, 0]],
229 )),
230 optimal_config: serde_json::json!(vec![Some(0), Some(1), Some(0), None]),
231 optimal_value: serde_json::json!(3),
232 }]
233}
234
235#[cfg(test)]
236#[path = "../../unit_tests/models/misc/shortest_common_superstring.rs"]
237mod tests;