problemreductions/models/misc/
longest_common_subsequence.rs1use crate::registry::{FieldInfo, ProblemSchemaEntry};
9use crate::traits::Problem;
10use crate::types::Max;
11use serde::{Deserialize, Serialize};
12
13inventory::submit! {
14 ProblemSchemaEntry {
15 name: "LongestCommonSubsequence",
16 display_name: "Longest Common Subsequence",
17 aliases: &["LCS"],
18 dimensions: &[],
19 module_path: module_path!(),
20 description: "Find a longest common subsequence for a set of strings",
21 fields: &[
22 FieldInfo { name: "alphabet_size", type_name: "usize", description: "Size of the alphabet" },
23 FieldInfo { name: "strings", type_name: "Vec<Vec<usize>>", description: "Input strings over the alphabet {0, ..., alphabet_size-1}" },
24 FieldInfo { name: "max_length", type_name: "usize", description: "Maximum possible subsequence length (min of string lengths)" },
25 ],
26 }
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct LongestCommonSubsequence {
43 alphabet_size: usize,
44 strings: Vec<Vec<usize>>,
45 max_length: usize,
46}
47
48impl LongestCommonSubsequence {
49 pub fn new(alphabet_size: usize, strings: Vec<Vec<usize>>) -> Self {
60 let max_length = strings.iter().map(|s| s.len()).min().unwrap_or(0);
61 assert!(
62 alphabet_size > 0 || strings.iter().all(|s| s.is_empty()),
63 "alphabet_size must be > 0 when any input string is non-empty"
64 );
65 assert!(
66 strings
67 .iter()
68 .flat_map(|s| s.iter())
69 .all(|&symbol| symbol < alphabet_size),
70 "input symbols must be less than alphabet_size"
71 );
72 Self {
73 alphabet_size,
74 strings,
75 max_length,
76 }
77 }
78
79 pub fn alphabet_size(&self) -> usize {
81 self.alphabet_size
82 }
83
84 pub fn strings(&self) -> &[Vec<usize>] {
86 &self.strings
87 }
88
89 pub fn max_length(&self) -> usize {
91 self.max_length
92 }
93
94 pub fn num_strings(&self) -> usize {
96 self.strings.len()
97 }
98
99 pub fn total_length(&self) -> usize {
101 self.strings.iter().map(|s| s.len()).sum()
102 }
103
104 pub fn sum_squared_lengths(&self) -> usize {
106 self.strings.iter().map(|s| s.len() * s.len()).sum()
107 }
108
109 pub fn sum_triangular_lengths(&self) -> usize {
111 self.strings
112 .iter()
113 .map(|s| s.len() * (s.len() + 1) / 2)
114 .sum()
115 }
116
117 pub fn num_transitions(&self) -> usize {
119 self.max_length.saturating_sub(1)
120 }
121
122 pub fn cross_frequency_product(&self) -> usize {
131 (0..self.alphabet_size)
132 .map(|c| {
133 self.strings
134 .iter()
135 .map(|s| s.iter().filter(|&&sym| sym == c).count())
136 .product::<usize>()
137 })
138 .sum()
139 }
140}
141
142fn is_subsequence(candidate: &[usize], target: &[usize]) -> bool {
145 let mut it = target.iter();
146 for &symbol in candidate {
147 loop {
148 match it.next() {
149 Some(&next) if next == symbol => break,
150 Some(_) => continue,
151 None => return false,
152 }
153 }
154 }
155 true
156}
157
158impl Problem for LongestCommonSubsequence {
159 const NAME: &'static str = "LongestCommonSubsequence";
160 type Value = Max<usize>;
161
162 fn variant() -> Vec<(&'static str, &'static str)> {
163 crate::variant_params![]
164 }
165
166 fn dims(&self) -> Vec<usize> {
167 vec![self.alphabet_size + 1; self.max_length]
168 }
169
170 fn evaluate(&self, config: &[usize]) -> Max<usize> {
171 if config.len() != self.max_length {
172 return Max(None);
173 }
174
175 let padding = self.alphabet_size;
176
177 let effective_length = config
179 .iter()
180 .position(|&s| s == padding)
181 .unwrap_or(self.max_length);
182
183 if config[effective_length..].iter().any(|&s| s != padding) {
185 return Max(None);
186 }
187
188 let prefix = &config[..effective_length];
190
191 if prefix.iter().any(|&s| s >= self.alphabet_size) {
193 return Max(None);
194 }
195
196 if !self.strings.iter().all(|s| is_subsequence(prefix, s)) {
198 return Max(None);
199 }
200
201 Max(Some(effective_length))
202 }
203}
204
205crate::declare_variants! {
206 default LongestCommonSubsequence => "(alphabet_size + 1) ^ max_length",
207}
208
209#[cfg(feature = "example-db")]
210pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
211 vec![crate::example_db::specs::ModelExampleSpec {
212 id: "longest_common_subsequence",
213 instance: Box::new(LongestCommonSubsequence::new(
214 2,
215 vec![
216 vec![0, 1, 0, 1, 1, 0],
217 vec![1, 0, 0, 1, 0, 1],
218 vec![0, 0, 1, 0, 1, 1],
219 vec![1, 1, 0, 0, 1, 0],
220 vec![0, 1, 0, 1, 0, 1],
221 vec![1, 0, 1, 0, 1, 0],
222 ],
223 )),
224 optimal_config: vec![0, 0, 1, 0, 2, 2],
225 optimal_value: serde_json::json!(4),
226 }]
227}
228
229#[cfg(test)]
230#[path = "../../unit_tests/models/misc/longest_common_subsequence.rs"]
231mod tests;