Lucas Soriano del Pino
3 years ago
5 changed files with 628 additions and 1 deletions
@ -0,0 +1,124 @@ |
|||||
|
use anyhow::{bail, Result}; |
||||
|
use bit_vec::BitVec; |
||||
|
use std::fmt::Display; |
||||
|
use std::ops::RangeInclusive; |
||||
|
|
||||
|
mod digit_decomposition; |
||||
|
|
||||
|
const BASE: usize = 2; |
||||
|
|
||||
|
/// Maximum number of binary digits for BTC price in whole USD.
|
||||
|
const MAX_DIGITS: usize = 20; |
||||
|
|
||||
|
const MAX_PRICE_DEC: u64 = (BASE as u64).pow(MAX_DIGITS as u32); |
||||
|
|
||||
|
#[derive(Debug)] |
||||
|
pub struct Interval(RangeInclusive<u64>); |
||||
|
|
||||
|
impl Interval { |
||||
|
pub fn new(start: u64, end: u64) -> Result<Self> { |
||||
|
if start > MAX_PRICE_DEC || end > MAX_PRICE_DEC { |
||||
|
bail!("price over maximum") |
||||
|
} |
||||
|
|
||||
|
if start > end { |
||||
|
bail!("invalid interval: start > end") |
||||
|
} |
||||
|
|
||||
|
Ok(Self(start..=end)) |
||||
|
} |
||||
|
|
||||
|
pub fn as_digits(&self) -> Vec<Digits> { |
||||
|
digit_decomposition::group_by_ignoring_digits( |
||||
|
*self.0.start() as usize, |
||||
|
*self.0.end() as usize, |
||||
|
BASE, |
||||
|
MAX_DIGITS, |
||||
|
) |
||||
|
.iter() |
||||
|
.map(|digits| { |
||||
|
let digits = digits.iter().map(|n| *n != 0).collect::<BitVec>(); |
||||
|
Digits(digits) |
||||
|
}) |
||||
|
.collect() |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
#[derive(Clone, Debug)] |
||||
|
pub struct Digits(BitVec); |
||||
|
|
||||
|
impl Digits { |
||||
|
pub fn range(&self) -> RangeInclusive<u64> { |
||||
|
let missing_bits = MAX_DIGITS - self.0.len(); |
||||
|
|
||||
|
let mut bits = self.0.clone(); |
||||
|
bits.append(&mut BitVec::from_elem(missing_bits, false)); |
||||
|
let start = bits.as_u64(); |
||||
|
|
||||
|
let mut bits = self.0.clone(); |
||||
|
bits.append(&mut BitVec::from_elem(missing_bits, true)); |
||||
|
let end = bits.as_u64(); |
||||
|
|
||||
|
start..=end |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
impl std::iter::Iterator for Digits { |
||||
|
type Item = Vec<u8>; |
||||
|
|
||||
|
fn next(&mut self) -> Option<Self::Item> { |
||||
|
self.0.iter().map(|bit| vec![bit as u8]).next() |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
trait BitVecExt { |
||||
|
fn as_u64(&self) -> u64; |
||||
|
} |
||||
|
|
||||
|
impl BitVecExt for BitVec { |
||||
|
fn as_u64(&self) -> u64 { |
||||
|
let len = self.len(); |
||||
|
|
||||
|
self.iter().enumerate().fold(0, |acc, (i, x)| { |
||||
|
acc + ((x as u64) * (BASE.pow((len - i - 1) as u32) as u64)) |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
impl Display for Digits { |
||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
||||
|
self.0 |
||||
|
.iter() |
||||
|
.try_for_each(|digit| write!(f, "{}", digit as u8))?; |
||||
|
|
||||
|
Ok(()) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
#[cfg(test)] |
||||
|
mod tests { |
||||
|
use super::*; |
||||
|
use proptest::prelude::*; |
||||
|
|
||||
|
impl PartialEq<Vec<Digits>> for Interval { |
||||
|
fn eq(&self, other: &Vec<Digits>) -> bool { |
||||
|
let sub_intervals = other.iter().flat_map(|i| i.range()); |
||||
|
sub_intervals.eq(self.0.clone()) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
prop_compose! { |
||||
|
fn interval()(x in 0u64..=MAX_PRICE_DEC, y in 0u64..=MAX_PRICE_DEC) -> Interval { |
||||
|
let (start, end) = if x < y { (x, y) } else { (y, x) }; |
||||
|
|
||||
|
Interval::new(start, end).unwrap() |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
proptest! { |
||||
|
#[test] |
||||
|
fn interval_equal_to_sum_of_sub_intervals_described_by_digits(interval in interval()) { |
||||
|
prop_assert!(interval == interval.as_digits()) |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,459 @@ |
|||||
|
//! Utility functions to decompose numeric outcome values
|
||||
|
//!
|
||||
|
//! This code has been lifted from:
|
||||
|
//! https://github.com/p2pderivatives/rust-dlc/blob/chore%2Ffactor-out-dlc-trie/dlc-trie/src/digit_decomposition.rs
|
||||
|
|
||||
|
/// Describes an interval that starts at `prefix || start` and terminates at `prefix || end`.
|
||||
|
struct PrefixInterval { |
||||
|
/// The prefix common to all numbers within the interval.
|
||||
|
prefix: Vec<usize>, |
||||
|
/// The suffix of the first number in the interval.
|
||||
|
start: Vec<usize>, |
||||
|
/// The suffix of the last number in the interval.
|
||||
|
end: Vec<usize>, |
||||
|
} |
||||
|
|
||||
|
/// Decompose a numeric value into digits in the specified base. If the decomposed
|
||||
|
/// value contains less than `nb_digits`, zeroes will be prepended to reach `nb_digits`
|
||||
|
/// size.
|
||||
|
fn decompose_value(mut value: usize, base: usize, nb_digits: usize) -> Vec<usize> { |
||||
|
let mut res = Vec::new(); |
||||
|
|
||||
|
while value > 0 { |
||||
|
res.push(value % base); |
||||
|
value = ((value as f64) / (base as f64)).floor() as usize; |
||||
|
} |
||||
|
|
||||
|
while res.len() < nb_digits { |
||||
|
res.push(0); |
||||
|
} |
||||
|
|
||||
|
assert_eq!(nb_digits, res.len()); |
||||
|
|
||||
|
res.into_iter().rev().collect() |
||||
|
} |
||||
|
|
||||
|
/// Returns the interval [start, end] as a `PrefixInterval`, which will contain
|
||||
|
/// the common prefix to all numbers in the interval as well as the start and end
|
||||
|
/// suffixes decomposed in the specified base, and zero padded to `nb_digits` if
|
||||
|
/// necessary.
|
||||
|
fn separate_prefix(start: usize, end: usize, base: usize, nb_digits: usize) -> PrefixInterval { |
||||
|
let start_digits = decompose_value(start, base, nb_digits); |
||||
|
let end_digits = decompose_value(end, base, nb_digits); |
||||
|
let mut prefix = Vec::new(); |
||||
|
|
||||
|
let mut i = 0; |
||||
|
while i < nb_digits && start_digits[i] == end_digits[i] { |
||||
|
prefix.push(start_digits[i]); |
||||
|
i += 1; |
||||
|
} |
||||
|
let start = start_digits.into_iter().skip(prefix.len()).collect(); |
||||
|
|
||||
|
let end = end_digits.into_iter().skip(prefix.len()).collect(); |
||||
|
|
||||
|
PrefixInterval { prefix, start, end } |
||||
|
} |
||||
|
|
||||
|
/// Removes the trailing digits from `digits` that are equal to `num`.
|
||||
|
fn remove_tail_if_equal(mut digits: Vec<usize>, num: usize) -> Vec<usize> { |
||||
|
let mut i = digits.len(); |
||||
|
while i > 1 && digits[i - 1] == num { |
||||
|
i -= 1; |
||||
|
} |
||||
|
digits.truncate(i); |
||||
|
digits |
||||
|
} |
||||
|
|
||||
|
/// Compute the groupings for the end of the interval.
|
||||
|
fn back_groupings(digits: Vec<usize>, base: usize) -> Vec<Vec<usize>> { |
||||
|
let digits = remove_tail_if_equal(digits, base - 1); |
||||
|
if digits.is_empty() { |
||||
|
return vec![vec![base - 1]]; |
||||
|
} |
||||
|
let mut prefix = vec![digits[0]]; |
||||
|
let mut res: Vec<Vec<usize>> = Vec::new(); |
||||
|
for digit in digits.iter().skip(1) { |
||||
|
let mut last = 0; |
||||
|
let digit = *digit; |
||||
|
while last < digit { |
||||
|
let mut new_res = prefix.clone(); |
||||
|
new_res.push(last); |
||||
|
res.push(new_res); |
||||
|
last += 1; |
||||
|
} |
||||
|
prefix.push(digit); |
||||
|
} |
||||
|
res.push(digits); |
||||
|
res |
||||
|
} |
||||
|
|
||||
|
/// Compute the groupings for the beginning of the interval.
|
||||
|
fn front_groupings(digits: Vec<usize>, base: usize) -> Vec<Vec<usize>> { |
||||
|
let digits = remove_tail_if_equal(digits, 0); |
||||
|
if digits.is_empty() { |
||||
|
return vec![vec![0]]; |
||||
|
} |
||||
|
let mut prefix = digits.clone(); |
||||
|
let mut res: Vec<Vec<usize>> = vec![digits.clone()]; |
||||
|
for digit in digits.into_iter().skip(1).rev() { |
||||
|
prefix.pop(); |
||||
|
let mut last = digit + 1; |
||||
|
while last < base { |
||||
|
let mut new_res = prefix.clone(); |
||||
|
new_res.push(last); |
||||
|
res.push(new_res); |
||||
|
last += 1; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
res |
||||
|
} |
||||
|
|
||||
|
/// Compute the groupings for the middle of the interval.
|
||||
|
fn middle_grouping(first_digit_start: usize, first_digit_end: usize) -> Vec<Vec<usize>> { |
||||
|
let mut res: Vec<Vec<usize>> = Vec::new(); |
||||
|
let mut first_digit_start = first_digit_start + 1; |
||||
|
while first_digit_start < first_digit_end { |
||||
|
res.push(vec![first_digit_start]); |
||||
|
first_digit_start += 1; |
||||
|
} |
||||
|
|
||||
|
res |
||||
|
} |
||||
|
|
||||
|
/// Returns the set of decomposed prefixes that cover the range [start, end].
|
||||
|
pub(crate) fn group_by_ignoring_digits( |
||||
|
start: usize, |
||||
|
end: usize, |
||||
|
base: usize, |
||||
|
num_digits: usize, |
||||
|
) -> Vec<Vec<usize>> { |
||||
|
let prefix_range = separate_prefix(start, end, base, num_digits); |
||||
|
let start_is_all_zeros = prefix_range.start.iter().all(|x| *x == 0); |
||||
|
let end_is_all_max = prefix_range.end.iter().all(|x| *x == base - 1); |
||||
|
|
||||
|
if start == end || start_is_all_zeros && end_is_all_max && !prefix_range.prefix.is_empty() { |
||||
|
return vec![prefix_range.prefix]; |
||||
|
} |
||||
|
let mut res: Vec<Vec<usize>> = Vec::new(); |
||||
|
if prefix_range.prefix.len() == num_digits - 1 { |
||||
|
for i in prefix_range.start[prefix_range.start.len() - 1] |
||||
|
..prefix_range.end[prefix_range.end.len() - 1] + 1 |
||||
|
{ |
||||
|
let mut new_res = prefix_range.prefix.clone(); |
||||
|
new_res.push(i); |
||||
|
res.push(new_res) |
||||
|
} |
||||
|
} else { |
||||
|
let mut front = front_groupings(prefix_range.start.clone(), base); |
||||
|
let mut middle = middle_grouping(prefix_range.start[0], prefix_range.end[0]); |
||||
|
let mut back = back_groupings(prefix_range.end.clone(), base); |
||||
|
res.append(&mut front); |
||||
|
res.append(&mut middle); |
||||
|
res.append(&mut back); |
||||
|
res = res |
||||
|
.into_iter() |
||||
|
.map(|x| { |
||||
|
prefix_range |
||||
|
.prefix |
||||
|
.iter() |
||||
|
.cloned() |
||||
|
.chain(x.into_iter()) |
||||
|
.collect() |
||||
|
}) |
||||
|
.collect(); |
||||
|
} |
||||
|
|
||||
|
res |
||||
|
} |
||||
|
|
||||
|
#[cfg(test)] |
||||
|
mod tests { |
||||
|
struct DecompositionTestCase { |
||||
|
composed: usize, |
||||
|
decomposed: Vec<usize>, |
||||
|
base: usize, |
||||
|
nb_digits: usize, |
||||
|
} |
||||
|
|
||||
|
struct GroupingTestCase { |
||||
|
start_index: usize, |
||||
|
end_index: usize, |
||||
|
base: usize, |
||||
|
nb_digits: usize, |
||||
|
expected: Vec<Vec<usize>>, |
||||
|
} |
||||
|
fn decomposition_test_cases() -> Vec<DecompositionTestCase> { |
||||
|
vec![ |
||||
|
DecompositionTestCase { |
||||
|
composed: 123456789, |
||||
|
decomposed: vec![1, 2, 3, 4, 5, 6, 7, 8, 9], |
||||
|
base: 10, |
||||
|
nb_digits: 9, |
||||
|
}, |
||||
|
DecompositionTestCase { |
||||
|
composed: 4321, |
||||
|
decomposed: vec![1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1], |
||||
|
base: 2, |
||||
|
nb_digits: 13, |
||||
|
}, |
||||
|
DecompositionTestCase { |
||||
|
composed: 0, |
||||
|
decomposed: vec![0, 0, 0, 0], |
||||
|
base: 8, |
||||
|
nb_digits: 4, |
||||
|
}, |
||||
|
DecompositionTestCase { |
||||
|
composed: 2, |
||||
|
decomposed: vec![0, 2], |
||||
|
base: 10, |
||||
|
nb_digits: 2, |
||||
|
}, |
||||
|
DecompositionTestCase { |
||||
|
composed: 1, |
||||
|
decomposed: vec![1], |
||||
|
base: 2, |
||||
|
nb_digits: 1, |
||||
|
}, |
||||
|
] |
||||
|
} |
||||
|
|
||||
|
fn grouping_test_cases() -> Vec<GroupingTestCase> { |
||||
|
vec![ |
||||
|
GroupingTestCase { |
||||
|
start_index: 123, |
||||
|
end_index: 123, |
||||
|
base: 10, |
||||
|
nb_digits: 3, |
||||
|
expected: vec![vec![1, 2, 3]], |
||||
|
}, |
||||
|
GroupingTestCase { |
||||
|
start_index: 171, |
||||
|
end_index: 210, |
||||
|
base: 16, |
||||
|
nb_digits: 2, |
||||
|
expected: vec![ |
||||
|
vec![10, 11], |
||||
|
vec![10, 12], |
||||
|
vec![10, 13], |
||||
|
vec![10, 14], |
||||
|
vec![10, 15], |
||||
|
vec![11], |
||||
|
vec![12], |
||||
|
vec![13, 0], |
||||
|
vec![13, 1], |
||||
|
vec![13, 2], |
||||
|
], |
||||
|
}, |
||||
|
GroupingTestCase { |
||||
|
start_index: 73899, |
||||
|
end_index: 73938, |
||||
|
base: 16, |
||||
|
nb_digits: 6, |
||||
|
expected: vec![ |
||||
|
vec![0, 1, 2, 0, 10, 11], |
||||
|
vec![0, 1, 2, 0, 10, 12], |
||||
|
vec![0, 1, 2, 0, 10, 13], |
||||
|
vec![0, 1, 2, 0, 10, 14], |
||||
|
vec![0, 1, 2, 0, 10, 15], |
||||
|
vec![0, 1, 2, 0, 11], |
||||
|
vec![0, 1, 2, 0, 12], |
||||
|
vec![0, 1, 2, 0, 13, 0], |
||||
|
vec![0, 1, 2, 0, 13, 1], |
||||
|
vec![0, 1, 2, 0, 13, 2], |
||||
|
], |
||||
|
}, |
||||
|
GroupingTestCase { |
||||
|
start_index: 1234, |
||||
|
end_index: 4321, |
||||
|
base: 10, |
||||
|
nb_digits: 4, |
||||
|
expected: vec![ |
||||
|
vec![1, 2, 3, 4], |
||||
|
vec![1, 2, 3, 5], |
||||
|
vec![1, 2, 3, 6], |
||||
|
vec![1, 2, 3, 7], |
||||
|
vec![1, 2, 3, 8], |
||||
|
vec![1, 2, 3, 9], |
||||
|
vec![1, 2, 4], |
||||
|
vec![1, 2, 5], |
||||
|
vec![1, 2, 6], |
||||
|
vec![1, 2, 7], |
||||
|
vec![1, 2, 8], |
||||
|
vec![1, 2, 9], |
||||
|
vec![1, 3], |
||||
|
vec![1, 4], |
||||
|
vec![1, 5], |
||||
|
vec![1, 6], |
||||
|
vec![1, 7], |
||||
|
vec![1, 8], |
||||
|
vec![1, 9], |
||||
|
vec![2], |
||||
|
vec![3], |
||||
|
vec![4, 0], |
||||
|
vec![4, 1], |
||||
|
vec![4, 2], |
||||
|
vec![4, 3, 0], |
||||
|
vec![4, 3, 1], |
||||
|
vec![4, 3, 2, 0], |
||||
|
vec![4, 3, 2, 1], |
||||
|
], |
||||
|
}, |
||||
|
GroupingTestCase { |
||||
|
start_index: 1201234, |
||||
|
end_index: 1204321, |
||||
|
base: 10, |
||||
|
nb_digits: 8, |
||||
|
expected: vec![ |
||||
|
vec![0, 1, 2, 0, 1, 2, 3, 4], |
||||
|
vec![0, 1, 2, 0, 1, 2, 3, 5], |
||||
|
vec![0, 1, 2, 0, 1, 2, 3, 6], |
||||
|
vec![0, 1, 2, 0, 1, 2, 3, 7], |
||||
|
vec![0, 1, 2, 0, 1, 2, 3, 8], |
||||
|
vec![0, 1, 2, 0, 1, 2, 3, 9], |
||||
|
vec![0, 1, 2, 0, 1, 2, 4], |
||||
|
vec![0, 1, 2, 0, 1, 2, 5], |
||||
|
vec![0, 1, 2, 0, 1, 2, 6], |
||||
|
vec![0, 1, 2, 0, 1, 2, 7], |
||||
|
vec![0, 1, 2, 0, 1, 2, 8], |
||||
|
vec![0, 1, 2, 0, 1, 2, 9], |
||||
|
vec![0, 1, 2, 0, 1, 3], |
||||
|
vec![0, 1, 2, 0, 1, 4], |
||||
|
vec![0, 1, 2, 0, 1, 5], |
||||
|
vec![0, 1, 2, 0, 1, 6], |
||||
|
vec![0, 1, 2, 0, 1, 7], |
||||
|
vec![0, 1, 2, 0, 1, 8], |
||||
|
vec![0, 1, 2, 0, 1, 9], |
||||
|
vec![0, 1, 2, 0, 2], |
||||
|
vec![0, 1, 2, 0, 3], |
||||
|
vec![0, 1, 2, 0, 4, 0], |
||||
|
vec![0, 1, 2, 0, 4, 1], |
||||
|
vec![0, 1, 2, 0, 4, 2], |
||||
|
vec![0, 1, 2, 0, 4, 3, 0], |
||||
|
vec![0, 1, 2, 0, 4, 3, 1], |
||||
|
vec![0, 1, 2, 0, 4, 3, 2, 0], |
||||
|
vec![0, 1, 2, 0, 4, 3, 2, 1], |
||||
|
], |
||||
|
}, |
||||
|
GroupingTestCase { |
||||
|
start_index: 2200, |
||||
|
end_index: 4999, |
||||
|
base: 10, |
||||
|
nb_digits: 4, |
||||
|
expected: vec![ |
||||
|
vec![2, 2], |
||||
|
vec![2, 3], |
||||
|
vec![2, 4], |
||||
|
vec![2, 5], |
||||
|
vec![2, 6], |
||||
|
vec![2, 7], |
||||
|
vec![2, 8], |
||||
|
vec![2, 9], |
||||
|
vec![3], |
||||
|
vec![4], |
||||
|
], |
||||
|
}, |
||||
|
GroupingTestCase { |
||||
|
start_index: 0, |
||||
|
end_index: 99, |
||||
|
base: 10, |
||||
|
nb_digits: 2, |
||||
|
expected: vec![ |
||||
|
vec![0], |
||||
|
vec![1], |
||||
|
vec![2], |
||||
|
vec![3], |
||||
|
vec![4], |
||||
|
vec![5], |
||||
|
vec![6], |
||||
|
vec![7], |
||||
|
vec![8], |
||||
|
vec![9], |
||||
|
], |
||||
|
}, |
||||
|
GroupingTestCase { |
||||
|
start_index: 100, |
||||
|
end_index: 199, |
||||
|
base: 10, |
||||
|
nb_digits: 3, |
||||
|
expected: vec![vec![1]], |
||||
|
}, |
||||
|
GroupingTestCase { |
||||
|
start_index: 100, |
||||
|
end_index: 200, |
||||
|
base: 10, |
||||
|
nb_digits: 3, |
||||
|
expected: vec![vec![1], vec![2, 0, 0]], |
||||
|
}, |
||||
|
GroupingTestCase { |
||||
|
start_index: 11, |
||||
|
end_index: 18, |
||||
|
base: 10, |
||||
|
nb_digits: 2, |
||||
|
expected: vec![ |
||||
|
vec![1, 1], |
||||
|
vec![1, 2], |
||||
|
vec![1, 3], |
||||
|
vec![1, 4], |
||||
|
vec![1, 5], |
||||
|
vec![1, 6], |
||||
|
vec![1, 7], |
||||
|
vec![1, 8], |
||||
|
], |
||||
|
}, |
||||
|
GroupingTestCase { |
||||
|
start_index: 11, |
||||
|
end_index: 23, |
||||
|
base: 2, |
||||
|
nb_digits: 5, |
||||
|
expected: vec![vec![0, 1, 0, 1, 1], vec![0, 1, 1], vec![1, 0]], |
||||
|
}, |
||||
|
GroupingTestCase { |
||||
|
start_index: 5677, |
||||
|
end_index: 8621, |
||||
|
base: 2, |
||||
|
nb_digits: 14, |
||||
|
expected: vec![ |
||||
|
vec![0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1], |
||||
|
vec![0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1], |
||||
|
vec![0, 1, 0, 1, 1, 0, 0, 0, 1, 1], |
||||
|
vec![0, 1, 0, 1, 1, 0, 0, 1], |
||||
|
vec![0, 1, 0, 1, 1, 0, 1], |
||||
|
vec![0, 1, 0, 1, 1, 1], |
||||
|
vec![0, 1, 1], |
||||
|
vec![1, 0, 0, 0, 0, 0], |
||||
|
vec![1, 0, 0, 0, 0, 1, 0], |
||||
|
vec![1, 0, 0, 0, 0, 1, 1, 0, 0], |
||||
|
vec![1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0], |
||||
|
vec![1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0], |
||||
|
vec![1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0], |
||||
|
], |
||||
|
}, |
||||
|
] |
||||
|
} |
||||
|
|
||||
|
#[test] |
||||
|
fn decompose_value_test() { |
||||
|
for test_case in decomposition_test_cases() { |
||||
|
assert_eq!( |
||||
|
test_case.decomposed, |
||||
|
super::decompose_value(test_case.composed, test_case.base, test_case.nb_digits) |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
#[test] |
||||
|
fn group_by_ignoring_digits_test() { |
||||
|
for test_case in grouping_test_cases() { |
||||
|
assert_eq!( |
||||
|
test_case.expected, |
||||
|
super::group_by_ignoring_digits( |
||||
|
test_case.start_index, |
||||
|
test_case.end_index, |
||||
|
test_case.base, |
||||
|
test_case.nb_digits |
||||
|
) |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
} |
Loading…
Reference in new issue