Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 40 additions & 21 deletions src/models/misc/closest_string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,26 @@ inventory::submit! {
/// syntactically feasible; the objective is its worst-case Hamming distance
/// to the input strings.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(try_from = "ClosestStringData")]
pub struct ClosestString {
alphabet_size: usize,
strings: Vec<Vec<usize>>,
}

#[derive(Deserialize)]
struct ClosestStringData {
alphabet_size: usize,
strings: Vec<Vec<usize>>,
}

impl TryFrom<ClosestStringData> for ClosestString {
type Error = crate::registry::ConstructionError;

fn try_from(data: ClosestStringData) -> Result<Self, Self::Error> {
Self::try_new(data.alphabet_size, data.strings)
}
}

impl ClosestString {
/// Create a new `ClosestString` instance.
///
Expand All @@ -62,30 +77,34 @@ impl ClosestString {
/// - `alphabet_size == 0` while any input string is non-empty,
/// - any symbol in any input string is `>= alphabet_size`.
pub fn new(alphabet_size: usize, strings: Vec<Vec<usize>>) -> Self {
assert!(
!strings.is_empty(),
"ClosestString requires at least one input string"
);
Self::try_new(alphabet_size, strings).unwrap_or_else(|error| panic!("{error}"))
}

fn try_new(
alphabet_size: usize,
strings: Vec<Vec<usize>>,
) -> Result<Self, crate::registry::ConstructionError> {
if strings.is_empty() {
return Err("ClosestString requires at least one input string".into());
}
let string_length = strings[0].len();
assert!(
strings.iter().all(|s| s.len() == string_length),
"all input strings must have the same length"
);
assert!(
alphabet_size > 0 || string_length == 0,
"alphabet_size must be > 0 when input strings are non-empty"
);
assert!(
strings
.iter()
.flat_map(|s| s.iter())
.all(|&symbol| symbol < alphabet_size),
"input symbols must be less than alphabet_size"
);
Self {
if !(strings.iter().all(|s| s.len() == string_length)) {
return Err("all input strings must have the same length".into());
}
if !(alphabet_size > 0 || string_length == 0) {
return Err("alphabet_size must be > 0 when input strings are non-empty".into());
}
if !(strings
.iter()
.flat_map(|s| s.iter())
.all(|&symbol| symbol < alphabet_size))
{
return Err("input symbols must be less than alphabet_size".into());
}
Ok(Self {
alphabet_size,
strings,
}
})
}

/// Returns the alphabet size `q`.
Expand Down
71 changes: 36 additions & 35 deletions src/models/misc/grouping_by_swapping.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,28 @@ inventory::submit! {
/// adjacent swap position `i` (swap positions `i` and `i + 1`) or the special
/// no-op value `string_len - 1`.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(try_from = "GroupingBySwappingData")]
pub struct GroupingBySwapping {
alphabet_size: usize,
string: Vec<usize>,
budget: usize,
}

#[derive(Deserialize)]
struct GroupingBySwappingData {
alphabet_size: usize,
string: Vec<usize>,
budget: usize,
}

impl TryFrom<GroupingBySwappingData> for GroupingBySwapping {
type Error = crate::registry::ConstructionError;

fn try_from(data: GroupingBySwappingData) -> Result<Self, Self::Error> {
Self::try_new(data.alphabet_size, data.string, data.budget)
}
}

#[derive(Debug, Deserialize, crate::CreateSpec)]
struct GroupingBySwappingCreateSpec {
/// Optional alphabet size; omitted values are inferred from the string.
Expand Down Expand Up @@ -61,27 +77,7 @@ impl TryFrom<GroupingBySwappingCreateSpec> for GroupingBySwapping {
.transpose()?
.unwrap_or(0);
let alphabet_size = spec.alphabet_size.unwrap_or(inferred_alphabet_size);
if alphabet_size < inferred_alphabet_size {
return Err(format!(
"alphabet size {alphabet_size} is smaller than inferred alphabet size {inferred_alphabet_size}"
).into());
}
if alphabet_size == 0 && !spec.string.is_empty() {
return Err("alphabet size must be positive for a non-empty string"
.to_string()
.into());
}
if spec.string.is_empty() && spec.bound != 0 {
return Err("bound must be zero when the string is empty"
.to_string()
.into());
}

Ok(Self {
alphabet_size,
string: spec.string,
budget: spec.bound,
})
Self::try_new(alphabet_size, spec.string, spec.bound)
}
}

Expand All @@ -93,23 +89,28 @@ impl GroupingBySwapping {
/// Panics if the string contains a symbol outside the declared alphabet,
/// or if the string is empty while the budget is positive.
pub fn new(alphabet_size: usize, string: Vec<usize>, budget: usize) -> Self {
assert!(
alphabet_size > 0 || string.is_empty(),
"alphabet_size must be > 0 when string is non-empty"
);
assert!(
string.iter().all(|&symbol| symbol < alphabet_size),
"input symbols must be less than alphabet_size"
);
assert!(
!string.is_empty() || budget == 0,
"budget must be 0 when string is empty"
);
Self {
Self::try_new(alphabet_size, string, budget).unwrap_or_else(|error| panic!("{error}"))
}

fn try_new(
alphabet_size: usize,
string: Vec<usize>,
budget: usize,
) -> Result<Self, crate::registry::ConstructionError> {
if !(alphabet_size > 0 || string.is_empty()) {
return Err("alphabet_size must be > 0 when string is non-empty".into());
}
if !(string.iter().all(|&symbol| symbol < alphabet_size)) {
return Err("input symbols must be less than alphabet_size".into());
}
if !(!string.is_empty() || budget == 0) {
return Err("budget must be 0 when string is empty".into());
}
Ok(Self {
alphabet_size,
string,
budget,
}
})
}

/// Returns the alphabet size.
Expand Down
66 changes: 36 additions & 30 deletions src/models/misc/longest_common_subsequence.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,27 @@ inventory::submit! {
/// subsequence consists of the symbols before padding starts. The objective is
/// to maximize the effective length.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(try_from = "LongestCommonSubsequenceData")]
pub struct LongestCommonSubsequence {
alphabet_size: usize,
strings: Vec<Vec<usize>>,
max_length: usize,
}

#[derive(Deserialize)]
struct LongestCommonSubsequenceData {
alphabet_size: usize,
strings: Vec<Vec<usize>>,
}

impl TryFrom<LongestCommonSubsequenceData> for LongestCommonSubsequence {
type Error = crate::registry::ConstructionError;

fn try_from(data: LongestCommonSubsequenceData) -> Result<Self, Self::Error> {
Self::try_new(data.alphabet_size, data.strings)
}
}

#[derive(Debug, Deserialize, crate::CreateSpec)]
struct LongestCommonSubsequenceCreateSpec {
/// Optional alphabet size; omitted values are inferred from the strings.
Expand Down Expand Up @@ -74,21 +89,7 @@ impl TryFrom<LongestCommonSubsequenceCreateSpec> for LongestCommonSubsequence {
.transpose()?
.unwrap_or(0);
let alphabet_size = spec.alphabet_size.unwrap_or(inferred_alphabet_size);
if alphabet_size < inferred_alphabet_size {
return Err(format!(
"alphabet size {alphabet_size} is smaller than inferred alphabet size {inferred_alphabet_size}"
).into());
}
if alphabet_size == 0 {
return Err("alphabet size must be positive".to_string().into());
}
let max_length = spec.strings.iter().map(Vec::len).min().unwrap_or(0);

Ok(Self {
alphabet_size,
strings: spec.strings,
max_length,
})
Self::try_new(alphabet_size, spec.strings)
}
}

Expand All @@ -101,26 +102,31 @@ impl LongestCommonSubsequence {
/// # Panics
///
/// Panics if `alphabet_size == 0` and any input string is non-empty, or if
/// an input symbol is outside the declared alphabet, or if all strings are
/// empty (max_length would be 0, requiring at least one non-empty string).
/// an input symbol is outside the declared alphabet.
pub fn new(alphabet_size: usize, strings: Vec<Vec<usize>>) -> Self {
Self::try_new(alphabet_size, strings).unwrap_or_else(|error| panic!("{error}"))
}

fn try_new(
alphabet_size: usize,
strings: Vec<Vec<usize>>,
) -> Result<Self, crate::registry::ConstructionError> {
let max_length = strings.iter().map(|s| s.len()).min().unwrap_or(0);
assert!(
alphabet_size > 0 || strings.iter().all(|s| s.is_empty()),
"alphabet_size must be > 0 when any input string is non-empty"
);
assert!(
strings
.iter()
.flat_map(|s| s.iter())
.all(|&symbol| symbol < alphabet_size),
"input symbols must be less than alphabet_size"
);
Self {
if !(alphabet_size > 0 || strings.iter().all(|s| s.is_empty())) {
return Err("alphabet_size must be > 0 when any input string is non-empty".into());
}
if !(strings
.iter()
.flat_map(|s| s.iter())
.all(|&symbol| symbol < alphabet_size))
{
return Err("input symbols must be less than alphabet_size".into());
}
Ok(Self {
alphabet_size,
strings,
max_length,
}
})
}

/// Returns the alphabet size.
Expand Down
59 changes: 36 additions & 23 deletions src/models/misc/shortest_common_supersequence.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,27 @@ inventory::submit! {
/// assert!(solution.is_some());
/// ```
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(try_from = "ShortestCommonSupersequenceData")]
pub struct ShortestCommonSupersequence {
alphabet_size: usize,
strings: Vec<Vec<usize>>,
max_length: usize,
}

#[derive(Deserialize)]
struct ShortestCommonSupersequenceData {
alphabet_size: usize,
strings: Vec<Vec<usize>>,
}

impl TryFrom<ShortestCommonSupersequenceData> for ShortestCommonSupersequence {
type Error = crate::registry::ConstructionError;

fn try_from(data: ShortestCommonSupersequenceData) -> Result<Self, Self::Error> {
Self::try_new(data.alphabet_size, data.strings)
}
}

#[derive(Debug, Deserialize, crate::CreateSpec)]
struct ShortestCommonSupersequenceCreateSpec {
/// Input strings; the alphabet and maximum length are inferred from them.
Expand All @@ -72,10 +87,6 @@ impl TryFrom<ShortestCommonSupersequenceCreateSpec> for ShortestCommonSuperseque
type Error = crate::registry::ConstructionError;

fn try_from(spec: ShortestCommonSupersequenceCreateSpec) -> Result<Self, Self::Error> {
if spec.strings.is_empty() {
return Err("must have at least one string".to_string().into());
}

let alphabet_size = spec
.strings
.iter()
Expand All @@ -89,17 +100,7 @@ impl TryFrom<ShortestCommonSupersequenceCreateSpec> for ShortestCommonSuperseque
})
.transpose()?
.unwrap_or(0);
let max_length = spec.strings.iter().try_fold(0_usize, |total, string| {
total
.checked_add(string.len())
.ok_or_else(|| "maximum supersequence length overflows usize".to_string())
})?;

Ok(Self {
alphabet_size,
strings: spec.strings,
max_length,
})
Self::try_new(alphabet_size, spec.strings)
}
}

Expand All @@ -114,17 +115,29 @@ impl ShortestCommonSupersequence {
/// Panics if `strings` is empty, or if `alphabet_size` is 0 and any input
/// string is non-empty.
pub fn new(alphabet_size: usize, strings: Vec<Vec<usize>>) -> Self {
assert!(!strings.is_empty(), "must have at least one string");
let max_length: usize = strings.iter().map(|s| s.len()).sum();
assert!(
alphabet_size > 0 || strings.iter().all(|s| s.is_empty()),
"alphabet_size must be > 0 when any input string is non-empty"
);
Self {
Self::try_new(alphabet_size, strings).unwrap_or_else(|error| panic!("{error}"))
}

fn try_new(
alphabet_size: usize,
strings: Vec<Vec<usize>>,
) -> Result<Self, crate::registry::ConstructionError> {
if strings.is_empty() {
return Err("must have at least one string".into());
}
let max_length = strings.iter().try_fold(0usize, |total, string| {
total
.checked_add(string.len())
.ok_or("maximum string length overflows usize")
})?;
if !(alphabet_size > 0 || strings.iter().all(|s| s.is_empty())) {
return Err("alphabet_size must be > 0 when any input string is non-empty".into());
}
Ok(Self {
alphabet_size,
strings,
max_length,
}
})
}

/// Returns the alphabet size.
Expand Down
Loading
Loading