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)]
74pub struct ConsecutiveSets {
75    /// Size of the alphabet (elements are 0..alphabet_size-1).
76    alphabet_size: usize,
77    /// Collection of subsets of the alphabet, each sorted in canonical form.
78    subsets: Vec<Vec<usize>>,
79    /// Maximum string length K.
80    bound_k: usize,
81}
82
83impl ConsecutiveSets {
84    /// Create a new Consecutive Sets problem.
85    ///
86    /// # Panics
87    ///
88    /// Panics if `bound_k` is zero, if any subset contains duplicate elements,
89    /// or if any element is outside the alphabet.
90    pub fn new(alphabet_size: usize, subsets: Vec<Vec<usize>>, bound_k: usize) -> Self {
91        assert!(bound_k > 0, "bound_k must be positive, got 0");
92        let mut subsets = subsets;
93        for (i, subset) in subsets.iter_mut().enumerate() {
94            let mut seen = HashSet::with_capacity(subset.len());
95            for &elem in subset.iter() {
96                assert!(
97                    elem < alphabet_size,
98                    "Subset {} contains element {} which is outside alphabet of size {}",
99                    i,
100                    elem,
101                    alphabet_size
102                );
103                assert!(
104                    seen.insert(elem),
105                    "Subset {} contains duplicate elements",
106                    i
107                );
108            }
109            subset.sort();
110        }
111        Self {
112            alphabet_size,
113            subsets,
114            bound_k,
115        }
116    }
117
118    /// Get the alphabet size.
119    pub fn alphabet_size(&self) -> usize {
120        self.alphabet_size
121    }
122
123    /// Get the number of subsets in the collection.
124    pub fn num_subsets(&self) -> usize {
125        self.subsets.len()
126    }
127
128    /// Get the bound K.
129    pub fn bound_k(&self) -> usize {
130        self.bound_k
131    }
132
133    /// Get the subsets.
134    pub fn subsets(&self) -> &[Vec<usize>] {
135        &self.subsets
136    }
137}
138
139impl Problem for ConsecutiveSets {
140    const NAME: &'static str = "ConsecutiveSets";
141    type Solution = Vec<Option<usize>>;
142    type Value = crate::types::Or;
143
144    crate::problem_parameters![
145        ("alphabet_size", alphabet_size),
146        ("num_subsets", num_subsets),
147        ("bound_k", bound_k),
148    ];
149
150    fn evaluate(
151        &self,
152        config: &Self::Solution,
153    ) -> Result<crate::types::Or, crate::traits::EvaluationError> {
154        if config.len() != self.bound_k {
155            return Err(crate::traits::EvaluationError::InvalidConfiguration(
156                "ordering representation length does not match the bound".into(),
157            ));
158        }
159        if config
160            .iter()
161            .any(|symbol| symbol.is_some_and(|value| value >= self.alphabet_size))
162        {
163            return Err(crate::traits::EvaluationError::InvalidConfiguration(
164                "ordering representation contains an out-of-range symbol".into(),
165            ));
166        }
167        let config = config
168            .iter()
169            .map(|symbol| symbol.unwrap_or(self.alphabet_size))
170            .collect::<Vec<_>>();
171        Ok({
172            crate::types::Or({
173                // 2. Build string: find the actual string length (strip trailing "unused")
174                let unused = self.alphabet_size;
175                let str_len = config
176                    .iter()
177                    .rposition(|&v| v != unused)
178                    .map_or(0, |p| p + 1);
179
180                // 3. Check no internal "unused" symbols
181                let w = &config[..str_len];
182                if w.contains(&unused) {
183                    return Ok(crate::types::Or(false));
184                }
185
186                let mut subset_membership = vec![0usize; self.alphabet_size];
187                let mut seen_in_window = vec![0usize; self.alphabet_size];
188                let mut subset_stamp = 1usize;
189                let mut window_stamp = 1usize;
190
191                // 4. Check each subset has a consecutive block
192                for subset in &self.subsets {
193                    let subset_len = subset.len();
194                    if subset_len == 0 {
195                        continue; // empty subset trivially satisfied
196                    }
197                    if subset_len > str_len {
198                        return Ok(crate::types::Or(false)); // can't fit
199                    }
200
201                    for &elem in subset {
202                        subset_membership[elem] = subset_stamp;
203                    }
204
205                    let mut found = false;
206                    for start in 0..=(str_len - subset_len) {
207                        let window = &w[start..start + subset_len];
208                        let current_window_stamp = window_stamp;
209                        window_stamp += 1;
210
211                        // Because subsets are validated to contain unique elements,
212                        // a window matches iff every symbol belongs to the subset and
213                        // appears at most once.
214                        if window.iter().all(|&elem| {
215                            let is_member = subset_membership[elem] == subset_stamp;
216                            let is_new = seen_in_window[elem] != current_window_stamp;
217                            if is_member && is_new {
218                                seen_in_window[elem] = current_window_stamp;
219                                true
220                            } else {
221                                false
222                            }
223                        }) {
224                            // subset is already sorted
225                            found = true;
226                            break;
227                        }
228                    }
229                    if !found {
230                        return Ok(crate::types::Or(false));
231                    }
232
233                    subset_stamp += 1;
234                }
235
236                true
237            })
238        })
239    }
240
241    fn variant() -> Vec<(&'static str, &'static str)> {
242        crate::variant_params![]
243    }
244}
245
246impl crate::solvers::BruteForceProblem for ConsecutiveSets {
247    fn dimensions(&self) -> Vec<usize> {
248        // Each position can be any symbol (0..alphabet_size-1) or "unused" (alphabet_size)
249        vec![self.alphabet_size + 1; self.bound_k]
250    }
251}
252
253crate::declare_variants! {
254    default ConsecutiveSets => "alphabet_size^bound_k * num_subsets",
255}
256
257crate::register_brute_force! {
258    ConsecutiveSets decode |problem: &ConsecutiveSets, indices: Vec<usize>| indices.into_iter().map(|value| (value != problem.alphabet_size()).then_some(value)).collect(),
259}
260
261#[cfg(feature = "example-db")]
262pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
263    vec![crate::example_db::specs::ModelExampleSpec {
264        id: "consecutive_sets",
265        // YES instance from issue: w = [0, 4, 2, 5, 1, 3]
266        instance: Box::new(ConsecutiveSets::new(
267            6,
268            vec![vec![0, 4], vec![2, 4], vec![2, 5], vec![1, 5], vec![1, 3]],
269            6,
270        )),
271        optimal_config: serde_json::json!(vec![0, 4, 2, 5, 1, 3]),
272        optimal_value: serde_json::json!(true),
273    }]
274}
275
276#[cfg(test)]
277#[path = "../../unit_tests/models/set/consecutive_sets.rs"]
278mod tests;