Skip to main content

problemreductions/models/set/
consecutive_sets.rs

1//! Consecutive Sets problem implementation.
2//!
3//! Given an alphabet of size n, a collection of subsets of the alphabet, and a
4//! bound K, determine if there exists a string of length at most K over the
5//! alphabet such that the elements of each subset appear consecutively (as a
6//! contiguous block in some order) within the string.
7
8use crate::registry::{FieldInfo, ProblemSchemaEntry};
9use crate::traits::Problem;
10use serde::{Deserialize, Serialize};
11use std::collections::HashSet;
12
13inventory::submit! {
14    ProblemSchemaEntry {
15        name: "ConsecutiveSets",
16        display_name: "Consecutive Sets",
17        aliases: &[],
18        dimensions: &[],
19        category: crate::registry::ProblemCategory::Set,
20        module_path: module_path!(),
21        description: "Determine if a string exists where each subset's elements appear consecutively",
22        fields: &[
23            FieldInfo { name: "alphabet_size", type_name: "usize", description: "Size of the alphabet (elements are 0..alphabet_size-1)" },
24            FieldInfo { name: "subsets", type_name: "Vec<Vec<usize>>", description: "Collection of subsets of the alphabet" },
25            FieldInfo { name: "bound_k", type_name: "usize", description: "Maximum string length K" },
26        ],
27    }
28}
29
30/// Consecutive Sets problem.
31///
32/// Given an alphabet {0, 1, ..., n-1}, a collection of subsets, and a bound K,
33/// determine if there exists a string w of length at most K over the alphabet
34/// such that the elements of each subset appear as a contiguous block (in any
35/// order) within w.
36///
37/// Solutions use `bound_k` positions. `Some(symbol)` represents an alphabet
38/// symbol and trailing `None` positions mark the unused suffix of a shorter
39/// string.
40///
41/// This problem is NP-complete and arises in physical mapping of DNA and in
42/// consecutive arrangements of hypergraph vertices.
43///
44/// # Example
45///
46/// ```
47/// use problemreductions::models::set::ConsecutiveSets;
48/// use problemreductions::{Problem, BruteForce};
49///
50/// // Alphabet: {0, 1, 2, 3, 4, 5}, subsets that must appear consecutively
51/// let problem = ConsecutiveSets::new(
52///     6,
53///     vec![vec![0, 4], vec![2, 4], vec![2, 5], vec![1, 5], vec![1, 3]],
54///     6,
55/// );
56///
57/// let solver = BruteForce::new();
58/// let solution = solver.solve(&problem).unwrap();
59///
60/// // w = [0, 4, 2, 5, 1, 3] is a valid solution
61/// assert!(solution.is_some());
62/// assert!(problem.evaluate(&solution.unwrap()).unwrap());
63///
64/// // Shorter strings use trailing `None` positions.
65/// let shorter = ConsecutiveSets::new(3, vec![vec![0, 1]], 4);
66/// assert!(shorter
67///     .evaluate(&vec![Some(0), Some(1), None, None])
68///     .unwrap());
69/// assert!(!shorter
70///     .evaluate(&vec![Some(0), None, Some(1), None])
71///     .unwrap());
72/// ```
73#[derive(Debug, Clone, Serialize, Deserialize)]
74#[serde(try_from = "ConsecutiveSetsData")]
75pub struct ConsecutiveSets {
76    /// Size of the alphabet (elements are 0..alphabet_size-1).
77    alphabet_size: usize,
78    /// Collection of subsets of the alphabet, each sorted in canonical form.
79    subsets: Vec<Vec<usize>>,
80    /// Maximum string length K.
81    bound_k: usize,
82}
83
84#[derive(Deserialize)]
85struct ConsecutiveSetsData {
86    alphabet_size: usize,
87    subsets: Vec<Vec<usize>>,
88    bound_k: usize,
89}
90
91impl TryFrom<ConsecutiveSetsData> for ConsecutiveSets {
92    type Error = crate::registry::ConstructionError;
93
94    fn try_from(data: ConsecutiveSetsData) -> Result<Self, Self::Error> {
95        Self::try_new(data.alphabet_size, data.subsets, data.bound_k)
96    }
97}
98
99impl ConsecutiveSets {
100    /// Create a new Consecutive Sets problem.
101    ///
102    /// # Panics
103    ///
104    /// Panics if `bound_k` is zero, if any subset contains duplicate elements,
105    /// or if any element is outside the alphabet.
106    pub fn new(alphabet_size: usize, subsets: Vec<Vec<usize>>, bound_k: usize) -> Self {
107        Self::try_new(alphabet_size, subsets, bound_k).unwrap_or_else(|error| panic!("{error}"))
108    }
109
110    fn try_new(
111        alphabet_size: usize,
112        mut subsets: Vec<Vec<usize>>,
113        bound_k: usize,
114    ) -> Result<Self, crate::registry::ConstructionError> {
115        if bound_k == 0 {
116            return Err("bound_k must be positive, got 0".into());
117        }
118        for (index, subset) in subsets.iter_mut().enumerate() {
119            let mut seen = HashSet::with_capacity(subset.len());
120            for &element in subset.iter() {
121                if element >= alphabet_size {
122                    return Err(format!("subset {index} contains element {element} outside alphabet of size {alphabet_size}").into());
123                }
124                if !seen.insert(element) {
125                    return Err(format!("subset {index} contains duplicate elements").into());
126                }
127            }
128            subset.sort();
129        }
130        Ok(Self {
131            alphabet_size,
132            subsets,
133            bound_k,
134        })
135    }
136
137    /// Get the alphabet size.
138    pub fn alphabet_size(&self) -> usize {
139        self.alphabet_size
140    }
141
142    /// Get the number of subsets in the collection.
143    pub fn num_subsets(&self) -> usize {
144        self.subsets.len()
145    }
146
147    /// Get the bound K.
148    pub fn bound_k(&self) -> usize {
149        self.bound_k
150    }
151
152    /// Get the subsets.
153    pub fn subsets(&self) -> &[Vec<usize>] {
154        &self.subsets
155    }
156}
157
158impl Problem for ConsecutiveSets {
159    const NAME: &'static str = "ConsecutiveSets";
160    type Solution = Vec<Option<usize>>;
161    type Value = crate::types::Or;
162
163    crate::problem_parameters![
164        ("alphabet_size", alphabet_size),
165        ("num_subsets", num_subsets),
166        ("bound_k", bound_k),
167    ];
168
169    fn evaluate(
170        &self,
171        config: &Self::Solution,
172    ) -> Result<crate::types::Or, crate::traits::EvaluationError> {
173        if config.len() != self.bound_k {
174            return Err(crate::traits::EvaluationError::InvalidConfiguration(
175                "ordering representation length does not match the bound".into(),
176            ));
177        }
178        if config
179            .iter()
180            .any(|symbol| symbol.is_some_and(|value| value >= self.alphabet_size))
181        {
182            return Err(crate::traits::EvaluationError::InvalidConfiguration(
183                "ordering representation contains an out-of-range symbol".into(),
184            ));
185        }
186        let config = config
187            .iter()
188            .map(|symbol| symbol.unwrap_or(self.alphabet_size))
189            .collect::<Vec<_>>();
190        Ok({
191            crate::types::Or({
192                // 2. Build string: find the actual string length (strip trailing "unused")
193                let unused = self.alphabet_size;
194                let str_len = config
195                    .iter()
196                    .rposition(|&v| v != unused)
197                    .map_or(0, |p| p + 1);
198
199                // 3. Check no internal "unused" symbols
200                let w = &config[..str_len];
201                if w.contains(&unused) {
202                    return Ok(crate::types::Or(false));
203                }
204
205                let mut subset_membership = vec![0usize; self.alphabet_size];
206                let mut seen_in_window = vec![0usize; self.alphabet_size];
207                let mut subset_stamp = 1usize;
208                let mut window_stamp = 1usize;
209
210                // 4. Check each subset has a consecutive block
211                for subset in &self.subsets {
212                    let subset_len = subset.len();
213                    if subset_len == 0 {
214                        continue; // empty subset trivially satisfied
215                    }
216                    if subset_len > str_len {
217                        return Ok(crate::types::Or(false)); // can't fit
218                    }
219
220                    for &elem in subset {
221                        subset_membership[elem] = subset_stamp;
222                    }
223
224                    let mut found = false;
225                    for start in 0..=(str_len - subset_len) {
226                        let window = &w[start..start + subset_len];
227                        let current_window_stamp = window_stamp;
228                        window_stamp += 1;
229
230                        // Because subsets are validated to contain unique elements,
231                        // a window matches iff every symbol belongs to the subset and
232                        // appears at most once.
233                        if window.iter().all(|&elem| {
234                            let is_member = subset_membership[elem] == subset_stamp;
235                            let is_new = seen_in_window[elem] != current_window_stamp;
236                            if is_member && is_new {
237                                seen_in_window[elem] = current_window_stamp;
238                                true
239                            } else {
240                                false
241                            }
242                        }) {
243                            // subset is already sorted
244                            found = true;
245                            break;
246                        }
247                    }
248                    if !found {
249                        return Ok(crate::types::Or(false));
250                    }
251
252                    subset_stamp += 1;
253                }
254
255                true
256            })
257        })
258    }
259
260    fn variant() -> Vec<(&'static str, &'static str)> {
261        crate::variant_params![]
262    }
263}
264
265impl crate::solvers::BruteForceProblem for ConsecutiveSets {
266    fn dimensions(&self) -> Vec<usize> {
267        // Each position can be any symbol (0..alphabet_size-1) or "unused" (alphabet_size)
268        vec![self.alphabet_size + 1; self.bound_k]
269    }
270}
271
272crate::declare_variants! {
273    default ConsecutiveSets => "alphabet_size^bound_k * num_subsets",
274}
275
276crate::register_brute_force! {
277    ConsecutiveSets decode |problem: &ConsecutiveSets, indices: Vec<usize>| indices.into_iter().map(|value| (value != problem.alphabet_size()).then_some(value)).collect(),
278}
279
280#[cfg(feature = "example-db")]
281pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
282    vec![crate::example_db::specs::ModelExampleSpec {
283        id: "consecutive_sets",
284        // YES instance from issue: w = [0, 4, 2, 5, 1, 3]
285        instance: Box::new(ConsecutiveSets::new(
286            6,
287            vec![vec![0, 4], vec![2, 4], vec![2, 5], vec![1, 5], vec![1, 3]],
288            6,
289        )),
290        optimal_config: serde_json::json!(vec![0, 4, 2, 5, 1, 3]),
291        optimal_value: serde_json::json!(true),
292    }]
293}
294
295#[cfg(test)]
296#[path = "../../unit_tests/models/set/consecutive_sets.rs"]
297mod tests;