problemreductions/models/misc/
string_to_string_correction.rs1use crate::registry::{CreateSpec, ProblemSchemaEntry};
18use crate::traits::Problem;
19use serde::{Deserialize, Serialize};
20
21inventory::submit! {
22 ProblemSchemaEntry {
23 name: "StringToStringCorrection",
24 display_name: "String-to-String Correction",
25 aliases: &[],
26 dimensions: &[],
27 category: crate::registry::ProblemCategory::Misc,
28 module_path: module_path!(),
29 description: "Derive target string from source using at most K deletions and adjacent swaps",
30 fields: StringToStringCorrectionCreateSpec::FIELDS,
31 }
32}
33
34#[derive(Debug, Clone, Serialize, Deserialize)]
69#[serde(try_from = "StringToStringCorrectionData")]
70pub struct StringToStringCorrection {
71 alphabet_size: usize,
72 source: Vec<usize>,
73 target: Vec<usize>,
74 bound: usize,
75}
76
77#[derive(Deserialize)]
78struct StringToStringCorrectionData {
79 alphabet_size: usize,
80 source: Vec<usize>,
81 target: Vec<usize>,
82 bound: usize,
83}
84
85impl TryFrom<StringToStringCorrectionData> for StringToStringCorrection {
86 type Error = crate::registry::ConstructionError;
87
88 fn try_from(data: StringToStringCorrectionData) -> Result<Self, Self::Error> {
89 Self::try_new(data.alphabet_size, data.source, data.target, data.bound)
90 }
91}
92
93#[derive(Debug, Deserialize, crate::CreateSpec)]
94struct StringToStringCorrectionCreateSpec {
95 alphabet_size: Option<usize>,
97 #[create(codec = "comma-separated")]
99 source_string: Vec<usize>,
100 #[create(codec = "comma-separated")]
102 target_string: Vec<usize>,
103 bound: usize,
105}
106
107impl TryFrom<StringToStringCorrectionCreateSpec> for StringToStringCorrection {
108 type Error = crate::registry::ConstructionError;
109
110 fn try_from(spec: StringToStringCorrectionCreateSpec) -> Result<Self, Self::Error> {
111 let inferred_alphabet_size = spec
112 .source_string
113 .iter()
114 .chain(&spec.target_string)
115 .copied()
116 .max()
117 .map(|symbol| {
118 symbol
119 .checked_add(1)
120 .ok_or_else(|| "inferred alphabet size overflows usize".to_string())
121 })
122 .transpose()?
123 .unwrap_or(0);
124 let alphabet_size = spec.alphabet_size.unwrap_or(inferred_alphabet_size);
125 Self::try_new(
126 alphabet_size,
127 spec.source_string,
128 spec.target_string,
129 spec.bound,
130 )
131 }
132}
133
134impl StringToStringCorrection {
135 pub fn new(alphabet_size: usize, source: Vec<usize>, target: Vec<usize>, bound: usize) -> Self {
143 Self::try_new(alphabet_size, source, target, bound)
144 .unwrap_or_else(|error| panic!("{error}"))
145 }
146
147 fn try_new(
148 alphabet_size: usize,
149 source: Vec<usize>,
150 target: Vec<usize>,
151 bound: usize,
152 ) -> Result<Self, crate::registry::ConstructionError> {
153 if !(alphabet_size > 0 || (source.is_empty() && target.is_empty())) {
154 return Err("alphabet_size must be > 0 when source or target is non-empty".into());
155 }
156 if !(source.iter().all(|&s| s < alphabet_size)) {
157 return Err("all source symbols must be < alphabet_size".into());
158 }
159 if !(target.iter().all(|&s| s < alphabet_size)) {
160 return Err("all target symbols must be < alphabet_size".into());
161 }
162 Ok(Self {
163 alphabet_size,
164 source,
165 target,
166 bound,
167 })
168 }
169
170 pub fn alphabet_size(&self) -> usize {
172 self.alphabet_size
173 }
174
175 pub fn source(&self) -> &[usize] {
177 &self.source
178 }
179
180 pub fn target(&self) -> &[usize] {
182 &self.target
183 }
184
185 pub fn bound(&self) -> usize {
187 self.bound
188 }
189
190 pub fn source_length(&self) -> usize {
192 self.source.len()
193 }
194
195 pub fn target_length(&self) -> usize {
197 self.target.len()
198 }
199}
200
201impl Problem for StringToStringCorrection {
202 const NAME: &'static str = "StringToStringCorrection";
203 type Solution = Vec<usize>;
204 type Value = crate::types::Or;
205
206 crate::problem_parameters![("bound", bound), ("source_length", source_length),];
207
208 fn variant() -> Vec<(&'static str, &'static str)> {
209 crate::variant_params![]
210 }
211
212 fn evaluate(
213 &self,
214 config: &Self::Solution,
215 ) -> Result<crate::types::Or, crate::traits::EvaluationError> {
216 Ok({
217 crate::types::Or({
218 if config.len() != self.bound {
219 return Err(crate::traits::EvaluationError::InvalidConfiguration(
220 "edit-program length does not match the operation bound".into(),
221 ));
222 }
223 if self.target.len() > self.source.len()
224 || self.target.len() < self.source.len().saturating_sub(self.bound)
225 {
226 return Ok(crate::types::Or(false));
227 }
228 let n = self.source.len();
229 let domain = 2 * n + 1;
230 if config.iter().any(|&v| v >= domain) {
231 return Err(crate::traits::EvaluationError::InvalidConfiguration(
232 "edit program contains an out-of-range operation".into(),
233 ));
234 }
235 let noop = 2 * n;
236 let mut working = self.source.clone();
237 for &op in config {
238 if op == noop {
239 continue;
241 }
242 let current_len = working.len();
243 if op < current_len {
244 working.remove(op);
246 } else {
247 let swap_pos = op - current_len;
248 if swap_pos + 1 < current_len {
249 working.swap(swap_pos, swap_pos + 1);
250 } else {
251 return Ok(crate::types::Or(false));
253 }
254 }
255 }
256 working == self.target
257 })
258 })
259 }
260}
261
262impl crate::solvers::BruteForceProblem for StringToStringCorrection {
263 fn dimensions(&self) -> Vec<usize> {
264 vec![2 * self.source.len() + 1; self.bound]
265 }
266}
267
268crate::declare_variants! {
269 default StringToStringCorrection => "(2 * source_length + 1) ^ bound" create StringToStringCorrectionCreateSpec,
270}
271
272crate::register_brute_force! {
273 StringToStringCorrection,
274}
275
276#[cfg(feature = "example-db")]
277pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
278 vec![crate::example_db::specs::ModelExampleSpec {
279 id: "string_to_string_correction",
280 instance: Box::new(StringToStringCorrection::new(
284 4,
285 vec![0, 1, 2, 3, 1, 0],
286 vec![0, 1, 3, 2, 1],
287 2,
288 )),
289 optimal_config: serde_json::json!(vec![8, 5]),
290 optimal_value: serde_json::json!(true),
291 }]
292}
293
294#[cfg(test)]
295#[path = "../../unit_tests/models/misc/string_to_string_correction.rs"]
296mod tests;