use crate::node::Value;
use super::ast::{ShellOrigin, TraversalOrder};
pub type Tuple = Vec<(String, Value)>;
pub fn apply_order(
tuples: Vec<Tuple>,
clause_sizes: &[usize],
order: &TraversalOrder,
) -> Result<Vec<Tuple>, String> {
let geometric = !matches!(order,
TraversalOrder::Lex { .. }
| TraversalOrder::Custom { .. }
| TraversalOrder::Sobol { .. }
);
if geometric {
let expected: usize = clause_sizes.iter().product();
if !clause_sizes.is_empty() && tuples.len() != expected {
return Err(format!(
"apply_order: tuple count ({}) doesn't match the lattice \
product ({}) for clause_sizes {clause_sizes:?}. \
Index-space orderings require a complete Cartesian \
lattice — filter-applied streams or Union-mode \
concatenations break this invariant.",
tuples.len(), expected,
));
}
}
match order {
TraversalOrder::Lex { count } => Ok(truncate(tuples, *count)),
TraversalOrder::ReverseLex { count } => Ok(order_reverse_lex(tuples, clause_sizes, *count)),
TraversalOrder::Diagonal { count } => Ok(order_diagonal(tuples, clause_sizes, *count, false)),
TraversalOrder::Antidiagonal { count } => Ok(order_diagonal(tuples, clause_sizes, *count, true)),
TraversalOrder::Extrema { strata } => Ok(order_extrema(tuples, clause_sizes, *strata)),
TraversalOrder::Shells { origin, depth } => Ok(order_shells(tuples, clause_sizes, *origin, *depth)),
TraversalOrder::Halton { count } => Ok(order_halton(tuples, clause_sizes, *count)),
TraversalOrder::Sobol { .. } => Err(
"order sobol: Sobol sequences require tabulated Joe-Kuo direction \
numbers (public domain but not yet bundled). Use `order: halton/N` \
for low-discrepancy coverage, or `order: lhs/N seed=K` for stratified \
sampling.".to_string()
),
TraversalOrder::Lhs { count, seed } => Ok(order_lhs(tuples, clause_sizes, *count, *seed)),
TraversalOrder::Custom { function } => Err(format!(
"order custom({function}): user-supplied ordering functions are not yet implemented"
)),
}
}
fn truncate(mut tuples: Vec<Tuple>, count: Option<usize>) -> Vec<Tuple> {
if let Some(n) = count {
tuples.truncate(n);
}
tuples
}
fn order_reverse_lex(
tuples: Vec<Tuple>,
sizes: &[usize],
count: Option<usize>,
) -> Vec<Tuple> {
if sizes.is_empty() || tuples.is_empty() {
return truncate(tuples, count);
}
let n_clauses = sizes.len();
let total: usize = sizes.iter().product();
if total != tuples.len() {
let mut t = tuples;
t.reverse();
return truncate(t, count);
}
let strides_lex = compute_lex_strides(sizes);
let strides_rev = compute_reverse_strides(sizes);
let mut indexed: Vec<(usize, Tuple)> = tuples.into_iter().enumerate()
.map(|(p, t)| {
let indices = decode_lex(p, &strides_lex, n_clauses, sizes);
let p_rev = encode_reverse(&indices, &strides_rev);
(p_rev, t)
})
.collect();
indexed.sort_by_key(|(p, _)| *p);
let result: Vec<Tuple> = indexed.into_iter().map(|(_, t)| t).collect();
truncate(result, count)
}
fn order_diagonal(
tuples: Vec<Tuple>,
sizes: &[usize],
count: Option<usize>,
descending: bool,
) -> Vec<Tuple> {
if sizes.is_empty() || tuples.is_empty() {
return truncate(tuples, count);
}
let total: usize = sizes.iter().product();
if total != tuples.len() {
return truncate(tuples, count);
}
let strides = compute_lex_strides(sizes);
let n = sizes.len();
let mut indexed: Vec<(usize, usize, Tuple)> = tuples.into_iter().enumerate()
.map(|(p, t)| {
let indices = decode_lex(p, &strides, n, sizes);
let sum: usize = indices.iter().sum();
(sum, p, t)
})
.collect();
indexed.sort_by(|a, b| {
if descending {
b.0.cmp(&a.0).then(b.1.cmp(&a.1))
} else {
a.0.cmp(&b.0).then(a.1.cmp(&b.1))
}
});
let result: Vec<Tuple> = indexed.into_iter().map(|(_, _, t)| t).collect();
truncate(result, count)
}
fn order_extrema(
tuples: Vec<Tuple>,
sizes: &[usize],
strata: Option<usize>,
) -> Vec<Tuple> {
if sizes.is_empty() || tuples.is_empty() {
return tuples;
}
let total: usize = sizes.iter().product();
if total != tuples.len() {
return tuples;
}
let strides = compute_lex_strides(sizes);
let n = sizes.len();
let mut indexed: Vec<(usize, usize, Tuple)> = tuples.into_iter().enumerate()
.map(|(p, t)| {
let indices = decode_lex(p, &strides, n, sizes);
let interior_count = indices.iter().enumerate()
.filter(|&(axis, &idx)| {
let s = sizes[axis];
s > 1 && idx != 0 && idx != s - 1
})
.count();
(interior_count, p, t)
})
.collect();
indexed.sort_by(|a, b| a.0.cmp(&b.0).then(a.1.cmp(&b.1)));
if let Some(strata_keep) = strata {
indexed.retain(|(c, _, _)| *c < strata_keep);
}
indexed.into_iter().map(|(_, _, t)| t).collect()
}
fn order_shells(
tuples: Vec<Tuple>,
sizes: &[usize],
origin: ShellOrigin,
depth: Option<usize>,
) -> Vec<Tuple> {
if sizes.is_empty() || tuples.is_empty() {
return tuples;
}
let total: usize = sizes.iter().product();
if total != tuples.len() {
return tuples;
}
let strides = compute_lex_strides(sizes);
let n = sizes.len();
let mut indexed: Vec<(usize, usize, Tuple)> = tuples.into_iter().enumerate()
.map(|(p, t)| {
let indices = decode_lex(p, &strides, n, sizes);
let d = shell_distance(&indices, sizes, origin);
(d, p, t)
})
.collect();
indexed.sort_by(|a, b| a.0.cmp(&b.0).then(a.1.cmp(&b.1)));
if let Some(d_keep) = depth {
indexed.retain(|(d, _, _)| *d < d_keep);
}
indexed.into_iter().map(|(_, _, t)| t).collect()
}
fn shell_distance(indices: &[usize], sizes: &[usize], origin: ShellOrigin) -> usize {
match origin {
ShellOrigin::Outer => {
indices.iter().enumerate()
.map(|(axis, &idx)| {
let s = sizes[axis];
if s <= 1 { 0 } else {
let to_min = idx;
let to_max = s - 1 - idx;
to_min.min(to_max)
}
})
.min()
.unwrap_or(0)
}
ShellOrigin::Center => {
indices.iter().enumerate()
.map(|(axis, &idx)| {
let s = sizes[axis];
if s <= 1 { 0 } else {
let center = (s - 1) / 2;
idx.abs_diff(center)
}
})
.max()
.unwrap_or(0)
}
ShellOrigin::Corner => {
indices.iter().copied().max().unwrap_or(0)
}
}
}
const HALTON_PRIMES: &[u64] = &[
2, 3, 5, 7, 11, 13, 17, 19, 23, 29,
31, 37, 41, 43, 47, 53, 59, 61, 67, 71,
73, 79, 83, 89, 97,
];
fn order_halton(
tuples: Vec<Tuple>,
sizes: &[usize],
count: Option<usize>,
) -> Vec<Tuple> {
let n = sizes.len();
if n == 0 || tuples.is_empty() {
return tuples;
}
if n > HALTON_PRIMES.len() {
return truncate(tuples, count);
}
let strides = compute_lex_strides(sizes);
let total: usize = sizes.iter().product();
let mut lex_to_input: std::collections::HashMap<usize, usize> =
std::collections::HashMap::with_capacity(tuples.len());
for (input_idx, tup) in tuples.iter().enumerate() {
lex_to_input.insert(input_idx, input_idx);
let _ = (strides.as_slice(), total, tup); }
let want = count.unwrap_or(tuples.len()).min(tuples.len());
let mut emitted: Vec<bool> = vec![false; tuples.len()];
let mut out: Vec<Tuple> = Vec::with_capacity(want);
let points: Vec<Vec<f64>> = (0..tuples.len())
.map(|input_idx| {
let indices = decode_lex(input_idx, &strides, n, sizes);
indices.iter().enumerate().map(|(axis, idx)| {
let s = sizes[axis] as f64;
if s <= 1.0 { 0.5 } else { *idx as f64 / (s - 1.0) }
}).collect()
})
.collect();
let mut halton_idx: u64 = 1; while out.len() < want {
let target: Vec<f64> = (0..n)
.map(|axis| halton_value(halton_idx, HALTON_PRIMES[axis]))
.collect();
let mut best: Option<(usize, f64)> = None;
for (i, p) in points.iter().enumerate() {
if emitted[i] { continue; }
let d2: f64 = p.iter().zip(target.iter())
.map(|(a, b)| (a - b).powi(2))
.sum();
if best.map_or(true, |(_, bd)| d2 < bd) {
best = Some((i, d2));
}
}
match best {
Some((i, _)) => {
emitted[i] = true;
out.push(tuples[i].clone());
}
None => break, }
halton_idx = halton_idx.saturating_add(1);
}
out
}
fn halton_value(index: u64, base: u64) -> f64 {
let mut result = 0.0_f64;
let mut f = 1.0_f64 / base as f64;
let mut i = index;
while i > 0 {
result += f * (i % base) as f64;
i /= base;
f /= base as f64;
}
result
}
fn order_lhs(
tuples: Vec<Tuple>,
sizes: &[usize],
count: Option<usize>,
seed: Option<u64>,
) -> Vec<Tuple> {
let n_axes = sizes.len();
if n_axes == 0 || tuples.is_empty() {
return tuples;
}
let want = count.unwrap_or(tuples.len()).min(tuples.len());
if want == 0 {
return Vec::new();
}
let seed = seed.unwrap_or(0);
let strides = compute_lex_strides(sizes);
let points: Vec<Vec<f64>> = (0..tuples.len())
.map(|input_idx| {
let indices = decode_lex(input_idx, &strides, n_axes, sizes);
indices.iter().enumerate().map(|(axis, idx)| {
let s = sizes[axis] as f64;
if s <= 1.0 { 0.5 } else { *idx as f64 / (s - 1.0) }
}).collect()
})
.collect();
let stratum_width = 1.0 / want as f64;
let mut targets: Vec<Vec<f64>> = (0..want)
.map(|i| vec![(i as f64 + 0.5) * stratum_width; n_axes])
.collect();
for axis in 0..n_axes {
let perm = fisher_yates_permutation(want, seed.wrapping_add(axis as u64));
let original: Vec<f64> = (0..want).map(|i| targets[i][axis]).collect();
for (new_pos, &old_pos) in perm.iter().enumerate() {
targets[new_pos][axis] = original[old_pos];
}
}
let mut emitted: Vec<bool> = vec![false; tuples.len()];
let mut out: Vec<Tuple> = Vec::with_capacity(want);
for target in &targets {
let mut best: Option<(usize, f64)> = None;
for (i, p) in points.iter().enumerate() {
if emitted[i] { continue; }
let d2: f64 = p.iter().zip(target.iter())
.map(|(a, b)| (a - b).powi(2))
.sum();
if best.map_or(true, |(_, bd)| d2 < bd) {
best = Some((i, d2));
}
}
match best {
Some((i, _)) => {
emitted[i] = true;
out.push(tuples[i].clone());
}
None => break,
}
}
out
}
fn fisher_yates_permutation(n: usize, seed: u64) -> Vec<usize> {
let mut perm: Vec<usize> = (0..n).collect();
let mut state = seed.wrapping_add(0x9E37_79B9_7F4A_7C15);
for i in (1..n).rev() {
state = state.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut z = state;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^= z >> 31;
let j = (z as usize) % (i + 1);
perm.swap(i, j);
}
perm
}
fn compute_lex_strides(sizes: &[usize]) -> Vec<usize> {
let n = sizes.len();
let mut strides = vec![1usize; n];
for axis in (0..n.saturating_sub(1)).rev() {
strides[axis] = strides[axis + 1] * sizes[axis + 1];
}
strides
}
fn compute_reverse_strides(sizes: &[usize]) -> Vec<usize> {
let n = sizes.len();
let mut strides = vec![1usize; n];
for axis in 1..n {
strides[axis] = strides[axis - 1] * sizes[axis - 1];
}
strides
}
fn decode_lex(p: usize, strides: &[usize], n: usize, sizes: &[usize]) -> Vec<usize> {
(0..n).map(|axis| (p / strides[axis]) % sizes[axis]).collect()
}
fn encode_reverse(indices: &[usize], strides: &[usize]) -> usize {
indices.iter().zip(strides.iter()).map(|(i, s)| i * s).sum()
}
#[cfg(test)]
mod tests {
use super::*;
fn tuple(vars: &[(&str, u64)]) -> Tuple {
vars.iter().map(|(n, v)| (n.to_string(), Value::U64(*v))).collect()
}
fn lex_3x3() -> (Vec<Tuple>, Vec<usize>) {
let mut tuples = Vec::new();
for x in 1..=3 {
for y in 1..=3 {
tuples.push(tuple(&[("x", x), ("y", y)]));
}
}
(tuples, vec![3, 3])
}
fn names(t: &Tuple) -> Vec<u64> {
t.iter().map(|(_, v)| match v {
Value::U64(n) => *n,
_ => 0,
}).collect()
}
#[test]
fn lex_no_truncate_is_identity() {
let (tuples, sizes) = lex_3x3();
let result = apply_order(tuples.clone(), &sizes,
&TraversalOrder::Lex { count: None }).unwrap();
assert_eq!(result.len(), 9);
assert_eq!(names(&result[0]), vec![1, 1]);
assert_eq!(names(&result[8]), vec![3, 3]);
}
#[test]
fn lex_with_count_truncates() {
let (tuples, sizes) = lex_3x3();
let result = apply_order(tuples, &sizes,
&TraversalOrder::Lex { count: Some(4) }).unwrap();
assert_eq!(result.len(), 4);
assert_eq!(names(&result[3]), vec![2, 1]);
}
#[test]
fn reverse_lex_swaps_axis_speed() {
let (tuples, sizes) = lex_3x3();
let result = apply_order(tuples, &sizes,
&TraversalOrder::ReverseLex { count: None }).unwrap();
assert_eq!(names(&result[0]), vec![1, 1]);
assert_eq!(names(&result[1]), vec![2, 1]);
assert_eq!(names(&result[2]), vec![3, 1]);
assert_eq!(names(&result[3]), vec![1, 2]);
}
#[test]
fn diagonal_is_index_sum_ascending() {
let (tuples, sizes) = lex_3x3();
let result = apply_order(tuples, &sizes,
&TraversalOrder::Diagonal { count: None }).unwrap();
assert_eq!(names(&result[0]), vec![1, 1]);
assert_eq!(names(&result[1]), vec![1, 2]);
assert_eq!(names(&result[2]), vec![2, 1]);
assert_eq!(names(&result[3]), vec![1, 3]);
assert_eq!(names(&result[8]), vec![3, 3]);
}
#[test]
fn antidiagonal_is_index_sum_descending() {
let (tuples, sizes) = lex_3x3();
let result = apply_order(tuples, &sizes,
&TraversalOrder::Antidiagonal { count: None }).unwrap();
assert_eq!(names(&result[0]), vec![3, 3]);
assert_eq!(names(&result[8]), vec![1, 1]);
}
#[test]
fn extrema_corners_first() {
let (tuples, sizes) = lex_3x3();
let result = apply_order(tuples, &sizes,
&TraversalOrder::Extrema { strata: None }).unwrap();
let first_four: Vec<Vec<u64>> = result[..4].iter().map(names).collect();
assert!(first_four.contains(&vec![1, 1]));
assert!(first_four.contains(&vec![1, 3]));
assert!(first_four.contains(&vec![3, 1]));
assert!(first_four.contains(&vec![3, 3]));
let next_four: Vec<Vec<u64>> = result[4..8].iter().map(names).collect();
assert!(next_four.contains(&vec![1, 2]));
assert!(next_four.contains(&vec![2, 1]));
assert_eq!(names(&result[8]), vec![2, 2]);
}
#[test]
fn extrema_strata_1_keeps_corners_only() {
let (tuples, sizes) = lex_3x3();
let result = apply_order(tuples, &sizes,
&TraversalOrder::Extrema { strata: Some(1) }).unwrap();
assert_eq!(result.len(), 4);
let yielded: Vec<Vec<u64>> = result.iter().map(names).collect();
assert!(yielded.contains(&vec![1, 1]));
assert!(yielded.contains(&vec![3, 3]));
}
#[test]
fn shells_outer_emits_boundary_first() {
let (tuples, sizes) = lex_3x3();
let result = apply_order(tuples, &sizes,
&TraversalOrder::Shells {
origin: ShellOrigin::Outer,
depth: None,
}).unwrap();
assert_eq!(result.len(), 9);
assert_eq!(names(&result[8]), vec![2, 2]);
}
#[test]
fn shells_outer_depth_1_keeps_only_boundary() {
let (tuples, sizes) = lex_3x3();
let result = apply_order(tuples, &sizes,
&TraversalOrder::Shells {
origin: ShellOrigin::Outer,
depth: Some(1),
}).unwrap();
assert_eq!(result.len(), 8);
}
#[test]
fn shells_center_emits_center_first() {
let (tuples, sizes) = lex_3x3();
let result = apply_order(tuples, &sizes,
&TraversalOrder::Shells {
origin: ShellOrigin::Center,
depth: None,
}).unwrap();
assert_eq!(names(&result[0]), vec![2, 2]);
}
#[test]
fn sobol_returns_clear_error_pointing_at_alternatives() {
let (tuples, sizes) = lex_3x3();
let err = apply_order(tuples, &sizes,
&TraversalOrder::Sobol { count: Some(4) }).unwrap_err();
assert!(err.to_lowercase().contains("sobol"), "{err}");
assert!(err.to_lowercase().contains("halton") || err.to_lowercase().contains("lhs"),
"error should suggest halton or lhs as alternatives: {err}");
}
#[test]
fn lhs_with_default_seed_is_deterministic() {
let (tuples, sizes) = lex_3x3();
let a = apply_order(tuples.clone(), &sizes,
&TraversalOrder::Lhs { count: Some(4), seed: None }).unwrap();
let b = apply_order(tuples, &sizes,
&TraversalOrder::Lhs { count: Some(4), seed: None }).unwrap();
assert_eq!(a, b, "lhs default seed should be deterministic");
assert_eq!(a.len(), 4);
}
#[test]
fn lhs_different_seeds_produce_different_orderings() {
let (tuples, sizes) = lex_3x3();
let a = apply_order(tuples.clone(), &sizes,
&TraversalOrder::Lhs { count: Some(4), seed: Some(1) }).unwrap();
let b = apply_order(tuples, &sizes,
&TraversalOrder::Lhs { count: Some(4), seed: Some(42) }).unwrap();
assert_ne!(a, b, "different seeds should produce different orderings");
}
#[test]
fn lhs_count_none_emits_every_tuple() {
let (tuples, sizes) = lex_3x3();
let result = apply_order(tuples.clone(), &sizes,
&TraversalOrder::Lhs { count: None, seed: Some(0) }).unwrap();
assert_eq!(result.len(), tuples.len());
for orig in &tuples {
assert!(result.contains(orig), "lhs dropped tuple {orig:?}");
}
}
#[test]
fn lhs_emits_unique_tuples() {
let (tuples, sizes) = lex_3x3();
let result = apply_order(tuples, &sizes,
&TraversalOrder::Lhs { count: Some(5), seed: Some(7) }).unwrap();
assert_eq!(result.len(), 5);
let unique: std::collections::HashSet<_> = result.iter()
.map(|t| t.iter().map(|(_, v)| format!("{v:?}")).collect::<Vec<_>>())
.collect();
assert_eq!(unique.len(), 5, "lhs should emit unique tuples");
}
#[test]
fn fisher_yates_seeded_is_deterministic() {
let p1 = fisher_yates_permutation(10, 42);
let p2 = fisher_yates_permutation(10, 42);
assert_eq!(p1, p2);
let mut sorted = p1.clone();
sorted.sort();
assert_eq!(sorted, (0..10).collect::<Vec<_>>());
}
#[test]
fn fisher_yates_different_seeds_differ() {
let p1 = fisher_yates_permutation(20, 1);
let p2 = fisher_yates_permutation(20, 2);
assert_ne!(p1, p2);
}
#[test]
fn halton_emits_count_tuples_in_deterministic_order() {
let (tuples, sizes) = lex_3x3();
let result_a = apply_order(tuples.clone(), &sizes,
&TraversalOrder::Halton { count: Some(4) }).unwrap();
let result_b = apply_order(tuples, &sizes,
&TraversalOrder::Halton { count: Some(4) }).unwrap();
assert_eq!(result_a.len(), 4);
assert_eq!(result_a, result_b);
let unique: std::collections::HashSet<_> = result_a.iter()
.map(|t| t.iter().map(|(_, v)| format!("{v:?}"))
.collect::<Vec<_>>())
.collect();
assert_eq!(unique.len(), 4, "duplicates in halton emission");
}
#[test]
fn halton_count_none_emits_every_tuple() {
let (tuples, sizes) = lex_3x3();
let result = apply_order(tuples.clone(), &sizes,
&TraversalOrder::Halton { count: None }).unwrap();
assert_eq!(result.len(), tuples.len());
for orig in &tuples {
assert!(result.contains(orig),
"halton dropped tuple {orig:?}");
}
}
#[test]
fn halton_count_larger_than_set_emits_all_and_stops() {
let (tuples, sizes) = lex_3x3();
let total = tuples.len();
let result = apply_order(tuples, &sizes,
&TraversalOrder::Halton { count: Some(total + 100) }).unwrap();
assert_eq!(result.len(), total);
}
#[test]
fn halton_two_dim_covers_better_than_lex() {
let mut tuples: Vec<Tuple> = Vec::new();
for i in 0..5 {
for j in 0..5 {
tuples.push(vec![
("x".to_string(), Value::U64(i)),
("y".to_string(), Value::U64(j)),
]);
}
}
let halton_4 = apply_order(tuples.clone(), &[5, 5],
&TraversalOrder::Halton { count: Some(4) }).unwrap();
assert_eq!(halton_4.len(), 4);
let xs: std::collections::HashSet<_> = halton_4.iter()
.map(|t| match t[0].1 { Value::U64(n) => n, _ => 999 })
.collect();
assert!(xs.len() >= 2,
"halton/4 should cover at least 2 distinct x rows in a 5×5 grid; got xs={xs:?}");
}
#[test]
fn halton_value_radical_inverse_is_correct() {
assert!((halton_value(1, 2) - 0.5).abs() < 1e-12);
assert!((halton_value(2, 2) - 0.25).abs() < 1e-12);
assert!((halton_value(3, 2) - 0.75).abs() < 1e-12);
assert!((halton_value(4, 2) - 0.125).abs() < 1e-12);
assert!((halton_value(1, 3) - (1.0/3.0)).abs() < 1e-12);
assert!((halton_value(2, 3) - (2.0/3.0)).abs() < 1e-12);
assert!((halton_value(3, 3) - (1.0/9.0)).abs() < 1e-12);
}
#[test]
fn apply_order_rejects_lattice_size_mismatch_for_index_space_strategies() {
let tuples: Vec<Tuple> = (1..=3).map(|x|
tuple(&[("x", x), ("y", x * 10)])
).collect();
let sizes = vec![3, 3];
for ord in [
TraversalOrder::ReverseLex { count: None },
TraversalOrder::Diagonal { count: None },
TraversalOrder::Antidiagonal { count: None },
TraversalOrder::Extrema { strata: None },
TraversalOrder::Shells { origin: ShellOrigin::Outer, depth: None },
TraversalOrder::Halton { count: None },
TraversalOrder::Lhs { count: None, seed: None },
] {
let err = apply_order(tuples.clone(), &sizes, &ord).unwrap_err();
assert!(err.contains("lattice product"),
"{ord:?}: should reject mismatch — got: {err}");
}
}
#[test]
fn apply_order_lex_passes_through_mismatched_sizes() {
let tuples: Vec<Tuple> = (1..=3).map(|x|
tuple(&[("x", x), ("y", x * 10)])
).collect();
let result = apply_order(tuples.clone(), &[3, 3],
&TraversalOrder::Lex { count: None }).unwrap();
assert_eq!(result.len(), 3);
}
}