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)]
66#[serde(try_from = "ShortestCommonSuperstringData")]
67pub struct ShortestCommonSuperstring {
68 alphabet_size: usize,
69 strings: Vec<Vec<usize>>,
70 max_length: usize,
71}
72
73#[derive(Deserialize)]
74struct ShortestCommonSuperstringData {
75 alphabet_size: usize,
76 strings: Vec<Vec<usize>>,
77}
78
79impl TryFrom<ShortestCommonSuperstringData> for ShortestCommonSuperstring {
80 type Error = crate::registry::ConstructionError;
81
82 fn try_from(data: ShortestCommonSuperstringData) -> Result<Self, Self::Error> {
83 Self::try_new(data.alphabet_size, data.strings)
84 }
85}
86
87impl ShortestCommonSuperstring {
88 pub fn new(alphabet_size: usize, strings: Vec<Vec<usize>>) -> Self {
98 Self::try_new(alphabet_size, strings).unwrap_or_else(|error| panic!("{error}"))
99 }
100
101 fn try_new(
102 alphabet_size: usize,
103 strings: Vec<Vec<usize>>,
104 ) -> Result<Self, crate::registry::ConstructionError> {
105 if strings.is_empty() {
106 return Err("must have at least one string".into());
107 }
108 let max_length = strings.iter().try_fold(0usize, |total, string| {
109 total
110 .checked_add(string.len())
111 .ok_or("maximum string length overflows usize")
112 })?;
113 if !(alphabet_size > 0 || strings.iter().all(|s| s.is_empty())) {
114 return Err("alphabet_size must be > 0 when any input string is non-empty".into());
115 }
116 Ok(Self {
117 alphabet_size,
118 strings,
119 max_length,
120 })
121 }
122
123 pub fn alphabet_size(&self) -> usize {
125 self.alphabet_size
126 }
127
128 pub fn strings(&self) -> &[Vec<usize>] {
130 &self.strings
131 }
132
133 pub fn max_length(&self) -> usize {
135 self.max_length
136 }
137
138 pub fn num_strings(&self) -> usize {
140 self.strings.len()
141 }
142
143 pub fn total_length(&self) -> usize {
145 self.strings.iter().map(|s| s.len()).sum()
146 }
147}
148
149fn is_substring(needle: &[usize], haystack: &[usize]) -> bool {
151 if needle.is_empty() {
152 return true;
153 }
154 if needle.len() > haystack.len() {
155 return false;
156 }
157 haystack
158 .windows(needle.len())
159 .any(|window| window == needle)
160}
161
162impl Problem for ShortestCommonSuperstring {
163 const NAME: &'static str = "ShortestCommonSuperstring";
164 type Solution = Vec<Option<usize>>;
165 type Value = Min<i64>;
166
167 crate::problem_parameters![
168 ("alphabet_size", alphabet_size),
169 ("num_strings", num_strings),
170 ("max_length", max_length),
171 ("total_length", total_length),
172 ];
173
174 fn variant() -> Vec<(&'static str, &'static str)> {
175 crate::variant_params![]
176 }
177
178 fn evaluate(
179 &self,
180 config: &Self::Solution,
181 ) -> Result<Min<i64>, crate::traits::EvaluationError> {
182 if config.len() != self.max_length {
183 return Err(crate::traits::EvaluationError::InvalidConfiguration(
184 "superstring representation length does not match the bound".into(),
185 ));
186 }
187 if config
188 .iter()
189 .any(|symbol| symbol.is_some_and(|value| value >= self.alphabet_size))
190 {
191 return Err(crate::traits::EvaluationError::InvalidConfiguration(
192 "superstring contains an out-of-range symbol".into(),
193 ));
194 }
195 let config = config
196 .iter()
197 .map(|symbol| symbol.unwrap_or(self.alphabet_size))
198 .collect::<Vec<_>>();
199 Ok({
200 let pad = self.alphabet_size;
201
202 let effective_length = config
204 .iter()
205 .position(|&v| v == pad)
206 .unwrap_or(self.max_length);
207
208 for &v in &config[effective_length..] {
210 if v != pad {
211 return Ok(Min(None));
212 }
213 }
214
215 let prefix = &config[..effective_length];
216
217 if !self.strings.iter().all(|s| is_substring(s, prefix)) {
219 return Ok(Min(None));
220 }
221
222 Min(Some(i64::try_from(effective_length).map_err(|_| {
223 crate::traits::EvaluationError::IntegerOverflow(
224 "converting superstring length to i64".into(),
225 )
226 })?))
227 })
228 }
229}
230
231impl crate::solvers::BruteForceProblem for ShortestCommonSuperstring {
232 fn dimensions(&self) -> Vec<usize> {
233 vec![self.alphabet_size + 1; self.max_length]
234 }
235}
236
237crate::declare_variants! {
238 default ShortestCommonSuperstring => "num_strings ^ 2 * 2 ^ num_strings",
239}
240
241crate::register_brute_force! {
242 ShortestCommonSuperstring decode |problem: &ShortestCommonSuperstring, indices: Vec<usize>| indices.into_iter().map(|value| (value != problem.alphabet_size()).then_some(value)).collect(),
243}
244
245#[cfg(feature = "example-db")]
246pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
247 vec![crate::example_db::specs::ModelExampleSpec {
252 id: "shortest_common_superstring",
253 instance: Box::new(ShortestCommonSuperstring::new(
254 2,
255 vec![vec![0, 1], vec![1, 0]],
256 )),
257 optimal_config: serde_json::json!(vec![Some(0), Some(1), Some(0), None]),
258 optimal_value: serde_json::json!(3),
259 }]
260}
261
262#[cfg(test)]
263#[path = "../../unit_tests/models/misc/shortest_common_superstring.rs"]
264mod tests;