problemreductions/models/misc/
closest_string.rs1use crate::registry::{FieldInfo, ProblemSchemaEntry};
9use crate::traits::Problem;
10use crate::types::Min;
11use serde::{Deserialize, Serialize};
12
13inventory::submit! {
14 ProblemSchemaEntry {
15 name: "ClosestString",
16 display_name: "Closest String",
17 aliases: &[],
18 dimensions: &[],
19 category: crate::registry::ProblemCategory::Misc,
20 module_path: module_path!(),
21 description: "Find a center string of fixed length that minimizes the maximum Hamming distance to a list of equal-length input strings",
22 fields: &[
23 FieldInfo {
24 name: "alphabet_size",
25 type_name: "usize",
26 description: "Size q of the finite alphabet {0, ..., q-1}",
27 },
28 FieldInfo {
29 name: "strings",
30 type_name: "Vec<Vec<usize>>",
31 description: "Input strings s_1, ..., s_n over the alphabet, all of equal length m",
32 },
33 ],
34 }
35}
36
37#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct ClosestString {
50 alphabet_size: usize,
51 strings: Vec<Vec<usize>>,
52}
53
54impl ClosestString {
55 pub fn new(alphabet_size: usize, strings: Vec<Vec<usize>>) -> Self {
65 assert!(
66 !strings.is_empty(),
67 "ClosestString requires at least one input string"
68 );
69 let string_length = strings[0].len();
70 assert!(
71 strings.iter().all(|s| s.len() == string_length),
72 "all input strings must have the same length"
73 );
74 assert!(
75 alphabet_size > 0 || string_length == 0,
76 "alphabet_size must be > 0 when input strings are non-empty"
77 );
78 assert!(
79 strings
80 .iter()
81 .flat_map(|s| s.iter())
82 .all(|&symbol| symbol < alphabet_size),
83 "input symbols must be less than alphabet_size"
84 );
85 Self {
86 alphabet_size,
87 strings,
88 }
89 }
90
91 pub fn alphabet_size(&self) -> usize {
93 self.alphabet_size
94 }
95
96 pub fn strings(&self) -> &[Vec<usize>] {
98 &self.strings
99 }
100
101 pub fn num_strings(&self) -> usize {
103 self.strings.len()
104 }
105
106 pub fn string_length(&self) -> usize {
108 self.strings[0].len()
109 }
110
111 pub fn total_length(&self) -> usize {
113 self.num_strings() * self.string_length()
114 }
115}
116
117impl Problem for ClosestString {
118 const NAME: &'static str = "ClosestString";
119 type Solution = Vec<usize>;
120 type Value = Min<i64>;
121
122 crate::problem_parameters![
123 ("alphabet_size", alphabet_size),
124 ("num_strings", num_strings),
125 ("string_length", string_length),
126 ("total_length", total_length),
127 ];
128
129 fn variant() -> Vec<(&'static str, &'static str)> {
130 crate::variant_params![]
131 }
132
133 fn evaluate(
134 &self,
135 config: &Self::Solution,
136 ) -> Result<Min<i64>, crate::traits::EvaluationError> {
137 Ok({
138 let m = self.string_length();
139 if config.len() != m {
140 return Err(crate::traits::EvaluationError::InvalidConfiguration(
141 "candidate string length does not match the instance strings".into(),
142 ));
143 }
144 if config.iter().any(|&symbol| symbol >= self.alphabet_size) {
145 return Err(crate::traits::EvaluationError::InvalidConfiguration(
146 "candidate string contains an out-of-range symbol".into(),
147 ));
148 }
149 let mut max_distance = 0_i64;
151 for string in &self.strings {
152 let distance = i64::try_from(
153 config
154 .iter()
155 .zip(string.iter())
156 .filter(|(center, target)| center != target)
157 .count(),
158 )
159 .map_err(|_| {
160 crate::traits::EvaluationError::IntegerOverflow(
161 "converting Hamming distance to i64".into(),
162 )
163 })?;
164 max_distance = max_distance.max(distance);
165 }
166 Min(Some(max_distance))
167 })
168 }
169}
170
171impl crate::solvers::BruteForceProblem for ClosestString {
172 fn dimensions(&self) -> Vec<usize> {
173 vec![self.alphabet_size; self.string_length()]
174 }
175}
176
177crate::declare_variants! {
178 default ClosestString => "alphabet_size ^ string_length",
179}
180
181crate::register_brute_force! {
182 ClosestString,
183}
184
185#[cfg(feature = "example-db")]
186pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
187 vec![crate::example_db::specs::ModelExampleSpec {
188 id: "closest_string",
189 instance: Box::new(ClosestString::new(
190 2,
191 vec![vec![0, 0, 0], vec![0, 1, 1], vec![1, 0, 1], vec![1, 1, 0]],
192 )),
193 optimal_config: serde_json::json!(vec![0, 0, 0]),
194 optimal_value: serde_json::json!(2),
195 }]
196}
197
198#[cfg(test)]
199#[path = "../../unit_tests/models/misc/closest_string.rs"]
200mod tests;