Skip to content

Commit 514d5ff

Browse files
GiggleLiuzazabapclaude
authored
Fix #421: Add ConsecutiveSets model (#670)
* Add plan for #421: ConsecutiveSets model * Implement ConsecutiveSets model (#421) * Add trait_consistency entry and regenerate schemas * Review fixes: prelude re-export, test improvements, fixture regen * Add ConsecutiveSets paper entry with bibliography * chore: remove plan file after implementation * fix: avoid per-window sorting in ConsecutiveSets * test: cover shorter ConsecutiveSets strings * docs: clarify ConsecutiveSets config encoding --------- Co-authored-by: zazabap <sweynan@icloud.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 3e409d1 commit 514d5ff

8 files changed

Lines changed: 425 additions & 5 deletions

File tree

docs/paper/reductions.typ

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@
9999
"BoundedComponentSpanningForest": [Bounded Component Spanning Forest],
100100
"BinPacking": [Bin Packing],
101101
"ClosestVectorProblem": [Closest Vector Problem],
102+
"ConsecutiveSets": [Consecutive Sets],
102103
"MinimumMultiwayCut": [Minimum Multiway Cut],
103104
"OptimalLinearArrangement": [Optimal Linear Arrangement],
104105
"RuralPostman": [Rural Postman],
@@ -1395,6 +1396,25 @@ NP-completeness was established by Garey, Johnson, and Stockmeyer @gareyJohnsonS
13951396
]
13961397
}
13971398

1399+
#{
1400+
let x = load-model-example("ConsecutiveSets")
1401+
let m = x.instance.alphabet_size
1402+
let n = x.instance.subsets.len()
1403+
let K = x.instance.bound_k
1404+
let subs = x.instance.subsets
1405+
let sol = x.optimal.at(0).config
1406+
let fmt-set(s) = "${" + s.map(e => str(e)).join(", ") + "}$"
1407+
[
1408+
#problem-def("ConsecutiveSets")[
1409+
Given a finite alphabet $Sigma$ of size $m$, a collection $cal(C) = {Sigma_1, Sigma_2, dots, Sigma_n}$ of subsets of $Sigma$, and a positive integer $K$, determine whether there exists a string $w in Sigma^*$ with $|w| lt.eq K$ such that, for each $i$, the elements of $Sigma_i$ occur in a consecutive block of $|Sigma_i|$ symbols of $w$.
1410+
][
1411+
This problem arises in information retrieval and file organization (SR18 in Garey and Johnson @garey1979). It generalizes the _consecutive ones property_ from binary matrices to a string-based formulation: given subsets of an alphabet, construct the shortest string where each subset's elements appear contiguously. The problem is NP-complete, as shown by #cite(<kou1977>, form: "prose") via reduction from Hamiltonian Path. The circular variant, where blocks may wrap around from the end of $w$ back to its beginning (considering $w w$), is also NP-complete @boothlueker1976. When $K$ equals the number of distinct symbols appearing in the subsets, the problem reduces to testing a binary matrix for the consecutive ones property, which is solvable in linear time using PQ-tree algorithms @boothlueker1976.
1412+
1413+
*Example.* Let $Sigma = {0, 1, dots, #(m - 1)}$, $K = #K$, and $cal(C) = {#range(n).map(i => $Sigma_#(i + 1)$).join(", ")}$ with #range(n).map(i => $Sigma_#(i + 1) = #fmt-set(subs.at(i))$).join(", "). A valid string is $w = (#sol.map(e => str(e)).join(", "))$ with $|w| = #sol.len() = K$: $Sigma_1 = {0, 4}$ appears as the block $(0, 4)$ at positions 0--1, $Sigma_2 = {2, 4}$ appears as $(4, 2)$ at positions 1--2, $Sigma_3 = {2, 5}$ appears as $(2, 5)$ at positions 2--3, $Sigma_4 = {1, 5}$ appears as $(5, 1)$ at positions 3--4, and $Sigma_5 = {1, 3}$ appears as $(1, 3)$ at positions 4--5.
1414+
]
1415+
]
1416+
}
1417+
13981418
#{
13991419
let x3c = load-model-example("ExactCoverBy3Sets")
14001420
let n = x3c.instance.universe_size

docs/paper/references.bib

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -797,3 +797,25 @@ @article{papadimitriou1982
797797
year = {1982},
798798
doi = {10.1145/322307.322309}
799799
}
800+
801+
@article{kou1977,
802+
author = {Lawrence T. Kou},
803+
title = {Polynomial Complete Consecutive Information Retrieval Problems},
804+
journal = {SIAM Journal on Computing},
805+
volume = {6},
806+
number = {1},
807+
pages = {67--75},
808+
year = {1977},
809+
doi = {10.1137/0206005}
810+
}
811+
812+
@article{boothlueker1976,
813+
author = {Kellogg S. Booth and George S. Lueker},
814+
title = {Testing for the Consecutive Ones Property, Interval Graphs, and Graph Planarity Using {PQ}-Tree Algorithms},
815+
journal = {Journal of Computer and System Sciences},
816+
volume = {13},
817+
number = {3},
818+
pages = {335--379},
819+
year = {1976},
820+
doi = {10.1016/S0022-0000(76)80045-1}
821+
}

src/example_db/fixtures/examples.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
{"problem":"CircuitSAT","variant":{},"instance":{"circuit":{"assignments":[{"expr":{"op":{"And":[{"op":{"Var":"x1"}},{"op":{"Var":"x2"}}]}},"outputs":["a"]},{"expr":{"op":{"Or":[{"op":{"Var":"x1"}},{"op":{"Var":"x2"}}]}},"outputs":["b"]},{"expr":{"op":{"Xor":[{"op":{"Var":"a"}},{"op":{"Var":"b"}}]}},"outputs":["c"]}]},"variables":["a","b","c","x1","x2"]},"samples":[{"config":[0,1,1,0,1],"metric":true},{"config":[0,1,1,1,0],"metric":true}],"optimal":[{"config":[0,0,0,0,0],"metric":true},{"config":[0,1,1,0,1],"metric":true},{"config":[0,1,1,1,0],"metric":true},{"config":[1,1,0,1,1],"metric":true}]},
99
{"problem":"ClosestVectorProblem","variant":{"weight":"i32"},"instance":{"basis":[[2,0],[1,2]],"bounds":[{"lower":-2,"upper":4},{"lower":-2,"upper":4}],"target":[2.8,1.5]},"samples":[{"config":[3,3],"metric":{"Valid":0.5385164807134505}}],"optimal":[{"config":[3,3],"metric":{"Valid":0.5385164807134505}}]},
1010
{"problem":"ComparativeContainment","variant":{"weight":"i32"},"instance":{"r_sets":[[0,1,2,3],[0,1]],"r_weights":[2,5],"s_sets":[[0,1,2,3],[2,3]],"s_weights":[3,6],"universe_size":4},"samples":[{"config":[1,0,0,0],"metric":true}],"optimal":[{"config":[0,1,0,0],"metric":true},{"config":[1,0,0,0],"metric":true},{"config":[1,1,0,0],"metric":true}]},
11+
{"problem":"ConsecutiveSets","variant":{},"instance":{"alphabet_size":6,"bound_k":6,"subsets":[[0,4],[2,4],[2,5],[1,5],[1,3]]},"samples":[{"config":[0,4,2,5,1,3],"metric":true}],"optimal":[{"config":[0,4,2,5,1,3],"metric":true},{"config":[3,1,5,2,4,0],"metric":true}]},
1112
{"problem":"DirectedTwoCommodityIntegralFlow","variant":{},"instance":{"capacities":[1,1,1,1,1,1,1,1],"graph":{"inner":{"edge_property":"directed","edges":[[0,2,null],[0,3,null],[1,2,null],[1,3,null],[2,4,null],[2,5,null],[3,4,null],[3,5,null]],"node_holes":[],"nodes":[null,null,null,null,null,null]}},"requirement_1":1,"requirement_2":1,"sink_1":4,"sink_2":5,"source_1":0,"source_2":1},"samples":[{"config":[1,0,0,0,1,0,0,0,0,0,0,1,0,0,0,1],"metric":true}],"optimal":[{"config":[0,0,0,1,0,0,1,0,0,0,1,0,0,1,0,0],"metric":true},{"config":[0,0,0,1,0,0,1,0,0,1,0,0,0,0,0,1],"metric":true},{"config":[0,0,0,1,0,0,1,0,0,1,1,0,0,1,0,1],"metric":true},{"config":[0,0,0,1,0,0,1,0,0,1,1,0,1,0,0,1],"metric":true},{"config":[0,0,0,1,0,0,1,0,1,0,0,0,0,1,0,0],"metric":true},{"config":[0,0,0,1,0,0,1,0,1,0,1,0,1,1,0,0],"metric":true},{"config":[0,0,0,1,0,0,1,0,1,1,0,0,0,1,0,1],"metric":true},{"config":[0,0,0,1,0,0,1,0,1,1,0,0,1,0,0,1],"metric":true},{"config":[0,0,0,1,0,0,1,0,1,1,1,0,1,1,0,1],"metric":true},{"config":[0,0,1,0,1,0,0,0,0,0,0,1,0,0,0,1],"metric":true},{"config":[0,0,1,0,1,0,0,0,0,1,0,0,0,0,0,1],"metric":true},{"config":[0,0,1,0,1,0,0,0,0,1,0,1,0,0,1,1],"metric":true},{"config":[0,0,1,0,1,0,0,0,1,0,0,0,0,1,0,0],"metric":true},{"config":[0,0,1,0,1,0,0,0,1,0,0,1,0,1,0,1],"metric":true},{"config":[0,0,1,0,1,0,0,0,1,0,0,1,0,1,1,0],"metric":true},{"config":[0,0,1,0,1,0,0,0,1,1,0,0,0,1,0,1],"metric":true},{"config":[0,0,1,0,1,0,0,0,1,1,0,0,0,1,1,0],"metric":true},{"config":[0,0,1,0,1,0,0,0,1,1,0,1,0,1,1,1],"metric":true},{"config":[0,0,1,1,0,1,1,0,0,1,0,0,0,0,0,1],"metric":true},{"config":[0,0,1,1,0,1,1,0,1,1,0,0,1,0,0,1],"metric":true},{"config":[0,0,1,1,1,0,0,1,1,0,0,0,0,1,0,0],"metric":true},{"config":[0,0,1,1,1,0,0,1,1,1,0,0,0,1,1,0],"metric":true},{"config":[0,0,1,1,1,0,1,0,0,1,0,0,0,0,0,1],"metric":true},{"config":[0,0,1,1,1,0,1,0,1,0,0,0,0,1,0,0],"metric":true},{"config":[0,0,1,1,1,0,1,0,1,1,0,0,0,1,0,1],"metric":true},{"config":[0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,1],"metric":true},{"config":[0,1,0,0,0,0,1,0,0,0,1,0,0,1,0,0],"metric":true},{"config":[0,1,0,0,0,0,1,0,0,0,1,1,0,1,0,1],"metric":true},{"config":[0,1,0,0,0,0,1,0,0,0,1,1,1,0,0,1],"metric":true},{"config":[0,1,0,0,0,0,1,0,1,0,0,0,0,1,0,0],"metric":true},{"config":[0,1,0,0,0,0,1,0,1,0,0,1,0,1,0,1],"metric":true},{"config":[0,1,0,0,0,0,1,0,1,0,0,1,1,0,0,1],"metric":true},{"config":[0,1,0,0,0,0,1,0,1,0,1,0,1,1,0,0],"metric":true},{"config":[0,1,0,0,0,0,1,0,1,0,1,1,1,1,0,1],"metric":true},{"config":[0,1,0,1,0,0,1,1,0,0,1,0,0,1,0,0],"metric":true},{"config":[0,1,0,1,0,0,1,1,1,0,0,0,0,1,0,0],"metric":true},{"config":[0,1,0,1,0,0,1,1,1,0,1,0,1,1,0,0],"metric":true},{"config":[0,1,1,0,0,1,1,0,0,0,0,1,0,0,0,1],"metric":true},{"config":[0,1,1,0,0,1,1,0,1,0,0,1,1,0,0,1],"metric":true},{"config":[0,1,1,0,1,0,0,1,1,0,0,0,0,1,0,0],"metric":true},{"config":[0,1,1,0,1,0,0,1,1,0,0,1,0,1,1,0],"metric":true},{"config":[0,1,1,0,1,0,1,0,0,0,0,1,0,0,0,1],"metric":true},{"config":[0,1,1,0,1,0,1,0,1,0,0,0,0,1,0,0],"metric":true},{"config":[0,1,1,0,1,0,1,0,1,0,0,1,0,1,0,1],"metric":true},{"config":[0,1,1,1,1,0,1,1,1,0,0,0,0,1,0,0],"metric":true},{"config":[1,0,0,0,1,0,0,0,0,0,0,1,0,0,0,1],"metric":true},{"config":[1,0,0,0,1,0,0,0,0,0,1,0,0,1,0,0],"metric":true},{"config":[1,0,0,0,1,0,0,0,0,0,1,1,0,1,0,1],"metric":true},{"config":[1,0,0,0,1,0,0,0,0,0,1,1,0,1,1,0],"metric":true},{"config":[1,0,0,0,1,0,0,0,0,1,0,0,0,0,0,1],"metric":true},{"config":[1,0,0,0,1,0,0,0,0,1,0,1,0,0,1,1],"metric":true},{"config":[1,0,0,0,1,0,0,0,0,1,1,0,0,1,0,1],"metric":true},{"config":[1,0,0,0,1,0,0,0,0,1,1,0,0,1,1,0],"metric":true},{"config":[1,0,0,0,1,0,0,0,0,1,1,1,0,1,1,1],"metric":true},{"config":[1,0,0,1,0,1,1,0,0,1,0,0,0,0,0,1],"metric":true},{"config":[1,0,0,1,0,1,1,0,0,1,1,0,1,0,0,1],"metric":true},{"config":[1,0,0,1,1,0,0,1,0,0,1,0,0,1,0,0],"metric":true},{"config":[1,0,0,1,1,0,0,1,0,1,1,0,0,1,1,0],"metric":true},{"config":[1,0,0,1,1,0,1,0,0,0,1,0,0,1,0,0],"metric":true},{"config":[1,0,0,1,1,0,1,0,0,1,0,0,0,0,0,1],"metric":true},{"config":[1,0,0,1,1,0,1,0,0,1,1,0,0,1,0,1],"metric":true},{"config":[1,0,1,0,1,1,0,0,0,0,0,1,0,0,0,1],"metric":true},{"config":[1,0,1,0,1,1,0,0,0,1,0,0,0,0,0,1],"metric":true},{"config":[1,0,1,0,1,1,0,0,0,1,0,1,0,0,1,1],"metric":true},{"config":[1,0,1,1,1,1,1,0,0,1,0,0,0,0,0,1],"metric":true},{"config":[1,1,0,0,0,1,1,0,0,0,0,1,0,0,0,1],"metric":true},{"config":[1,1,0,0,0,1,1,0,0,0,1,1,1,0,0,1],"metric":true},{"config":[1,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0],"metric":true},{"config":[1,1,0,0,1,0,0,1,0,0,1,1,0,1,1,0],"metric":true},{"config":[1,1,0,0,1,0,1,0,0,0,0,1,0,0,0,1],"metric":true},{"config":[1,1,0,0,1,0,1,0,0,0,1,0,0,1,0,0],"metric":true},{"config":[1,1,0,0,1,0,1,0,0,0,1,1,0,1,0,1],"metric":true},{"config":[1,1,0,1,1,0,1,1,0,0,1,0,0,1,0,0],"metric":true},{"config":[1,1,1,0,1,1,1,0,0,0,0,1,0,0,0,1],"metric":true}]},
1213
{"problem":"ExactCoverBy3Sets","variant":{},"instance":{"subsets":[[0,1,2],[0,2,4],[3,4,5],[3,5,7],[6,7,8],[1,4,6],[2,5,8]],"universe_size":9},"samples":[{"config":[1,0,1,0,1,0,0],"metric":true}],"optimal":[{"config":[1,0,1,0,1,0,0],"metric":true}]},
1314
{"problem":"Factoring","variant":{},"instance":{"m":2,"n":3,"target":15},"samples":[{"config":[1,1,1,0,1],"metric":{"Valid":0}}],"optimal":[{"config":[1,1,1,0,1],"metric":{"Valid":0}}]},

src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,8 @@ pub mod prelude {
6363
ShortestCommonSupersequence, StaffScheduling, StringToStringCorrection, SubsetSum,
6464
};
6565
pub use crate::models::set::{
66-
ComparativeContainment, ExactCoverBy3Sets, MaximumSetPacking, MinimumSetCovering, SetBasis,
66+
ComparativeContainment, ConsecutiveSets, ExactCoverBy3Sets, MaximumSetPacking,
67+
MinimumCardinalityKey, MinimumSetCovering, SetBasis,
6768
};
6869

6970
// Core traits

src/models/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,6 @@ pub use misc::{
2727
ShortestCommonSupersequence, StaffScheduling, StringToStringCorrection, SubsetSum,
2828
};
2929
pub use set::{
30-
ComparativeContainment, ExactCoverBy3Sets, MaximumSetPacking, MinimumCardinalityKey,
31-
MinimumSetCovering, SetBasis,
30+
ComparativeContainment, ConsecutiveSets, ExactCoverBy3Sets, MaximumSetPacking,
31+
MinimumCardinalityKey, MinimumSetCovering, SetBasis,
3232
};

src/models/set/consecutive_sets.rs

Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
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+
8+
use crate::registry::{FieldInfo, ProblemSchemaEntry};
9+
use crate::traits::{Problem, SatisfactionProblem};
10+
use serde::{Deserialize, Serialize};
11+
use std::collections::HashSet;
12+
13+
inventory::submit! {
14+
ProblemSchemaEntry {
15+
name: "ConsecutiveSets",
16+
display_name: "Consecutive Sets",
17+
aliases: &[],
18+
dimensions: &[],
19+
module_path: module_path!(),
20+
description: "Determine if a string exists where each subset's elements appear consecutively",
21+
fields: &[
22+
FieldInfo { name: "alphabet_size", type_name: "usize", description: "Size of the alphabet (elements are 0..alphabet_size-1)" },
23+
FieldInfo { name: "subsets", type_name: "Vec<Vec<usize>>", description: "Collection of subsets of the alphabet" },
24+
FieldInfo { name: "bound_k", type_name: "usize", description: "Maximum string length K" },
25+
],
26+
}
27+
}
28+
29+
/// Consecutive Sets problem.
30+
///
31+
/// Given an alphabet {0, 1, ..., n-1}, a collection of subsets, and a bound K,
32+
/// determine if there exists a string w of length at most K over the alphabet
33+
/// such that the elements of each subset appear as a contiguous block (in any
34+
/// order) within w.
35+
///
36+
/// Configurations use `bound_k` positions. Values `0..alphabet_size-1`
37+
/// represent alphabet symbols, and the extra value `alphabet_size` marks
38+
/// unused positions beyond the end of a shorter string. Only trailing unused
39+
/// positions are valid.
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, Solver, 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.find_satisfying(&problem);
59+
///
60+
/// // w = [0, 4, 2, 5, 1, 3] is a valid solution
61+
/// assert!(solution.is_some());
62+
/// assert!(problem.evaluate(&solution.unwrap()));
63+
///
64+
/// // Shorter strings are encoded with trailing `unused = alphabet_size`.
65+
/// let shorter = ConsecutiveSets::new(3, vec![vec![0, 1]], 4);
66+
/// let unused = shorter.alphabet_size();
67+
/// assert!(shorter.evaluate(&[0, 1, unused, unused]));
68+
/// assert!(!shorter.evaluate(&[0, unused, 1, unused]));
69+
/// ```
70+
#[derive(Debug, Clone, Serialize, Deserialize)]
71+
pub struct ConsecutiveSets {
72+
/// Size of the alphabet (elements are 0..alphabet_size-1).
73+
alphabet_size: usize,
74+
/// Collection of subsets of the alphabet, each sorted in canonical form.
75+
subsets: Vec<Vec<usize>>,
76+
/// Maximum string length K.
77+
bound_k: usize,
78+
}
79+
80+
impl ConsecutiveSets {
81+
/// Create a new Consecutive Sets problem.
82+
///
83+
/// # Panics
84+
///
85+
/// Panics if `bound_k` is zero, if any subset contains duplicate elements,
86+
/// or if any element is outside the alphabet.
87+
pub fn new(alphabet_size: usize, subsets: Vec<Vec<usize>>, bound_k: usize) -> Self {
88+
assert!(bound_k > 0, "bound_k must be positive, got 0");
89+
let mut subsets = subsets;
90+
for (i, subset) in subsets.iter_mut().enumerate() {
91+
let mut seen = HashSet::with_capacity(subset.len());
92+
for &elem in subset.iter() {
93+
assert!(
94+
elem < alphabet_size,
95+
"Subset {} contains element {} which is outside alphabet of size {}",
96+
i,
97+
elem,
98+
alphabet_size
99+
);
100+
assert!(
101+
seen.insert(elem),
102+
"Subset {} contains duplicate elements",
103+
i
104+
);
105+
}
106+
subset.sort();
107+
}
108+
Self {
109+
alphabet_size,
110+
subsets,
111+
bound_k,
112+
}
113+
}
114+
115+
/// Get the alphabet size.
116+
pub fn alphabet_size(&self) -> usize {
117+
self.alphabet_size
118+
}
119+
120+
/// Get the number of subsets in the collection.
121+
pub fn num_subsets(&self) -> usize {
122+
self.subsets.len()
123+
}
124+
125+
/// Get the bound K.
126+
pub fn bound_k(&self) -> usize {
127+
self.bound_k
128+
}
129+
130+
/// Get the subsets.
131+
pub fn subsets(&self) -> &[Vec<usize>] {
132+
&self.subsets
133+
}
134+
}
135+
136+
impl Problem for ConsecutiveSets {
137+
const NAME: &'static str = "ConsecutiveSets";
138+
type Metric = bool;
139+
140+
fn dims(&self) -> Vec<usize> {
141+
// Each position can be any symbol (0..alphabet_size-1) or "unused" (alphabet_size)
142+
vec![self.alphabet_size + 1; self.bound_k]
143+
}
144+
145+
fn evaluate(&self, config: &[usize]) -> bool {
146+
// 1. Validate config
147+
if config.len() != self.bound_k || config.iter().any(|&v| v > self.alphabet_size) {
148+
return false;
149+
}
150+
151+
// 2. Build string: find the actual string length (strip trailing "unused")
152+
let unused = self.alphabet_size;
153+
let str_len = config
154+
.iter()
155+
.rposition(|&v| v != unused)
156+
.map_or(0, |p| p + 1);
157+
158+
// 3. Check no internal "unused" symbols
159+
let w = &config[..str_len];
160+
if w.contains(&unused) {
161+
return false;
162+
}
163+
164+
let mut subset_membership = vec![0usize; self.alphabet_size];
165+
let mut seen_in_window = vec![0usize; self.alphabet_size];
166+
let mut subset_stamp = 1usize;
167+
let mut window_stamp = 1usize;
168+
169+
// 4. Check each subset has a consecutive block
170+
for subset in &self.subsets {
171+
let subset_len = subset.len();
172+
if subset_len == 0 {
173+
continue; // empty subset trivially satisfied
174+
}
175+
if subset_len > str_len {
176+
return false; // can't fit
177+
}
178+
179+
for &elem in subset {
180+
subset_membership[elem] = subset_stamp;
181+
}
182+
183+
let mut found = false;
184+
for start in 0..=(str_len - subset_len) {
185+
let window = &w[start..start + subset_len];
186+
let current_window_stamp = window_stamp;
187+
window_stamp += 1;
188+
189+
// Because subsets are validated to contain unique elements,
190+
// a window matches iff every symbol belongs to the subset and
191+
// appears at most once.
192+
if window.iter().all(|&elem| {
193+
let is_member = subset_membership[elem] == subset_stamp;
194+
let is_new = seen_in_window[elem] != current_window_stamp;
195+
if is_member && is_new {
196+
seen_in_window[elem] = current_window_stamp;
197+
true
198+
} else {
199+
false
200+
}
201+
}) {
202+
// subset is already sorted
203+
found = true;
204+
break;
205+
}
206+
}
207+
if !found {
208+
return false;
209+
}
210+
211+
subset_stamp += 1;
212+
}
213+
214+
true
215+
}
216+
217+
fn variant() -> Vec<(&'static str, &'static str)> {
218+
crate::variant_params![]
219+
}
220+
}
221+
222+
impl SatisfactionProblem for ConsecutiveSets {}
223+
224+
crate::declare_variants! {
225+
default sat ConsecutiveSets => "alphabet_size^bound_k * num_subsets",
226+
}
227+
228+
#[cfg(feature = "example-db")]
229+
pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
230+
vec![crate::example_db::specs::ModelExampleSpec {
231+
id: "consecutive_sets",
232+
build: || {
233+
// YES instance from issue: w = [0, 4, 2, 5, 1, 3]
234+
let problem = ConsecutiveSets::new(
235+
6,
236+
vec![vec![0, 4], vec![2, 4], vec![2, 5], vec![1, 5], vec![1, 3]],
237+
6,
238+
);
239+
crate::example_db::specs::satisfaction_example(problem, vec![vec![0, 4, 2, 5, 1, 3]])
240+
},
241+
}]
242+
}
243+
244+
#[cfg(test)]
245+
#[path = "../../unit_tests/models/set/consecutive_sets.rs"]
246+
mod tests;

0 commit comments

Comments
 (0)