use succinctly::bp::{
enclose, find_close, find_close_in_word, find_open, find_unmatched_close_in_word,
BalancedParens,
};
fn naive_find_close_in_word(word: u64, p: u32) -> Option<u32> {
if p >= 64 {
return None;
}
if (word >> p) & 1 == 0 {
return Some(p); }
let mut excess: i32 = 1; for bit in (p + 1)..64 {
if (word >> bit) & 1 == 1 {
excess += 1;
} else {
excess -= 1;
if excess == 0 {
return Some(bit);
}
}
}
None
}
fn naive_find_unmatched_close_in_word(word: u64) -> u32 {
let mut excess: i32 = 0;
for bit in 0..64 {
if (word >> bit) & 1 == 1 {
excess += 1;
} else {
excess -= 1;
if excess < 0 {
return bit;
}
}
}
64
}
fn naive_word_min_excess(word: u64, valid_bits: usize) -> (i16, i16) {
let mut excess: i16 = 0;
let mut min_excess: i16 = 0;
for i in 0..valid_bits {
if (word >> i) & 1 == 1 {
excess += 1;
} else {
excess -= 1;
min_excess = min_excess.min(excess);
}
}
(min_excess, excess)
}
fn naive_excess(words: &[u64], len: usize, p: usize) -> i32 {
if p >= len {
return 0;
}
let mut total: i32 = 0;
for i in 0..=p {
let word_idx = i / 64;
let bit_idx = i % 64;
if (words[word_idx] >> bit_idx) & 1 == 1 {
total += 1;
} else {
total -= 1;
}
}
total
}
mod excess_tests {
use super::*;
#[test]
fn test_excess_empty_sequence() {
let bp = BalancedParens::new(vec![], 0);
assert_eq!(bp.excess(0), 0);
assert_eq!(bp.excess(100), 0);
}
#[test]
fn test_excess_single_pair() {
let bp = BalancedParens::new(vec![0b01], 2);
assert_eq!(bp.excess(0), 1); assert_eq!(bp.excess(1), 0); }
#[test]
fn test_excess_nested_pairs() {
let bp = BalancedParens::new(vec![0b0011], 4);
assert_eq!(bp.excess(0), 1); assert_eq!(bp.excess(1), 2); assert_eq!(bp.excess(2), 1); assert_eq!(bp.excess(3), 0); }
#[test]
fn test_excess_sequential_pairs() {
let bp = BalancedParens::new(vec![0b0101], 4);
assert_eq!(bp.excess(0), 1);
assert_eq!(bp.excess(1), 0);
assert_eq!(bp.excess(2), 1);
assert_eq!(bp.excess(3), 0);
}
#[test]
fn test_excess_all_opens() {
let bp = BalancedParens::new(vec![u64::MAX], 64);
for i in 0..64 {
assert_eq!(bp.excess(i), (i + 1) as i32);
}
}
#[test]
fn test_excess_all_closes() {
let bp = BalancedParens::new(vec![0], 64);
for i in 0..64 {
assert_eq!(bp.excess(i), -((i + 1) as i32));
}
}
#[test]
fn test_excess_multi_word() {
let bp = BalancedParens::new(vec![u64::MAX, 0], 128);
assert_eq!(bp.excess(0), 1);
assert_eq!(bp.excess(63), 64);
assert_eq!(bp.excess(64), 63);
assert_eq!(bp.excess(127), 0);
}
#[test]
fn test_excess_matches_naive() {
let test_cases: Vec<(Vec<u64>, usize)> = vec![
(vec![0b001011], 6), (vec![0b0011], 4), (vec![u64::MAX, 0], 128), (vec![0x5555555555555555], 64), (vec![0xAAAAAAAAAAAAAAAA], 64), ];
for (words, len) in test_cases {
let bp = BalancedParens::new(words.clone(), len);
for p in 0..len {
let expected = naive_excess(&words, len, p);
assert_eq!(
bp.excess(p),
expected,
"excess({}) mismatch for words={:?}",
p,
words
);
}
}
}
#[test]
fn test_excess_out_of_bounds() {
let bp = BalancedParens::new(vec![0b01], 2);
assert_eq!(bp.excess(2), 0);
assert_eq!(bp.excess(100), 0);
}
#[test]
fn test_excess_partial_word() {
let bp = BalancedParens::new(vec![0b0011001011], 10);
assert_eq!(bp.excess(0), 1);
assert_eq!(bp.excess(1), 2);
assert_eq!(bp.excess(2), 1);
assert_eq!(bp.excess(5), 0);
assert_eq!(bp.excess(9), 0);
}
#[test]
fn test_excess_depth_relationship() {
let bp = BalancedParens::new(vec![0b001011], 6);
for p in 0..6 {
if bp.is_open(p) {
assert_eq!(
bp.depth(p).unwrap() as i32,
bp.excess(p),
"depth != excess at open position {}",
p
);
}
}
}
}
mod min_excess_tests {
use super::*;
#[test]
fn test_min_excess_all_opens() {
let (min, total) = naive_word_min_excess(u64::MAX, 64);
assert_eq!(min, 0); assert_eq!(total, 64);
}
#[test]
fn test_min_excess_all_closes() {
let (min, total) = naive_word_min_excess(0, 64);
assert_eq!(min, -64);
assert_eq!(total, -64);
}
#[test]
fn test_min_excess_balanced() {
let (min, total) = naive_word_min_excess(0b01, 2);
assert_eq!(min, 0);
assert_eq!(total, 0);
let (min, total) = naive_word_min_excess(0b0101, 4);
assert_eq!(min, 0);
assert_eq!(total, 0);
}
#[test]
fn test_min_excess_starts_with_close() {
let (min, total) = naive_word_min_excess(0b10, 2);
assert_eq!(min, -1);
assert_eq!(total, 0);
let (min, total) = naive_word_min_excess(0b1100, 4);
assert_eq!(min, -2);
assert_eq!(total, 0);
}
#[test]
fn test_min_excess_alternating() {
let (min, total) = naive_word_min_excess(0x55, 8);
assert_eq!(min, 0);
assert_eq!(total, 0);
let (min, total) = naive_word_min_excess(0xAA, 8);
assert_eq!(min, -1);
assert_eq!(total, 0);
}
#[test]
fn test_min_excess_partial_bits() {
let (min, total) = naive_word_min_excess(0b011, 3);
assert_eq!(min, 0);
assert_eq!(total, 1);
let (min, total) = naive_word_min_excess(0b001, 3);
assert_eq!(min, -1);
assert_eq!(total, -1);
}
#[test]
fn test_min_excess_invariants() {
let test_words = [
0u64,
u64::MAX,
0x5555555555555555,
0xAAAAAAAAAAAAAAAA,
0x123456789ABCDEF0,
0xFEDCBA9876543210,
];
for word in test_words {
let (min, total) = naive_word_min_excess(word, 64);
assert!(
min <= total,
"min_excess > total_excess for word={:#x}: min={}, total={}",
word,
min,
total
);
}
}
}
mod find_close_in_word_tests {
use super::*;
#[test]
fn test_find_close_in_word_matches_naive_simple() {
let test_cases = [
(0b01u64, 0u32), (0b0011u64, 0), (0b0011u64, 1), (0b0101u64, 0), (0b0101u64, 2), (0b001011u64, 0), (0b001011u64, 1), (0b001011u64, 3), ];
for (word, p) in test_cases {
let result = find_close_in_word(word, p);
let naive = naive_find_close_in_word(word, p);
assert_eq!(
result, naive,
"find_close_in_word({:#x}, {}) mismatch: got {:?}, expected {:?}",
word, p, result, naive
);
}
}
#[test]
fn test_find_close_in_word_matches_naive_all_patterns() {
for word in 0u64..=0xFFFF {
for p in 0..16u32 {
let result = find_close_in_word(word, p);
let naive = naive_find_close_in_word(word, p);
assert_eq!(
result, naive,
"find_close_in_word({:#x}, {}) mismatch",
word, p
);
}
}
}
#[test]
fn test_find_close_in_word_at_close_position() {
let word = 0b001011u64;
for p in [2, 4, 5] {
let result = find_close_in_word(word, p as u32);
let naive = naive_find_close_in_word(word, p as u32);
assert_eq!(result, Some(p as u32));
assert_eq!(naive, Some(p as u32));
}
}
#[test]
fn test_find_close_in_word_no_match() {
let word = u64::MAX;
for p in 0..64u32 {
let result = find_close_in_word(word, p);
let naive = naive_find_close_in_word(word, p);
assert_eq!(result, None);
assert_eq!(naive, None);
}
}
#[test]
fn test_find_close_in_word_boundary_positions() {
let word = 0b11u64 << 62;
assert_eq!(find_close_in_word(word, 62), None);
assert_eq!(naive_find_close_in_word(word, 62), None);
assert_eq!(find_close_in_word(word, 63), None);
assert_eq!(naive_find_close_in_word(word, 63), None);
}
#[test]
fn test_find_unmatched_close_matches_naive() {
let test_words = [
0u64,
u64::MAX,
0x5555555555555555,
0xAAAAAAAAAAAAAAAA,
0b001u64 | (u64::MAX << 3), 0b00011u64 | (u64::MAX << 5), ];
for word in test_words {
let result = find_unmatched_close_in_word(word);
let naive = naive_find_unmatched_close_in_word(word);
assert_eq!(
result, naive,
"find_unmatched_close_in_word({:#x}) mismatch: got {}, expected {}",
word, result, naive
);
}
}
}
mod cross_verification_tests {
use super::*;
use rand::prelude::*;
use rand_chacha::ChaCha8Rng;
fn verify_implementations(words: Vec<u64>, len: usize) {
let bp = BalancedParens::new(words.clone(), len);
for p in 0..len {
if bp.is_open(p) {
let bp_result = bp.find_close(p);
let linear_result = find_close(&words, len, p);
assert_eq!(
bp_result, linear_result,
"find_close({}) mismatch at len={}",
p, len
);
}
if bp.is_close(p) {
let bp_result = bp.find_open(p);
let linear_result = find_open(&words, len, p);
assert_eq!(
bp_result, linear_result,
"find_open({}) mismatch at len={}",
p, len
);
}
if bp.is_open(p) {
let bp_result = bp.enclose(p);
let linear_result = enclose(&words, len, p);
assert_eq!(
bp_result, linear_result,
"enclose({}) mismatch at len={}",
p, len
);
}
let bp_excess = bp.excess(p);
let naive_exc = naive_excess(&words, len, p);
assert_eq!(
bp_excess, naive_exc,
"excess({}) mismatch at len={}",
p, len
);
}
}
#[test]
fn test_cross_verify_small_patterns() {
let patterns: Vec<(Vec<u64>, usize)> = vec![
(vec![0b01], 2), (vec![0b0011], 4), (vec![0b0101], 4), (vec![0b001011], 6), (vec![0b000111], 6), (vec![0x5555555555555555], 64), ];
for (words, len) in patterns {
verify_implementations(words, len);
}
}
#[test]
fn test_cross_verify_multi_word() {
verify_implementations(vec![u64::MAX, 0], 128);
verify_implementations(vec![u64::MAX, u64::MAX, 0, 0], 256);
verify_implementations(vec![0x5555555555555555, 0x5555555555555555], 128);
}
#[test]
fn test_cross_verify_l1_boundary() {
let mut words = vec![u64::MAX; 32]; words.extend(std::iter::repeat_n(0u64, 32)); verify_implementations(words, 4096);
}
#[test]
fn test_cross_verify_random_small() {
let mut rng = ChaCha8Rng::seed_from_u64(42);
for _ in 0..100 {
let num_words = rng.gen_range(1..=4);
let words: Vec<u64> = (0..num_words).map(|_| rng.r#gen()).collect();
let len = num_words * 64;
verify_implementations(words, len);
}
}
#[test]
fn test_cross_verify_random_medium() {
let mut rng = ChaCha8Rng::seed_from_u64(123);
for _ in 0..20 {
let num_words = rng.gen_range(10..=100);
let words: Vec<u64> = (0..num_words).map(|_| rng.r#gen()).collect();
let len = num_words * 64;
let bp = BalancedParens::new(words.clone(), len);
for _ in 0..100 {
let p = rng.gen_range(0..len);
if bp.is_open(p) {
let bp_result = bp.find_close(p);
let linear_result = find_close(&words, len, p);
assert_eq!(bp_result, linear_result, "find_close({}) mismatch", p);
}
}
}
}
#[test]
fn test_cross_verify_random_large() {
let mut rng = ChaCha8Rng::seed_from_u64(999);
let num_words = 1024; let words: Vec<u64> = (0..num_words).map(|_| rng.r#gen()).collect();
let len = num_words * 64;
let bp = BalancedParens::new(words.clone(), len);
for _ in 0..500 {
let p = rng.gen_range(0..len);
if bp.is_open(p) {
let bp_result = bp.find_close(p);
let linear_result = find_close(&words, len, p);
assert_eq!(
bp_result, linear_result,
"find_close({}) mismatch at large scale",
p
);
}
}
}
}
mod inverse_tests {
use super::*;
#[test]
fn test_find_close_find_open_inverse() {
let patterns: Vec<(Vec<u64>, usize)> = vec![
(vec![0b01], 2),
(vec![0b0011], 4),
(vec![0b001011], 6),
(vec![u64::MAX, 0], 128),
];
for (words, len) in patterns {
let bp = BalancedParens::new(words, len);
for p in 0..len {
if bp.is_open(p) {
if let Some(close) = bp.find_close(p) {
let open = bp.find_open(close);
assert_eq!(
open,
Some(p),
"find_open(find_close({})) != {} for close={}",
p,
p,
close
);
}
}
}
}
}
#[test]
fn test_excess_balanced_returns_zero() {
let patterns: Vec<(Vec<u64>, usize)> = vec![
(vec![0b01], 2),
(vec![0b0011], 4),
(vec![0b0101], 4),
(vec![0b001011], 6),
(vec![u64::MAX, 0], 128),
];
for (words, len) in patterns {
let bp = BalancedParens::new(words, len);
assert_eq!(
bp.excess(len - 1),
0,
"Balanced sequence should end with excess 0"
);
}
}
#[test]
fn test_subtree_size_formula() {
let bp = BalancedParens::new(vec![0b001011], 6);
for p in 0..6 {
if bp.is_open(p) {
if let (Some(size), Some(close)) = (bp.subtree_size(p), bp.find_close(p)) {
assert_eq!(
size,
(close - p) / 2,
"subtree_size formula failed at {}",
p
);
}
}
}
}
}
mod index_structure_tests {
use super::*;
#[test]
fn test_l0_index_single_word() {
let bp = BalancedParens::new(vec![0b001011], 6);
assert_eq!(bp.len(), 6);
}
#[test]
fn test_index_correctness_via_find_close() {
let bp = BalancedParens::new(vec![0b001011], 6);
assert_eq!(bp.find_close(0), Some(5));
let bp = BalancedParens::new(vec![u64::MAX, 0], 128);
assert_eq!(bp.find_close(0), Some(127));
assert_eq!(bp.find_close(63), Some(64));
let mut words = vec![u64::MAX; 32];
words.extend(std::iter::repeat_n(0u64, 32));
let bp = BalancedParens::new(words, 4096);
assert_eq!(bp.find_close(0), Some(4095));
assert_eq!(bp.find_close(2047), Some(2048));
}
#[test]
fn test_empty_structure() {
let bp = BalancedParens::new(vec![], 0);
assert!(bp.is_empty());
assert_eq!(bp.find_close(0), None);
assert_eq!(bp.find_open(0), None);
assert_eq!(bp.excess(0), 0);
}
}
mod depth_subtree_tests {
use super::*;
#[test]
fn test_depth_increases_on_open() {
let bp = BalancedParens::new(vec![0b000111], 6);
let expected_depths = [1, 2, 3, 2, 1, 0];
for (i, &expected) in expected_depths.iter().enumerate() {
assert_eq!(
bp.depth(i),
Some(expected),
"depth({}) should be {}",
i,
expected
);
}
}
#[test]
fn test_depth_monotonic_for_opens() {
let bp = BalancedParens::new(vec![u64::MAX], 64);
let mut prev_depth = 0;
for p in 0..64 {
let depth = bp.depth(p).unwrap();
assert!(depth > prev_depth, "Depth should increase for nested opens");
prev_depth = depth;
}
}
#[test]
fn test_subtree_size_leaves() {
let bp = BalancedParens::new(vec![0b0101], 4);
assert_eq!(bp.subtree_size(0), Some(0)); assert_eq!(bp.subtree_size(2), Some(0)); }
#[test]
fn test_subtree_size_nested() {
let bp = BalancedParens::new(vec![0b0011], 4);
assert_eq!(bp.subtree_size(0), Some(1)); assert_eq!(bp.subtree_size(1), Some(0)); }
#[test]
fn test_subtree_size_at_close_returns_none() {
let bp = BalancedParens::new(vec![0b0011], 4);
assert_eq!(bp.subtree_size(2), None);
assert_eq!(bp.subtree_size(3), None);
}
}
mod navigation_tests {
use super::*;
#[test]
fn test_first_child_with_children() {
let bp = BalancedParens::new(vec![0b001011], 6);
assert_eq!(bp.first_child(0), Some(1));
}
#[test]
fn test_first_child_leaf() {
let bp = BalancedParens::new(vec![0b01], 2);
assert_eq!(bp.first_child(0), None);
}
#[test]
fn test_next_sibling_chain() {
let bp = BalancedParens::new(vec![0b010101], 6);
assert_eq!(bp.next_sibling(0), Some(2));
assert_eq!(bp.next_sibling(2), Some(4));
assert_eq!(bp.next_sibling(4), None);
}
#[test]
fn test_next_sibling_nested() {
let bp = BalancedParens::new(vec![0b001011], 6);
assert_eq!(bp.next_sibling(1), Some(3)); assert_eq!(bp.next_sibling(3), None); }
#[test]
fn test_parent_navigation() {
let bp = BalancedParens::new(vec![0b001011], 6);
assert_eq!(bp.parent(1), Some(0));
assert_eq!(bp.parent(3), Some(0));
assert_eq!(bp.parent(0), None); }
#[test]
fn test_navigation_at_close() {
let bp = BalancedParens::new(vec![0b001011], 6);
assert_eq!(bp.first_child(2), None);
assert_eq!(bp.next_sibling(2), None);
assert_eq!(bp.parent(2), None);
}
}
mod edge_cases {
use super::*;
#[test]
fn test_single_bit() {
let bp = BalancedParens::new(vec![1], 1);
assert!(bp.is_open(0));
assert_eq!(bp.find_close(0), None);
assert_eq!(bp.excess(0), 1);
let bp = BalancedParens::new(vec![0], 1);
assert!(bp.is_close(0));
assert_eq!(bp.find_open(0), None);
assert_eq!(bp.excess(0), -1);
}
#[test]
fn test_alternating_full_words() {
let word = 0x5555555555555555u64;
let bp = BalancedParens::new(vec![word], 64);
for i in 0..32 {
assert_eq!(bp.find_close(i * 2), Some(i * 2 + 1));
}
}
#[test]
fn test_deeply_nested_multi_word() {
let words = vec![u64::MAX, u64::MAX, 0, 0];
let bp = BalancedParens::new(words.clone(), 256);
assert_eq!(bp.find_close(0), Some(255));
assert_eq!(bp.find_close(127), Some(128));
assert_eq!(find_close(&words, 256, 0), Some(255));
assert_eq!(find_close(&words, 256, 127), Some(128));
}
#[test]
fn test_partial_word_len() {
let bp = BalancedParens::new(vec![0b0011], 4);
assert_eq!(bp.len(), 4);
assert_eq!(bp.find_close(0), Some(3));
assert_eq!(bp.find_close(1), Some(2));
assert_eq!(bp.excess(3), 0);
}
#[test]
fn test_all_operations_at_boundary() {
let words = vec![u64::MAX, 0];
let bp = BalancedParens::new(words, 128);
assert!(bp.is_open(63));
assert_eq!(bp.find_close(63), Some(64));
assert!(bp.is_close(64));
assert_eq!(bp.find_open(64), Some(63));
assert_eq!(bp.excess(63), 64);
assert_eq!(bp.excess(64), 63);
}
}