problemreductions/models/misc/
shortest_common_supersequence.rs1use crate::registry::{CreateSpec, ProblemSchemaEntry};
15use crate::traits::Problem;
16use crate::types::Min;
17use serde::{Deserialize, Serialize};
18
19inventory::submit! {
20 ProblemSchemaEntry {
21 name: "ShortestCommonSupersequence",
22 display_name: "Shortest Common Supersequence",
23 aliases: &["SCS"],
24 dimensions: &[],
25 category: crate::registry::ProblemCategory::Misc,
26 module_path: module_path!(),
27 description: "Find a shortest common supersequence for a set of strings",
28 fields: ShortestCommonSupersequenceCreateSpec::FIELDS,
29 }
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct ShortestCommonSupersequence {
59 alphabet_size: usize,
60 strings: Vec<Vec<usize>>,
61 max_length: usize,
62}
63
64#[derive(Debug, Deserialize, crate::CreateSpec)]
65struct ShortestCommonSupersequenceCreateSpec {
66 #[create(codec = "semicolon-separated")]
68 strings: Vec<Vec<usize>>,
69}
70
71impl TryFrom<ShortestCommonSupersequenceCreateSpec> for ShortestCommonSupersequence {
72 type Error = crate::registry::ConstructionError;
73
74 fn try_from(spec: ShortestCommonSupersequenceCreateSpec) -> Result<Self, Self::Error> {
75 if spec.strings.is_empty() {
76 return Err("must have at least one string".to_string().into());
77 }
78
79 let alphabet_size = spec
80 .strings
81 .iter()
82 .flatten()
83 .copied()
84 .max()
85 .map(|symbol| {
86 symbol
87 .checked_add(1)
88 .ok_or_else(|| "alphabet size overflows usize".to_string())
89 })
90 .transpose()?
91 .unwrap_or(0);
92 let max_length = spec.strings.iter().try_fold(0_usize, |total, string| {
93 total
94 .checked_add(string.len())
95 .ok_or_else(|| "maximum supersequence length overflows usize".to_string())
96 })?;
97
98 Ok(Self {
99 alphabet_size,
100 strings: spec.strings,
101 max_length,
102 })
103 }
104}
105
106impl ShortestCommonSupersequence {
107 pub fn new(alphabet_size: usize, strings: Vec<Vec<usize>>) -> Self {
117 assert!(!strings.is_empty(), "must have at least one string");
118 let max_length: usize = strings.iter().map(|s| s.len()).sum();
119 assert!(
120 alphabet_size > 0 || strings.iter().all(|s| s.is_empty()),
121 "alphabet_size must be > 0 when any input string is non-empty"
122 );
123 Self {
124 alphabet_size,
125 strings,
126 max_length,
127 }
128 }
129
130 pub fn alphabet_size(&self) -> usize {
132 self.alphabet_size
133 }
134
135 pub fn strings(&self) -> &[Vec<usize>] {
137 &self.strings
138 }
139
140 pub fn max_length(&self) -> usize {
142 self.max_length
143 }
144
145 pub fn num_strings(&self) -> usize {
147 self.strings.len()
148 }
149
150 pub fn total_length(&self) -> usize {
152 self.strings.iter().map(|s| s.len()).sum()
153 }
154}
155
156fn is_subsequence(needle: &[usize], haystack: &[usize]) -> bool {
159 let mut it = haystack.iter();
160 for &ch in needle {
161 loop {
162 match it.next() {
163 Some(&c) if c == ch => break,
164 Some(_) => continue,
165 None => return false,
166 }
167 }
168 }
169 true
170}
171
172impl Problem for ShortestCommonSupersequence {
173 const NAME: &'static str = "ShortestCommonSupersequence";
174 type Solution = Vec<Option<usize>>;
175 type Value = Min<i64>;
176
177 crate::problem_parameters![
178 ("alphabet_size", alphabet_size),
179 ("max_length", max_length),
180 ("total_length", total_length),
181 ];
182
183 fn variant() -> Vec<(&'static str, &'static str)> {
184 crate::variant_params![]
185 }
186
187 fn evaluate(
188 &self,
189 config: &Self::Solution,
190 ) -> Result<Min<i64>, crate::traits::EvaluationError> {
191 if config.len() != self.max_length {
192 return Err(crate::traits::EvaluationError::InvalidConfiguration(
193 "supersequence representation length does not match the bound".into(),
194 ));
195 }
196 if config
197 .iter()
198 .any(|symbol| symbol.is_some_and(|value| value >= self.alphabet_size))
199 {
200 return Err(crate::traits::EvaluationError::InvalidConfiguration(
201 "supersequence contains an out-of-range symbol".into(),
202 ));
203 }
204 let config = config
205 .iter()
206 .map(|symbol| symbol.unwrap_or(self.alphabet_size))
207 .collect::<Vec<_>>();
208 Ok({
209 let pad = self.alphabet_size;
210
211 let effective_length = config
213 .iter()
214 .position(|&v| v == pad)
215 .unwrap_or(self.max_length);
216
217 for &v in &config[effective_length..] {
219 if v != pad {
220 return Ok(Min(None));
221 }
222 }
223
224 let prefix = &config[..effective_length];
225
226 if !self.strings.iter().all(|s| is_subsequence(s, prefix)) {
228 return Ok(Min(None));
229 }
230
231 Min(Some(i64::try_from(effective_length).map_err(|_| {
232 crate::traits::EvaluationError::IntegerOverflow(
233 "converting supersequence length to i64".into(),
234 )
235 })?))
236 })
237 }
238}
239
240impl crate::solvers::BruteForceProblem for ShortestCommonSupersequence {
241 fn dimensions(&self) -> Vec<usize> {
242 vec![self.alphabet_size + 1; self.max_length]
243 }
244}
245
246crate::declare_variants! {
247 default ShortestCommonSupersequence => "(alphabet_size + 1) ^ max_length" create ShortestCommonSupersequenceCreateSpec,
248}
249
250crate::register_brute_force! {
251 ShortestCommonSupersequence decode |problem: &ShortestCommonSupersequence, indices: Vec<usize>| indices.into_iter().map(|value| (value != problem.alphabet_size()).then_some(value)).collect(),
252}
253
254#[cfg(feature = "example-db")]
255pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
256 vec![crate::example_db::specs::ModelExampleSpec {
260 id: "shortest_common_supersequence",
261 instance: Box::new(ShortestCommonSupersequence::new(
262 2,
263 vec![vec![0, 1], vec![1, 0]],
264 )),
265 optimal_config: serde_json::json!(vec![Some(0), Some(1), Some(0), None]),
266 optimal_value: serde_json::json!(3),
267 }]
268}
269
270#[cfg(test)]
271#[path = "../../unit_tests/models/misc/shortest_common_supersequence.rs"]
272mod tests;