#![expect(
clippy::cast_sign_loss,
reason = "i32 sequence IDs used as Vec indices"
)]
#![expect(
clippy::cast_possible_truncation,
reason = "sequence ID (i32) <-> usize for Vec indexing"
)]
#![expect(
clippy::cast_possible_wrap,
reason = "usize -> i32 for sequence IDs — always in range"
)]
use std::collections::HashMap;
#[derive(Clone, Debug, PartialEq)]
pub struct IdSetLexicon {
index: HashMap<Vec<i32>, i32>,
sets: Vec<Vec<i32>>,
}
pub const EMPTY_SET_ID: i32 = i32::MIN;
impl IdSetLexicon {
pub fn new() -> Self {
IdSetLexicon {
index: HashMap::new(),
sets: Vec::new(),
}
}
pub const fn add_singleton(value: i32) -> i32 {
-(value + 1)
}
pub const fn singleton_value(id: i32) -> i32 {
debug_assert!(id < 0 && id != EMPTY_SET_ID);
-id - 1
}
pub fn add_set(&mut self, values: &[i32]) -> i32 {
if values.is_empty() {
return EMPTY_SET_ID;
}
if values.len() == 1 {
return -(values[0] + 1);
}
let mut sorted = values.to_vec();
sorted.sort_unstable();
sorted.dedup();
if sorted.len() == 1 {
return -(sorted[0] + 1);
}
if let Some(&id) = self.index.get(&sorted) {
return id;
}
let id = self.sets.len() as i32;
self.index.insert(sorted.clone(), id);
self.sets.push(sorted);
id
}
pub fn id_set(&self, id: i32) -> Vec<i32> {
if id == EMPTY_SET_ID {
return Vec::new();
}
if id < 0 {
return vec![-(id + 1)];
}
self.sets.get(id as usize).cloned().unwrap_or_default()
}
}
impl Default for IdSetLexicon {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use quickcheck_macros::quickcheck;
#[test]
fn test_empty_set() {
let mut lex = IdSetLexicon::new();
let id = lex.add_set(&[]);
assert_eq!(id, EMPTY_SET_ID);
assert_eq!(lex.id_set(id), Vec::<i32>::new());
}
#[test]
fn test_single_element() {
let mut lex = IdSetLexicon::new();
let id = lex.add_set(&[42]);
assert!(id < 0);
assert_eq!(lex.id_set(id), vec![42]);
}
#[test]
fn test_single_element_zero() {
let mut lex = IdSetLexicon::new();
let id = lex.add_set(&[0]);
assert_eq!(id, -1); assert_eq!(lex.id_set(id), vec![0]);
}
#[test]
fn test_multi_element() {
let mut lex = IdSetLexicon::new();
let id = lex.add_set(&[3, 1, 2]);
assert!(id >= 0);
assert_eq!(lex.id_set(id), vec![1, 2, 3]); }
#[test]
fn test_dedup() {
let mut lex = IdSetLexicon::new();
let id1 = lex.add_set(&[1, 2, 3]);
let id2 = lex.add_set(&[3, 1, 2]); let id3 = lex.add_set(&[1, 2, 3, 2]); assert_eq!(id1, id2);
assert_eq!(id1, id3);
}
#[test]
fn test_different_sets_get_different_ids() {
let mut lex = IdSetLexicon::new();
let id1 = lex.add_set(&[1, 2]);
let id2 = lex.add_set(&[1, 3]);
assert_ne!(id1, id2);
}
#[test]
fn test_single_element_dedup_after_sort() {
let mut lex = IdSetLexicon::new();
let id = lex.add_set(&[5, 5, 5]);
assert!(id < 0);
assert_eq!(lex.id_set(id), vec![5]);
}
#[quickcheck]
fn prop_roundtrip(values: Vec<u16>) -> bool {
let mut lex = IdSetLexicon::new();
let vals: Vec<i32> = values.iter().map(|&v| i32::from(v)).collect();
let id = lex.add_set(&vals);
let result = lex.id_set(id);
let mut expected = vals;
expected.sort_unstable();
expected.dedup();
result == expected
}
#[quickcheck]
fn prop_single_element_negative_id(v: u16) -> bool {
let mut lex = IdSetLexicon::new();
let id = lex.add_set(&[i32::from(v)]);
id < 0 && lex.id_set(id) == vec![i32::from(v)]
}
#[quickcheck]
fn prop_single_element_encoding(v: u16) -> bool {
let mut lex = IdSetLexicon::new();
let val = i32::from(v);
let id = lex.add_set(&[val]);
id == -(val + 1)
}
#[quickcheck]
fn prop_dedup_same_id(values: Vec<u16>) -> bool {
if values.is_empty() {
return true; }
let mut lex = IdSetLexicon::new();
let vals: Vec<i32> = values.iter().map(|&v| i32::from(v)).collect();
let id1 = lex.add_set(&vals);
let id2 = lex.add_set(&vals);
id1 == id2
}
#[quickcheck]
fn prop_dedup_permutation(mut values: Vec<u16>) -> bool {
if values.len() < 2 {
return true;
}
let mut lex = IdSetLexicon::new();
let vals1: Vec<i32> = values.iter().map(|&v| i32::from(v)).collect();
let id1 = lex.add_set(&vals1);
values.reverse();
let vals2: Vec<i32> = values.iter().map(|&v| i32::from(v)).collect();
let id2 = lex.add_set(&vals2);
id1 == id2
}
#[quickcheck]
fn prop_different_sets_different_ids(a: u16, b: u16) -> bool {
if a == b {
return true;
}
let mut lex = IdSetLexicon::new();
let id1 = lex.add_set(&[i32::from(a)]);
let id2 = lex.add_set(&[i32::from(b)]);
id1 != id2
}
#[quickcheck]
fn prop_empty_set_stable(values: Vec<u16>) -> bool {
let mut lex = IdSetLexicon::new();
for &v in &values {
lex.add_set(&[i32::from(v)]);
}
let id = lex.add_set(&[]);
id == EMPTY_SET_ID && lex.id_set(id).is_empty()
}
}
#[cfg(test)]
#[path = "id_set_lexicon_tests.rs"]
mod id_set_lexicon_tests;