Vendor dependencies

This commit is contained in:
2026-08-01 16:11:49 +03:00
parent 7f139a0241
commit 6b5e7f0f8b
29706 changed files with 9575646 additions and 0 deletions
+73
View File
@@ -0,0 +1,73 @@
// This file is part of ICU4X. For terms of use, please see the file
// called LICENSE at the top level of the ICU4X source tree
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
use postcard::ser_flavors::{AllocVec, Flavor};
use serde::Serialize;
use zerotrie::ZeroTriePerfectHash;
use zerotrie::ZeroTrieSimpleAscii;
use zerovec::ZeroMap;
mod testdata {
include!("data/data.rs");
}
#[test]
fn test_basic() {
let bytes_ascii = testdata::basic::TRIE_ASCII;
let data_ascii = testdata::basic::DATA_ASCII;
let trie_ascii = ZeroTrieSimpleAscii::from_bytes(bytes_ascii);
let trie_phf_ascii = ZeroTriePerfectHash::from_bytes(bytes_ascii);
let bytes_unicode = testdata::basic::TRIE_UNICODE;
let data_unicode = testdata::basic::DATA_UNICODE;
let trie_phf_unicode = ZeroTriePerfectHash::from_bytes(bytes_unicode);
let bytes_binary = testdata::basic::TRIE_BINARY;
let data_binary = testdata::basic::DATA_BINARY;
let trie_phf_binary = ZeroTriePerfectHash::from_bytes(bytes_binary);
// Check that the getter works
for (key, expected) in data_ascii {
let actual = match trie_ascii.get(key) {
Some(v) => v,
None => panic!("value should be in trie: {key:?} => {expected}"),
};
assert_eq!(*expected, actual);
let actual = match trie_phf_ascii.get(key) {
Some(v) => v,
None => panic!("value should be in trie6: {key:?} => {expected}"),
};
assert_eq!(*expected, actual);
}
for (key, expected) in data_unicode {
let actual_unicode = match trie_phf_unicode.get(key) {
Some(v) => v,
None => panic!("value should be in trie6: {key:?} => {expected}"),
};
assert_eq!(*expected, actual_unicode);
}
for (key, expected) in data_binary {
let actual_bin6 = match trie_phf_binary.get(key) {
Some(v) => v,
None => panic!("value should be in trie6: {key:?} => {expected}"),
};
assert_eq!(*expected, actual_bin6);
}
// Compare the size to a postcard ZeroMap
let zm: ZeroMap<[u8], u32> = data_ascii.iter().map(|(a, b)| (*a, *b as u32)).collect();
let mut serializer = postcard::Serializer {
output: AllocVec::new(),
};
Serialize::serialize(&zm, &mut serializer).unwrap();
let zeromap_bytes = serializer
.output
.finalize()
.expect("Failed to finalize serializer output");
assert_eq!(26, bytes_ascii.len());
assert_eq!(77, zeromap_bytes.len());
}
+855
View File
@@ -0,0 +1,855 @@
// This file is part of ICU4X. For terms of use, please see the file
// called LICENSE at the top level of the ICU4X source tree
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
use litemap::LiteMap;
use zerotrie::ZeroTriePerfectHash;
use zerotrie::ZeroTrieSimpleAscii;
mod testdata {
include!("data/data.rs");
}
use testdata::strings_to_litemap;
const NON_EXISTENT_STRINGS: &[&str] = &[
"a9PS", "ahsY", "ahBO", "a8IN", "xk8o", "xv1l", "xI2S", "618y", "d6My", "uszy",
];
macro_rules! assert_bytes_eq {
($len:literal, $a:expr, $b:expr) => {
assert_eq!($len, $a.len());
assert_eq!($a, $b);
};
}
fn check_simple_ascii_trie<S>(items: &LiteMap<&[u8], usize>, trie: &ZeroTrieSimpleAscii<S>)
where
S: AsRef<[u8]> + ?Sized,
{
// Check that each item is in the trie
for (k, v) in items.iter() {
assert_eq!(trie.get(k), Some(*v));
}
// Check that some items are not in the trie
for s in NON_EXISTENT_STRINGS.iter() {
assert_eq!(trie.get(s.as_bytes()), None);
}
// Check that the iterator returns items in the same order as the LiteMap
assert!(items
.iter()
.map(|(s, v)| (String::from_utf8(s.to_vec()).unwrap(), *v))
.eq(trie.iter()));
// Check that the const builder works
let const_trie = ZeroTrieSimpleAscii::try_from_litemap_with_const_builder(items).unwrap();
assert_eq!(trie.as_bytes(), const_trie.as_bytes());
}
fn check_phf_ascii_trie<S>(items: &LiteMap<&[u8], usize>, trie: &ZeroTriePerfectHash<S>)
where
S: AsRef<[u8]> + ?Sized,
{
// Check that each item is in the trie
for (k, v) in items.iter() {
assert_eq!(trie.get(k), Some(*v));
}
// Check that some items are not in the trie
for s in NON_EXISTENT_STRINGS.iter() {
assert_eq!(trie.get(s.as_bytes()), None);
}
// Check that the iterator returns the contents of the LiteMap
// Note: Since the items might not be in order, we collect them into a new LiteMap
let recovered_items: LiteMap<_, _> = trie.iter().collect();
assert_eq!(
items.to_borrowed_keys_values::<[u8], usize, Vec<_>>(),
recovered_items.to_borrowed_keys_values()
);
}
fn check_phf_bytes_trie<S>(items: &LiteMap<&[u8], usize>, trie: &ZeroTriePerfectHash<S>)
where
S: AsRef<[u8]> + ?Sized,
{
// Check that each item is in the trie
for (k, v) in items.iter() {
assert_eq!(trie.get(k), Some(*v), "{k:?}");
}
// Check that some items are not in the trie
for s in NON_EXISTENT_STRINGS.iter() {
assert_eq!(trie.get(s.as_bytes()), None, "{s:?}");
}
// Check that the iterator returns the contents of the LiteMap
// Note: Since the items might not be in order, we collect them into a new LiteMap
let recovered_items: LiteMap<_, _> = trie.iter().collect();
assert_eq!(
items.to_borrowed_keys_values::<[u8], usize, Vec<_>>(),
recovered_items.to_borrowed_keys_values()
);
}
#[test]
fn test_basic() {
let lm1a: LiteMap<&[u8], usize> = testdata::basic::DATA_ASCII.iter().copied().collect();
let lm1b: LiteMap<&[u8], usize> = lm1a.to_borrowed_keys();
let lm2: LiteMap<&[u8], usize> = testdata::basic::DATA_UNICODE.iter().copied().collect();
let lm3: LiteMap<&[u8], usize> = testdata::basic::DATA_BINARY.iter().copied().collect();
let expected_bytes = testdata::basic::TRIE_ASCII;
let trie = ZeroTrieSimpleAscii::try_from(&lm1a).unwrap();
assert_bytes_eq!(26, trie.as_bytes(), expected_bytes);
check_simple_ascii_trie(&lm1a, &trie);
let trie = ZeroTriePerfectHash::try_from(&lm1b).unwrap();
assert_bytes_eq!(26, trie.as_bytes(), expected_bytes);
check_phf_ascii_trie(&lm1a, &trie);
let expected_bytes = testdata::basic::TRIE_UNICODE;
let trie = ZeroTriePerfectHash::try_from(&lm2).unwrap();
assert_bytes_eq!(39, trie.as_bytes(), expected_bytes);
check_phf_bytes_trie(&lm2, &trie);
let expected_bytes = testdata::basic::TRIE_BINARY;
let trie = ZeroTriePerfectHash::try_from(&lm3).unwrap();
assert_bytes_eq!(26, trie.as_bytes(), expected_bytes);
check_phf_bytes_trie(&lm3, &trie);
}
#[test]
fn test_empty() {
let trie = ZeroTrieSimpleAscii::try_from(&LiteMap::<&[u8], usize>::new_vec()).unwrap();
assert_eq!(trie.byte_len(), 0);
assert!(trie.is_empty());
assert_eq!(trie.get(b""), None);
assert_eq!(trie.as_bytes(), &[] as &[u8]);
}
#[test]
fn test_single_empty_value() {
let litemap: LiteMap<&[u8], usize> = [
(&b""[..], 10), //
]
.into_iter()
.collect();
let trie = ZeroTrieSimpleAscii::try_from(&litemap.as_sliced()).unwrap();
assert_eq!(trie.get(b""), Some(10));
assert_eq!(trie.get(b"x"), None);
let expected_bytes = &[0b10001010];
assert_eq!(trie.as_bytes(), expected_bytes);
let litemap_bytes = litemap.to_borrowed_keys::<[u8], Vec<_>>();
let trie_phf = ZeroTriePerfectHash::try_from(&litemap_bytes).unwrap();
assert_bytes_eq!(1, trie_phf.as_bytes(), expected_bytes);
check_phf_ascii_trie(&litemap, &trie_phf);
}
#[test]
fn test_single_byte_string() {
let litemap: LiteMap<&[u8], usize> = [
(&b"x"[..], 10), //
]
.into_iter()
.collect();
let trie = ZeroTrieSimpleAscii::try_from(&litemap.as_sliced()).unwrap();
assert_eq!(trie.get(b""), None);
assert_eq!(trie.get(b"xy"), None);
check_simple_ascii_trie(&litemap, &trie);
let expected_bytes = &[b'x', 0b10001010];
assert_bytes_eq!(2, trie.as_bytes(), expected_bytes);
let litemap_bytes = litemap.to_borrowed_keys::<[u8], Vec<_>>();
let trie_phf = ZeroTriePerfectHash::try_from(&litemap_bytes).unwrap();
assert_bytes_eq!(2, trie_phf.as_bytes(), expected_bytes);
check_phf_ascii_trie(&litemap, &trie_phf);
}
#[test]
fn test_single_string() {
let litemap: LiteMap<&[u8], usize> = [
(&b"xyz"[..], 10), //
]
.into_iter()
.collect();
let trie = ZeroTrieSimpleAscii::try_from(&litemap.as_sliced()).unwrap();
assert_eq!(trie.get(b""), None);
assert_eq!(trie.get(b"x"), None);
assert_eq!(trie.get(b"xy"), None);
assert_eq!(trie.get(b"xyzz"), None);
check_simple_ascii_trie(&litemap, &trie);
let expected_bytes = &[b'x', b'y', b'z', 0b10001010];
assert_bytes_eq!(4, trie.as_bytes(), expected_bytes);
let litemap_bytes = litemap.to_borrowed_keys::<[u8], Vec<_>>();
let trie_phf = ZeroTriePerfectHash::try_from(&litemap_bytes).unwrap();
assert_bytes_eq!(4, trie_phf.as_bytes(), expected_bytes);
check_phf_ascii_trie(&litemap, &trie_phf);
}
#[test]
fn test_prefix_strings() {
let litemap: LiteMap<&[u8], usize> = [(&b"x"[..], 0), (b"xy", 1)].into_iter().collect();
let trie = ZeroTrieSimpleAscii::try_from(&litemap.as_sliced()).unwrap();
assert_eq!(trie.get(b""), None);
assert_eq!(trie.get(b"xyz"), None);
check_simple_ascii_trie(&litemap, &trie);
let expected_bytes = &[b'x', 0b10000000, b'y', 0b10000001];
assert_bytes_eq!(4, trie.as_bytes(), expected_bytes);
let litemap_bytes = litemap.to_borrowed_keys::<[u8], Vec<_>>();
let trie_phf = ZeroTriePerfectHash::try_from(&litemap_bytes).unwrap();
assert_bytes_eq!(4, trie_phf.as_bytes(), expected_bytes);
check_phf_ascii_trie(&litemap, &trie_phf);
}
#[test]
fn test_single_byte_branch() {
let litemap: LiteMap<&[u8], usize> = [(&b"x"[..], 0), (b"y", 1)].into_iter().collect();
let trie = ZeroTrieSimpleAscii::try_from(&litemap.as_sliced()).unwrap();
assert_eq!(trie.get(b""), None);
assert_eq!(trie.get(b"xy"), None);
check_simple_ascii_trie(&litemap, &trie);
let expected_bytes = &[0b11000010, b'x', b'y', 1, 0b10000000, 0b10000001];
assert_bytes_eq!(6, trie.as_bytes(), expected_bytes);
let litemap_bytes = litemap.to_borrowed_keys::<[u8], Vec<_>>();
let trie_phf = ZeroTriePerfectHash::try_from(&litemap_bytes).unwrap();
assert_bytes_eq!(6, trie_phf.as_bytes(), expected_bytes);
check_phf_ascii_trie(&litemap, &trie_phf);
}
#[test]
fn test_multi_byte_branch() {
let litemap: LiteMap<&[u8], usize> = [(&b"axb"[..], 0), (b"ayc", 1)].into_iter().collect();
let trie = ZeroTrieSimpleAscii::try_from(&litemap.as_sliced()).unwrap();
assert_eq!(trie.get(b""), None);
assert_eq!(trie.get(b"a"), None);
assert_eq!(trie.get(b"ax"), None);
assert_eq!(trie.get(b"ay"), None);
check_simple_ascii_trie(&litemap, &trie);
let expected_bytes = &[
b'a', 0b11000010, b'x', b'y', 2, b'b', 0b10000000, b'c', 0b10000001,
];
assert_bytes_eq!(9, trie.as_bytes(), expected_bytes);
let litemap_bytes = litemap.to_borrowed_keys::<[u8], Vec<_>>();
let trie_phf = ZeroTriePerfectHash::try_from(&litemap_bytes).unwrap();
assert_bytes_eq!(9, trie_phf.as_bytes(), expected_bytes);
check_phf_ascii_trie(&litemap, &trie_phf);
}
#[test]
fn test_linear_varint_values() {
let litemap: LiteMap<&[u8], usize> = [(&b""[..], 100), (b"x", 500), (b"xyz", 5000)]
.into_iter()
.collect();
let trie = ZeroTrieSimpleAscii::try_from(&litemap.as_sliced()).unwrap();
assert_eq!(trie.get(b"xy"), None);
assert_eq!(trie.get(b"xz"), None);
assert_eq!(trie.get(b"xyzz"), None);
check_simple_ascii_trie(&litemap, &trie);
let expected_bytes = &[0x90, 0x54, b'x', 0x93, 0x64, b'y', b'z', 0x90, 0x96, 0x78];
assert_bytes_eq!(10, trie.as_bytes(), expected_bytes);
let litemap_bytes = litemap.to_borrowed_keys::<[u8], Vec<_>>();
let trie_phf = ZeroTriePerfectHash::try_from(&litemap_bytes).unwrap();
assert_bytes_eq!(10, trie_phf.as_bytes(), expected_bytes);
check_phf_ascii_trie(&litemap, &trie_phf);
}
#[test]
fn test_bug() {
let litemap: LiteMap<&[u8], usize> = [(&b"abc"[..], 100), (b"abcd", 500), (b"abcde", 5000)]
.into_iter()
.collect();
let trie = ZeroTrieSimpleAscii::try_from(&litemap.as_sliced()).unwrap();
assert_eq!(trie.get(b"ab"), None);
assert_eq!(trie.get(b"abd"), None);
assert_eq!(trie.get(b"abCD"), None);
check_simple_ascii_trie(&litemap, &trie);
let litemap_bytes = litemap.to_borrowed_keys::<[u8], Vec<_>>();
let trie_phf = ZeroTriePerfectHash::try_from(&litemap_bytes).unwrap();
check_phf_ascii_trie(&litemap, &trie_phf);
}
#[test]
fn test_varint_branch() {
let chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
let litemap: LiteMap<&[u8], usize> = (0..chars.len())
.map(|i| (chars.get(i..i + 1).unwrap().as_bytes(), i))
.collect();
let trie = ZeroTrieSimpleAscii::try_from(&litemap.as_sliced()).unwrap();
assert_eq!(trie.get(b""), None);
assert_eq!(trie.get(b"ax"), None);
assert_eq!(trie.get(b"ay"), None);
check_simple_ascii_trie(&litemap, &trie);
#[rustfmt::skip]
let expected_bytes = &[
0b11100000, // branch varint lead
0x14, // branch varint trail
// search array:
b'A', b'B', b'C', b'D', b'E', b'F', b'G', b'H', b'I', b'J',
b'K', b'L', b'M', b'N', b'O', b'P', b'Q', b'R', b'S', b'T',
b'U', b'V', b'W', b'X', b'Y', b'Z',
b'a', b'b', b'c', b'd', b'e', b'f', b'g', b'h', b'i', b'j',
b'k', b'l', b'm', b'n', b'o', b'p', b'q', b'r', b's', b't',
b'u', b'v', b'w', b'x', b'y', b'z',
// offset array:
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20,
22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52,
54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84,
86,
// single-byte values:
0x80, (0x80 | 1), (0x80 | 2), (0x80 | 3), (0x80 | 4),
(0x80 | 5), (0x80 | 6), (0x80 | 7), (0x80 | 8), (0x80 | 9),
(0x80 | 10), (0x80 | 11), (0x80 | 12), (0x80 | 13), (0x80 | 14),
(0x80 | 15),
// multi-byte values:
0x90, 0, 0x90, 1, 0x90, 2, 0x90, 3, 0x90, 4, 0x90, 5,
0x90, 6, 0x90, 7, 0x90, 8, 0x90, 9, 0x90, 10, 0x90, 11,
0x90, 12, 0x90, 13, 0x90, 14, 0x90, 15, 0x90, 16, 0x90, 17,
0x90, 18, 0x90, 19, 0x90, 20, 0x90, 21, 0x90, 22, 0x90, 23,
0x90, 24, 0x90, 25, 0x90, 26, 0x90, 27, 0x90, 28, 0x90, 29,
0x90, 30, 0x90, 31, 0x90, 32, 0x90, 33, 0x90, 34, 0x90, 35,
];
assert_bytes_eq!(193, trie.as_bytes(), expected_bytes);
#[rustfmt::skip]
let expected_bytes = &[
0b11100000, // branch varint lead
0x14, // branch varint trail
// PHF metadata:
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 10, 12, 16, 4, 4, 4, 4, 4, 4, 8,
4, 4, 4, 16, 16, 16, 16, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 7,
// search array:
b'h', b'i', b'j', b'k', b'l', b'm', b'n', b'o',
b'p', b'u', b'v', b'w', b'D', b'E', b'F', b'q',
b'r', b'A', b'B', b'C', b'x', b'y', b'z', b's',
b'H', b'I', b'J', b'G', b'P', b'Q', b'R', b'S',
b'T', b'U', b'V', b'W', b'X', b'Y', b'Z', b'K',
b'L', b'M', b'N', b'O', b'g', b'a', b'b', b'c',
b't', b'd', b'f', b'e',
// offset array:
2, 4, 6, 8, 10, 12, 14,
16, 18, 20, 22, 24, 25, 26, 27,
29, 31, 32, 33, 34, 36, 38, 40,
42, 43, 44, 45, 46, 47, 49, 51,
53, 55, 57, 59, 61, 63, 65, 67,
68, 69, 70, 71, 72, 74, 76, 78,
80, 82, 84, 86,
// values:
0x90, 17, 0x90, 18, 0x90, 19, 0x90, 20, 0x90, 21, 0x90, 22, 0x90, 23,
0x90, 24, 0x90, 25, 0x90, 30, 0x90, 31, 0x90, 32, 0x80 | 3, 0x80 | 4,
0x80 | 5, 0x90, 26, 0x90, 27, 0x80, 0x80 | 1, 0x80 | 2, 0x90, 33,
0x90, 34, 0x90, 35, 0x90, 28, 0x80 | 7, 0x80 | 8, 0x80 | 9, 0x80 | 6,
0x80 | 15, 0x90, 0, 0x90, 1, 0x90, 2, 0x90, 3, 0x90, 4, 0x90, 5,
0x90, 6, 0x90, 7, 0x90, 8, 0x90, 9, 0x80 | 10, 0x80 | 11, 0x80 | 12,
0x80 | 13, 0x80 | 14, 0x90, 16, 0x90, 10, 0x90, 11, 0x90, 12, 0x90, 29,
0x90, 13, 0x90, 15, 0x90, 14,
];
let litemap_bytes = litemap.to_borrowed_keys::<[u8], Vec<_>>();
let trie_phf = ZeroTriePerfectHash::try_from(&litemap_bytes).unwrap();
assert_bytes_eq!(246, trie_phf.as_bytes(), expected_bytes);
check_phf_ascii_trie(&litemap, &trie_phf);
}
#[test]
fn test_below_wide() {
let litemap: LiteMap<&[u8], usize> = [
(&b"abcdefghijklmnopqrstuvwxyz"[..], 1),
(b"bcdefghijklmnopqrstuvwxyza", 2),
(b"cdefghijklmnopqrstuvwxyzab", 3),
(b"defghijklmnopqrstuvwxyzabc", 4),
(b"efghijklmnopqrstuvwxyzabcd", 5),
(b"fghijklmnopqrstuvwxyzabcde", 6),
(b"ghijklmnopqrstuvwxyzabcdef", 7),
(b"hijklmnopqrstuvwxyzabcdefg", 8),
(b"ijklmnopqrstuvwxyzabcdefgh", 9),
(b"jklmnopqrstuvwxyzabcd", 10),
]
.into_iter()
.collect();
let trie = ZeroTrieSimpleAscii::try_from(&litemap.as_sliced()).unwrap();
assert_eq!(trie.get(b""), None);
assert_eq!(trie.get(b"abc"), None);
check_simple_ascii_trie(&litemap, &trie);
#[rustfmt::skip]
let expected_bytes = &[
0b11001010, // branch
// search array:
b'a', b'b', b'c', b'd', b'e', b'f', b'g', b'h', b'i', b'j',
// offset array:
26, 52, 78, 104, 130, 156, 182, 208, 234,
// offset data:
b'b', b'c', b'd', b'e', b'f', b'g', b'h', b'i', b'j', b'k', b'l', b'm', b'n',
b'o', b'p', b'q', b'r', b's', b't', b'u', b'v', b'w', b'x', b'y', b'z',
0x81,
b'c', b'd', b'e', b'f', b'g', b'h', b'i', b'j', b'k', b'l', b'm', b'n', b'o',
b'p', b'q', b'r', b's', b't', b'u', b'v', b'w', b'x', b'y', b'z', b'a',
0x82,
b'd', b'e', b'f', b'g', b'h', b'i', b'j', b'k', b'l', b'm', b'n', b'o', b'p',
b'q', b'r', b's', b't', b'u', b'v', b'w', b'x', b'y', b'z', b'a', b'b',
0x83,
b'e', b'f', b'g', b'h', b'i', b'j', b'k', b'l', b'm', b'n', b'o', b'p', b'q',
b'r', b's', b't', b'u', b'v', b'w', b'x', b'y', b'z', b'a', b'b', b'c',
0x84,
b'f', b'g', b'h', b'i', b'j', b'k', b'l', b'm', b'n', b'o', b'p', b'q', b'r',
b's', b't', b'u', b'v', b'w', b'x', b'y', b'z', b'a', b'b', b'c', b'd',
0x85,
b'g', b'h', b'i', b'j', b'k', b'l', b'm', b'n', b'o', b'p', b'q', b'r', b's',
b't', b'u', b'v', b'w', b'x', b'y', b'z', b'a', b'b', b'c', b'd', b'e',
0x86,
b'h', b'i', b'j', b'k', b'l', b'm', b'n', b'o', b'p', b'q', b'r', b's', b't',
b'u', b'v', b'w', b'x', b'y', b'z', b'a', b'b', b'c', b'd', b'e', b'f',
0x87,
b'i', b'j', b'k', b'l', b'm', b'n', b'o', b'p', b'q', b'r', b's', b't', b'u',
b'v', b'w', b'x', b'y', b'z', b'a', b'b', b'c', b'd', b'e', b'f', b'g',
0x88,
b'j', b'k', b'l', b'm', b'n', b'o', b'p', b'q', b'r', b's', b't', b'u', b'v',
b'w', b'x', b'y', b'z', b'a', b'b', b'c', b'd', b'e', b'f', b'g', b'h',
0x89,
b'k', b'l', b'm', b'n', b'o', b'p', b'q', b'r', b's', b't', b'u', b'v', b'w',
b'x', b'y', b'z', b'a', b'b', b'c', b'd',
0x8A,
];
assert_bytes_eq!(275, trie.as_bytes(), expected_bytes);
}
#[test]
fn test_at_wide() {
let litemap: LiteMap<&[u8], usize> = [
(&b"abcdefghijklmnopqrstuvwxyz"[..], 1),
(b"bcdefghijklmnopqrstuvwxyza", 2),
(b"cdefghijklmnopqrstuvwxyzab", 3),
(b"defghijklmnopqrstuvwxyzabc", 4),
(b"efghijklmnopqrstuvwxyzabcd", 5),
(b"fghijklmnopqrstuvwxyzabcde", 6),
(b"ghijklmnopqrstuvwxyzabcdef", 7),
(b"hijklmnopqrstuvwxyzabcdefg", 8),
(b"ijklmnopqrstuvwxyzabcdefgh", 9),
(b"jklmnopqrstuvwxyzabcde", 10),
]
.into_iter()
.collect();
let trie = ZeroTrieSimpleAscii::try_from(&litemap.as_sliced()).unwrap();
assert_eq!(trie.get(b""), None);
assert_eq!(trie.get(b"abc"), None);
check_simple_ascii_trie(&litemap, &trie);
#[rustfmt::skip]
let expected_bytes = &[
0b11100001, // branch lead
0x6A, // branch trail
// search array:
b'a', b'b', b'c', b'd', b'e', b'f', b'g', b'h', b'i', b'j',
// offset array (wide):
0, 0, 0, 0, 0, 0, 0, 0, 0,
26, 52, 78, 104, 130, 156, 182, 208, 234,
// offset data:
b'b', b'c', b'd', b'e', b'f', b'g', b'h', b'i', b'j', b'k', b'l', b'm', b'n',
b'o', b'p', b'q', b'r', b's', b't', b'u', b'v', b'w', b'x', b'y', b'z',
0x81,
b'c', b'd', b'e', b'f', b'g', b'h', b'i', b'j', b'k', b'l', b'm', b'n', b'o',
b'p', b'q', b'r', b's', b't', b'u', b'v', b'w', b'x', b'y', b'z', b'a',
0x82,
b'd', b'e', b'f', b'g', b'h', b'i', b'j', b'k', b'l', b'm', b'n', b'o', b'p',
b'q', b'r', b's', b't', b'u', b'v', b'w', b'x', b'y', b'z', b'a', b'b',
0x83,
b'e', b'f', b'g', b'h', b'i', b'j', b'k', b'l', b'm', b'n', b'o', b'p', b'q',
b'r', b's', b't', b'u', b'v', b'w', b'x', b'y', b'z', b'a', b'b', b'c',
0x84,
b'f', b'g', b'h', b'i', b'j', b'k', b'l', b'm', b'n', b'o', b'p', b'q', b'r',
b's', b't', b'u', b'v', b'w', b'x', b'y', b'z', b'a', b'b', b'c', b'd',
0x85,
b'g', b'h', b'i', b'j', b'k', b'l', b'm', b'n', b'o', b'p', b'q', b'r', b's',
b't', b'u', b'v', b'w', b'x', b'y', b'z', b'a', b'b', b'c', b'd', b'e',
0x86,
b'h', b'i', b'j', b'k', b'l', b'm', b'n', b'o', b'p', b'q', b'r', b's', b't',
b'u', b'v', b'w', b'x', b'y', b'z', b'a', b'b', b'c', b'd', b'e', b'f',
0x87,
b'i', b'j', b'k', b'l', b'm', b'n', b'o', b'p', b'q', b'r', b's', b't', b'u',
b'v', b'w', b'x', b'y', b'z', b'a', b'b', b'c', b'd', b'e', b'f', b'g',
0x88,
b'j', b'k', b'l', b'm', b'n', b'o', b'p', b'q', b'r', b's', b't', b'u', b'v',
b'w', b'x', b'y', b'z', b'a', b'b', b'c', b'd', b'e', b'f', b'g', b'h',
0x89,
b'k', b'l', b'm', b'n', b'o', b'p', b'q', b'r', b's', b't', b'u', b'v', b'w',
b'x', b'y', b'z', b'a', b'b', b'c', b'd', b'e',
0x8A,
];
assert_bytes_eq!(286, trie.as_bytes(), expected_bytes);
}
#[test]
fn test_at_wide_plus() {
let litemap: LiteMap<&[u8], usize> = [
(&b"abcdefghijklmnopqrstuvwxyz"[..], 1),
(b"bcdefghijklmnopqrstuvwxyza", 2),
(b"cdefghijklmnopqrstuvwxyzab", 3),
(b"defghijklmnopqrstuvwxyzabc", 4),
(b"efghijklmnopqrstuvwxyzabcd", 5),
(b"fghijklmnopqrstuvwxyzabcde", 6),
(b"ghijklmnopqrstuvwxyzabcdef", 7),
(b"hijklmnopqrstuvwxyzabcdefg", 8),
(b"ijklmnopqrstuvwxyzabcdefgh", 9),
(b"jklmnopqrstuvwxyzabcdef", 10),
]
.into_iter()
.collect();
let trie = ZeroTrieSimpleAscii::try_from(&litemap.as_sliced()).unwrap();
assert_eq!(trie.get(b""), None);
assert_eq!(trie.get(b"abc"), None);
check_simple_ascii_trie(&litemap, &trie);
#[rustfmt::skip]
let expected_bytes = &[
0b11100001, // branch lead
0x6A, // branch trail
// search array:
b'a', b'b', b'c', b'd', b'e', b'f', b'g', b'h', b'i', b'j',
// offset array (wide):
0, 0, 0, 0, 0, 0, 0, 0, 0,
26, 52, 78, 104, 130, 156, 182, 208, 234,
// offset data:
b'b', b'c', b'd', b'e', b'f', b'g', b'h', b'i', b'j', b'k', b'l', b'm', b'n',
b'o', b'p', b'q', b'r', b's', b't', b'u', b'v', b'w', b'x', b'y', b'z',
0x81,
b'c', b'd', b'e', b'f', b'g', b'h', b'i', b'j', b'k', b'l', b'm', b'n', b'o',
b'p', b'q', b'r', b's', b't', b'u', b'v', b'w', b'x', b'y', b'z', b'a',
0x82,
b'd', b'e', b'f', b'g', b'h', b'i', b'j', b'k', b'l', b'm', b'n', b'o', b'p',
b'q', b'r', b's', b't', b'u', b'v', b'w', b'x', b'y', b'z', b'a', b'b',
0x83,
b'e', b'f', b'g', b'h', b'i', b'j', b'k', b'l', b'm', b'n', b'o', b'p', b'q',
b'r', b's', b't', b'u', b'v', b'w', b'x', b'y', b'z', b'a', b'b', b'c',
0x84,
b'f', b'g', b'h', b'i', b'j', b'k', b'l', b'm', b'n', b'o', b'p', b'q', b'r',
b's', b't', b'u', b'v', b'w', b'x', b'y', b'z', b'a', b'b', b'c', b'd',
0x85,
b'g', b'h', b'i', b'j', b'k', b'l', b'm', b'n', b'o', b'p', b'q', b'r', b's',
b't', b'u', b'v', b'w', b'x', b'y', b'z', b'a', b'b', b'c', b'd', b'e',
0x86,
b'h', b'i', b'j', b'k', b'l', b'm', b'n', b'o', b'p', b'q', b'r', b's', b't',
b'u', b'v', b'w', b'x', b'y', b'z', b'a', b'b', b'c', b'd', b'e', b'f',
0x87,
b'i', b'j', b'k', b'l', b'm', b'n', b'o', b'p', b'q', b'r', b's', b't', b'u',
b'v', b'w', b'x', b'y', b'z', b'a', b'b', b'c', b'd', b'e', b'f', b'g',
0x88,
b'j', b'k', b'l', b'm', b'n', b'o', b'p', b'q', b'r', b's', b't', b'u', b'v',
b'w', b'x', b'y', b'z', b'a', b'b', b'c', b'd', b'e', b'f', b'g', b'h',
0x89,
b'k', b'l', b'm', b'n', b'o', b'p', b'q', b'r', b's', b't', b'u', b'v', b'w',
b'x', b'y', b'z', b'a', b'b', b'c', b'd', b'e', b'f',
0x8A,
];
assert_bytes_eq!(287, trie.as_bytes(), expected_bytes);
}
#[test]
fn test_everything() {
let litemap: LiteMap<&[u8], usize> = [
(&b""[..], 0),
(b"axb", 100),
(b"ayc", 2),
(b"azd", 3),
(b"bxe", 4),
(b"bxefg", 500),
(b"bxefh", 6),
(b"bxei", 7),
(b"bxeikl", 8),
]
.into_iter()
.collect();
let trie = ZeroTrieSimpleAscii::try_from(&litemap.as_sliced()).unwrap();
assert_eq!(trie.get(b""), Some(0));
assert_eq!(trie.get(b"a"), None);
assert_eq!(trie.get(b"ax"), None);
assert_eq!(trie.get(b"ay"), None);
check_simple_ascii_trie(&litemap, &trie);
let expected_bytes = &[
0b10000000, // value 0
0b11000010, // branch of 2
b'a', //
b'b', //
13, //
0b11000011, // branch of 3
b'x', //
b'y', //
b'z', //
3, //
5, //
b'b', //
0b10010000, // value 100 (lead)
0x54, // value 100 (trail)
b'c', //
0b10000010, // value 2
b'd', //
0b10000011, // value 3
b'x', //
b'e', //
0b10000100, // value 4
0b11000010, // branch of 2
b'f', //
b'i', //
7, //
0b11000010, // branch of 2
b'g', //
b'h', //
2, //
0b10010011, // value 500 (lead)
0x64, // value 500 (trail)
0b10000110, // value 6
0b10000111, // value 7
b'k', //
b'l', //
0b10001000, // value 8
];
assert_bytes_eq!(36, trie.as_bytes(), expected_bytes);
#[rustfmt::skip]
let expected_bytes = &[
0b10000000, // value 0
0b11000010, // branch of 2
b'a', //
b'b', //
13, //
0b11000011, // start of 'a' subtree: branch of 3
b'x', //
b'y', //
b'z', //
3, //
5, //
b'b', //
0b10010000, // value 100 (lead)
0x54, // value 100 (trail)
b'c', //
0b10000010, // value 2
b'd', //
0b10000011, // value 3
b'x', // start of 'b' subtree
b'e', //
0b10000100, // value 4
0b11000010, // branch of 2
b'f', //
b'i', //
7, //
0b11000010, // branch of 2
b'g', //
b'h', //
2, //
0b10010011, // value 500 (lead)
0x64, // value 500 (trail)
0b10000110, // value 6
0b10000111, // value 7
b'k', //
b'l', //
0b10001000, // value 8
];
let litemap_bytes = litemap.to_borrowed_keys::<[u8], Vec<_>>();
let trie_phf = ZeroTriePerfectHash::try_from(&litemap_bytes).unwrap();
assert_bytes_eq!(36, trie_phf.as_bytes(), expected_bytes);
check_phf_ascii_trie(&litemap, &trie_phf);
let zhm: zerovec::ZeroMap<[u8], u32> = litemap.iter().map(|(a, b)| (*a, *b as u32)).collect();
let zhm_buf = postcard::to_allocvec(&zhm).unwrap();
assert_eq!(zhm_buf.len(), 88);
let zhm: zerovec::ZeroMap<[u8], u8> = litemap.iter().map(|(a, b)| (*a, *b as u8)).collect();
let zhm_buf = postcard::to_allocvec(&zhm).unwrap();
assert_eq!(zhm_buf.len(), 61);
let zhm: zerovec::ZeroHashMap<[u8], u32> =
litemap.iter().map(|(a, b)| (*a, *b as u32)).collect();
let zhm_buf = postcard::to_allocvec(&zhm).unwrap();
assert_eq!(zhm_buf.len(), 161);
let zhm: zerovec::ZeroHashMap<[u8], u8> = litemap.iter().map(|(a, b)| (*a, *b as u8)).collect();
let zhm_buf = postcard::to_allocvec(&zhm).unwrap();
assert_eq!(zhm_buf.len(), 134);
}
macro_rules! utf8_byte {
($ch:expr, $i:literal) => {{
let mut utf8_encoder_buf = [0u8; 4];
$ch.encode_utf8(&mut utf8_encoder_buf);
utf8_encoder_buf[$i]
}};
}
#[test]
fn test_non_ascii() {
let litemap: LiteMap<&[u8], usize> = [
("".as_bytes(), 0),
("axb".as_bytes(), 100),
("ayc".as_bytes(), 2),
("azd".as_bytes(), 3),
("bxe".as_bytes(), 4),
("bxefg".as_bytes(), 500),
("bxefh".as_bytes(), 6),
("bxei".as_bytes(), 7),
("bxeikl".as_bytes(), 8),
("bxeiklmΚαλημέρααα".as_bytes(), 9),
("bxeiklmαnλo".as_bytes(), 10),
("bxeiklmη".as_bytes(), 11),
]
.into_iter()
.collect();
#[rustfmt::skip]
let expected_bytes = &[
0b10000000, // value 0
0b11000010, // branch of 2
b'a', //
b'b', //
13, //
0b11000011, // start of 'a' subtree: branch of 3
b'x', //
b'y', //
b'z', //
3, //
5, //
b'b', //
0b10010000, // value 100 (lead)
0x54, // value 100 (trail)
b'c', //
0b10000010, // value 2
b'd', //
0b10000011, // value 3
b'x', // start of 'b' subtree
b'e', //
0b10000100, // value 4
0b11000010, // branch of 2
b'f', //
b'i', //
7, //
0b11000010, // branch of 2
b'g', //
b'h', //
2, //
0b10010011, // value 500 (lead)
0x64, // value 500 (trail)
0b10000110, // value 6
0b10000111, // value 7
b'k', //
b'l', //
0b10001000, // value 8
b'm', //
0b10100001, // span of length 1
utf8_byte!('Κ', 0), // NOTE: all three letters have the same lead byte
0b11000011, // branch of 3
utf8_byte!('Κ', 1),
utf8_byte!('α', 1),
utf8_byte!('η', 1),
21,
27,
0b10110000, // span of length 18 (lead)
0b00000010, // span of length 18 (trail)
utf8_byte!('α', 0),
utf8_byte!('α', 1),
utf8_byte!('λ', 0),
utf8_byte!('λ', 1),
utf8_byte!('η', 0),
utf8_byte!('η', 1),
utf8_byte!('μ', 0),
utf8_byte!('μ', 1),
utf8_byte!('έ', 0),
utf8_byte!('έ', 1),
utf8_byte!('ρ', 0),
utf8_byte!('ρ', 1),
utf8_byte!('α', 0),
utf8_byte!('α', 1),
utf8_byte!('α', 0),
utf8_byte!('α', 1),
utf8_byte!('α', 0),
utf8_byte!('α', 1),
0b10001001, // value 9
b'n',
0b10100010, // span of length 2
utf8_byte!('λ', 0),
utf8_byte!('λ', 1),
b'o',
0b10001010, // value 10
0b10001011, // value 11
];
let trie_phf = ZeroTriePerfectHash::try_from(&litemap).unwrap();
assert_bytes_eq!(73, trie_phf.as_bytes(), expected_bytes);
check_phf_bytes_trie(&litemap, &trie_phf);
}
#[test]
fn test_max_branch() {
// Evaluate a branch with all 256 possible children
let mut litemap: LiteMap<&[u8], usize> = LiteMap::new_vec();
let all_bytes: Vec<u8> = (u8::MIN..=u8::MAX).collect();
assert_eq!(all_bytes.len(), 256);
let all_bytes_prefixed: Vec<[u8; 2]> = (u8::MIN..=u8::MAX).map(|x| [b'\0', x]).collect();
for b in all_bytes.iter() {
litemap.insert(core::slice::from_ref(b), *b as usize);
}
for s in all_bytes_prefixed.iter() {
litemap.insert(s, s[1] as usize);
}
let trie_phf = ZeroTriePerfectHash::try_from(&litemap).unwrap();
assert_eq!(trie_phf.byte_len(), 3042);
check_phf_bytes_trie(&litemap, &trie_phf);
}
#[test]
fn test_short_subtags_10pct() {
let litemap = strings_to_litemap(testdata::short_subtags_10pct::STRINGS);
let trie = ZeroTrieSimpleAscii::try_from(&litemap).unwrap();
assert_eq!(trie.byte_len(), 1050);
check_simple_ascii_trie(&litemap, &trie);
let litemap_bytes = litemap.to_borrowed_keys::<[u8], Vec<_>>();
let trie_phf = ZeroTriePerfectHash::try_from(&litemap_bytes).unwrap();
assert_eq!(trie_phf.byte_len(), 1100);
check_phf_ascii_trie(&litemap, &trie_phf);
let zhm: zerovec::ZeroMap<[u8], u32> = litemap.iter().map(|(a, b)| (*a, *b as u32)).collect();
let zhm_buf = postcard::to_allocvec(&zhm).unwrap();
assert_eq!(zhm_buf.len(), 1890);
let zhm: zerovec::ZeroMap<[u8], u8> = litemap.iter().map(|(a, b)| (*a, *b as u8)).collect();
let zhm_buf = postcard::to_allocvec(&zhm).unwrap();
assert_eq!(zhm_buf.len(), 1326);
let zhm: zerovec::ZeroHashMap<[u8], u32> =
litemap.iter().map(|(a, b)| (*a, *b as u32)).collect();
let zhm_buf = postcard::to_allocvec(&zhm).unwrap();
assert_eq!(zhm_buf.len(), 3396);
let zhm: zerovec::ZeroHashMap<[u8], u8> = litemap.iter().map(|(a, b)| (*a, *b as u8)).collect();
let zhm_buf = postcard::to_allocvec(&zhm).unwrap();
assert_eq!(zhm_buf.len(), 2832);
}
#[test]
fn test_short_subtags() {
let litemap = strings_to_litemap(testdata::short_subtags::STRINGS);
let trie = ZeroTrieSimpleAscii::try_from(&litemap).unwrap();
assert_eq!(trie.byte_len(), 8793);
check_simple_ascii_trie(&litemap, &trie);
let litemap_bytes = litemap.to_borrowed_keys::<[u8], Vec<_>>();
let trie_phf = ZeroTriePerfectHash::try_from(&litemap_bytes).unwrap();
assert_eq!(trie_phf.byte_len(), 9400);
check_phf_ascii_trie(&litemap, &trie_phf);
let zm: zerovec::ZeroMap<[u8], u32> = litemap.iter().map(|(a, b)| (*a, *b as u32)).collect();
let zhm_buf = postcard::to_allocvec(&zm).unwrap();
assert_eq!(zhm_buf.len(), 18931);
let zm: zerovec::ZeroMap<[u8], u8> = litemap.iter().map(|(a, b)| (*a, *b as u8)).collect();
let zhm_buf = postcard::to_allocvec(&zm).unwrap();
assert_eq!(zhm_buf.len(), 13300);
let zhm: zerovec::ZeroHashMap<[u8], u32> =
litemap.iter().map(|(a, b)| (*a, *b as u32)).collect();
let zhm_buf = postcard::to_allocvec(&zhm).unwrap();
assert_eq!(zhm_buf.len(), 33949);
let zhm: zerovec::ZeroHashMap<[u8], u8> = litemap.iter().map(|(a, b)| (*a, *b as u8)).collect();
let zhm_buf = postcard::to_allocvec(&zhm).unwrap();
assert_eq!(zhm_buf.len(), 28318);
}
File diff suppressed because it is too large Load Diff
+316
View File
@@ -0,0 +1,316 @@
// This file is part of ICU4X. For terms of use, please see the file
// called LICENSE at the top level of the ICU4X source tree
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
use itertools::Either;
use itertools::Itertools;
use rand::Rng;
use rand::SeedableRng;
use std::collections::BTreeMap;
use zerotrie::dense::ZeroAsciiDenseSparse2dTrieBorrowed;
use zerotrie::dense::ZeroAsciiDenseSparse2dTrieOwned;
use zerotrie::dense::ZeroAsciiDenseSparse2dTrieVarULE;
use zerotrie::ZeroTrieSimpleAscii;
use zerovec::VarZeroCow;
mod testdata {
include!("data/data.rs");
}
const BASIC_DATA: &[&str] = &[
"ar/FR", "ar/IR", "ar/SA", "ar/UK", "ar/US", "en/AU", "en/FR", "en/UK", "en/US", "fr/FR",
"fr/SA", "fr/UK", "fr/US", "it/IT",
];
const NON_EXISTENT_BASIC_DATA: &[&str] = &["ar/IT", "en/ZA", "it/FR", "zh/UK"];
fn strings_to_2d_btreemap<'a>(
strings: &[&'a str],
delimiter: &str,
) -> BTreeMap<&'a str, BTreeMap<&'a str, usize>> {
let mut map = BTreeMap::<&str, BTreeMap<&str, usize>>::default();
for (value, (prefix, suffix)) in strings
.iter()
.flat_map(|s| s.split_once(delimiter))
.enumerate()
{
map.entry(prefix).or_default().insert(suffix, value);
}
map
}
fn check_data(
data: &BTreeMap<&str, BTreeMap<&str, usize>>,
trie: ZeroAsciiDenseSparse2dTrieBorrowed,
polarity: bool,
) {
for (prefix, values) in data.iter() {
for (suffix, value) in values.iter() {
if polarity {
assert_eq!(trie.get(prefix, suffix), Some(*value), "{prefix}/{suffix}");
} else {
assert_eq!(trie.get(prefix, suffix), None, "{prefix}/{suffix}");
}
}
}
}
#[must_use]
fn check_encoding(trie: ZeroAsciiDenseSparse2dTrieBorrowed) -> usize {
let ule =
VarZeroCow::<ZeroAsciiDenseSparse2dTrieVarULE>::from_encodeable(&trie.as_encodeable());
let decoded = ZeroAsciiDenseSparse2dTrieBorrowed::from(&*ule);
assert_eq!(decoded, trie);
ule.as_bytes().len()
}
#[must_use]
fn make_simple_ascii_trie(
data: &BTreeMap<&str, BTreeMap<&str, usize>>,
) -> ZeroTrieSimpleAscii<Vec<u8>> {
let mut data_for_simple_ascii_trie = BTreeMap::new();
for (prefix, values) in data.iter() {
for (suffix, value) in values.iter() {
data_for_simple_ascii_trie.insert(format!("{prefix}/{suffix}"), *value);
}
}
ZeroTrieSimpleAscii::try_from_btree_map_str(&data_for_simple_ascii_trie).unwrap()
}
#[test]
fn test_basic() {
let data_as_map = strings_to_2d_btreemap(BASIC_DATA, "/");
let dense =
ZeroAsciiDenseSparse2dTrieOwned::try_from_btree_map_str(&data_as_map, b'/').unwrap();
check_data(&data_as_map, dense.as_borrowed(), true);
let non_existent_data_as_map = strings_to_2d_btreemap(NON_EXISTENT_BASIC_DATA, "/");
check_data(&non_existent_data_as_map, dense.as_borrowed(), false);
let byte_len = check_encoding(dense.as_borrowed());
assert_eq!(byte_len, 106);
let simple_trie = make_simple_ascii_trie(&data_as_map);
assert_eq!(simple_trie.byte_len(), 71);
}
#[test]
fn test_locales_with_aux() {
let data_as_map = strings_to_2d_btreemap(testdata::locales_with_aux::STRINGS, "-x-");
let dense =
ZeroAsciiDenseSparse2dTrieOwned::try_from_btree_map_str(&data_as_map, b'/').unwrap();
check_data(&data_as_map, dense.as_borrowed(), true);
let byte_len = check_encoding(dense.as_borrowed());
assert_eq!(byte_len, 4045);
let simple_trie = make_simple_ascii_trie(&data_as_map);
assert_eq!(simple_trie.byte_len(), 4614);
}
#[test]
fn test_short_subtags() {
let (prefixes, suffixes): (Vec<&str>, Vec<&str>) = testdata::short_subtags::STRINGS
.iter()
.copied()
.partition_map(|s| match s.strip_prefix("und-") {
Some(suffix) => Either::Right(suffix),
None => Either::Left(s),
});
assert_eq!(prefixes.len(), 1427);
assert_eq!(suffixes.len(), 450);
let mut counter = 0;
let mut rng = rand_pcg::Lcg64Xsh32::seed_from_u64(2025);
let mut data: BTreeMap<&str, BTreeMap<&str, usize>> = BTreeMap::new();
for prefix in prefixes.iter() {
// Two types of keys: big and small. Pick at random with a certain chance of big.
let is_big = rng.random::<f64>() < 0.3;
for suffix in suffixes.iter() {
// Include the suffix with a probability dependent on is_big.
let include_value = rng.random::<f64>() < if is_big { 0.8 } else { 0.05 };
if include_value {
data.entry(prefix).or_default().insert(suffix, counter);
counter += 1;
}
}
}
assert_eq!(data.values().map(|m| m.len()).sum::<usize>(), 175306);
let dense = ZeroAsciiDenseSparse2dTrieOwned::try_from_btree_map_str(&data, b'/').unwrap();
check_data(&data, dense.as_borrowed(), true);
let byte_len = check_encoding(dense.as_borrowed());
assert_eq!(byte_len, 555240);
let simple_trie = make_simple_ascii_trie(&data);
assert_eq!(simple_trie.byte_len(), 1099634);
}
#[test]
fn test_dense_sparse_window_selection() {
//Make initial Btree (not using enumerate)
let row_width = usize::from(u16::MAX); // Densetype max
let far_low = 0;
let cluster_start = 50;
let far_high = cluster_start + row_width + 100;
let mut inner = BTreeMap::new();
inner.insert("low", far_low);
inner.insert("a", 50); // cluster_start
inner.insert("c", 53); // cluster_start + 3
inner.insert("d", cluster_start + row_width - 3);
inner.insert("e", cluster_start + row_width - 2);
inner.insert("f", cluster_start + row_width - 1);
inner.insert("c2", 53); // cluster_start + 3
inner.insert("g", cluster_start + row_width);
inner.insert("h", cluster_start + row_width + 1);
inner.insert("b", 52); // cluster_start + 2
inner.insert("high", far_high);
inner.insert("c3", 53); // cluster_start + 3
inner.insert("low2", far_low);
let mut data = BTreeMap::new();
data.insert("p", inner);
// Build the 2d trie.
let dense = ZeroAsciiDenseSparse2dTrieOwned::try_from_btree_map_str(&data, b'/').unwrap();
let trie = dense.as_borrowed();
assert_eq!(
format!("{:?}", trie),
"ZeroAsciiDenseSparse2dTrieBorrowed { primary: ZeroTrieSimpleAscii { store: [112, 128, 47, 144, 36, 195, 97, 104, 108, 2, 8, 144, 34, 105, 103, 104, 147, 241, 5, 111, 119, 128, 50, 128] }, suffixes: ZeroTrieSimpleAscii { store: [201, 97, 98, 99, 100, 101, 102, 103, 104, 108, 1, 2, 9, 10, 11, 12, 13, 18, 128, 129, 130, 194, 50, 51, 1, 131, 132, 133, 134, 135, 136, 137, 105, 103, 104, 138, 111, 119, 139, 50, 140] }, dense: ZeroVec([65535, 0, 1, 1, 1, 65530, 65531, 65532, 65533, 65534, 65535, 65535, 65535]), suffix_count: 13, delimiter: 47 }"
);
check_data(&data, trie, true);
let byte_len = check_encoding(dense.as_borrowed());
assert_eq!(byte_len, 102);
let simple_trie = make_simple_ascii_trie(&data);
assert_eq!(simple_trie.byte_len(), 60);
}
#[test]
fn test_dense_suffix_count_with_low_frequency_suffixes() {
// Create a dataset where many suffixes appear in only one prefix.
// This should result in fewer dense suffixes than if we included all.
let mut data: BTreeMap<&str, BTreeMap<&str, usize>> = BTreeMap::new();
// Common suffixes that appear in multiple prefixes
data.entry("en").or_default().insert("US", 1);
data.entry("en").or_default().insert("GB", 2);
data.entry("en").or_default().insert("CA", 3);
data.entry("fr").or_default().insert("FR", 4);
data.entry("fr").or_default().insert("CA", 5);
data.entry("fr").or_default().insert("BE", 6);
data.entry("de").or_default().insert("DE", 7);
data.entry("de").or_default().insert("AT", 8);
data.entry("de").or_default().insert("CH", 9);
// Low-frequency suffixes (appear in only one prefix each)
data.entry("en").or_default().insert("rare1", 100);
data.entry("fr").or_default().insert("rare2", 101);
data.entry("de").or_default().insert("rare3", 102);
data.entry("es").or_default().insert("rare4", 103);
data.entry("es").or_default().insert("ES", 104);
let trie = ZeroAsciiDenseSparse2dTrieOwned::try_from_btree_map_str(&data, b'/').unwrap();
assert_eq!(
format!("{:?}", trie.as_borrowed()),
"ZeroAsciiDenseSparse2dTrieBorrowed { primary: ZeroTrieSimpleAscii { store: [195, 100, 101, 102, 22, 61, 101, 47, 196, 65, 67, 68, 114, 2, 4, 6, 84, 136, 72, 137, 69, 135, 97, 114, 101, 51, 144, 86, 194, 110, 115, 21, 47, 196, 67, 71, 85, 114, 2, 4, 6, 65, 131, 66, 130, 83, 129, 97, 114, 101, 49, 144, 84, 47, 194, 69, 114, 3, 83, 144, 88, 97, 114, 101, 52, 144, 87, 114, 47, 196, 66, 67, 70, 114, 2, 4, 6, 69, 134, 65, 133, 82, 132, 97, 114, 101, 50, 144, 85] }, suffixes: ZeroTrieSimpleAscii { store: [201, 65, 66, 67, 68, 69, 70, 71, 85, 114, 2, 4, 10, 12, 14, 16, 18, 20, 84, 128, 69, 129, 194, 65, 72, 1, 130, 131, 69, 132, 83, 133, 82, 134, 66, 135, 83, 136, 97, 114, 101, 196, 49, 50, 51, 52, 1, 2, 3, 137, 138, 139, 140] }, dense: ZeroVec([]), suffix_count: 13, delimiter: 47 }"
);
check_data(&data, trie.as_borrowed(), true);
// Non-existent lookups
let mut non_existent: BTreeMap<&str, BTreeMap<&str, usize>> = BTreeMap::new();
non_existent.entry("en").or_default().insert("FR", 0);
non_existent
.entry("nonexistent")
.or_default()
.insert("US", 0);
check_data(&non_existent, trie.as_borrowed(), false);
}
#[test]
fn test_empty_dense_when_no_suffix_meets_threshold() {
// Create a dataset where NO suffix appears in enough prefixes to meet threshold.
// Dense matrix should be empty, all lookups use sparse path.
let mut data: BTreeMap<&str, BTreeMap<&str, usize>> = BTreeMap::new();
// Each suffix appears in only one prefix (0% overlap)
data.entry("a").or_default().insert("s1", 1);
data.entry("b").or_default().insert("s2", 2);
data.entry("c").or_default().insert("s3", 3);
let trie = ZeroAsciiDenseSparse2dTrieOwned::try_from_btree_map_str(&data, b'/').unwrap();
assert_eq!(
format!("{:?}", trie.as_borrowed()),
"ZeroAsciiDenseSparse2dTrieBorrowed { primary: ZeroTrieSimpleAscii { store: [195, 97, 98, 99, 4, 8, 47, 115, 49, 129, 47, 115, 50, 130, 47, 115, 51, 131] }, suffixes: ZeroTrieSimpleAscii { store: [115, 195, 49, 50, 51, 1, 2, 128, 129, 130] }, dense: ZeroVec([]), suffix_count: 3, delimiter: 47 }"
);
check_data(&data, trie.as_borrowed(), true);
// Non-existent
let mut non_existent: BTreeMap<&str, BTreeMap<&str, usize>> = BTreeMap::new();
non_existent.entry("a").or_default().insert("s2", 0);
non_existent.entry("b").or_default().insert("s3", 0);
check_data(&non_existent, trie.as_borrowed(), false);
}
#[test]
fn test_empty_input() {
let data: BTreeMap<&str, BTreeMap<&str, usize>> = BTreeMap::new();
let trie = ZeroAsciiDenseSparse2dTrieOwned::try_from_btree_map_str(&data, b'/').unwrap();
check_data(&data, trie.as_borrowed(), true);
}
#[test]
fn test_lookup_semantics_remain_correct() {
// Verify that both dense and sparse suffixes return correct values
let mut data: BTreeMap<&str, BTreeMap<&str, usize>> = BTreeMap::new();
// High-frequency suffix (will be dense)
for i in 0..10 {
let prefix = format!("p{}", i);
data.entry(prefix.leak() as &str)
.or_default()
.insert("common", 100 + i);
}
// Low-frequency suffix (will be sparse)
data.entry("p0").or_default().insert("rare", 999);
let trie = ZeroAsciiDenseSparse2dTrieOwned::try_from_btree_map_str(&data, b'/').unwrap();
assert_eq!(
format!("{:?}", trie.as_borrowed()),
"ZeroAsciiDenseSparse2dTrieBorrowed { primary: ZeroTrieSimpleAscii { store: [112, 202, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 4, 8, 12, 16, 20, 24, 28, 32, 36, 128, 47, 144, 84, 129, 47, 144, 85, 130, 47, 144, 86, 131, 47, 144, 87, 132, 47, 144, 88, 133, 47, 144, 89, 134, 47, 144, 90, 135, 47, 144, 91, 136, 47, 144, 92, 137, 47, 144, 93] }, suffixes: ZeroTrieSimpleAscii { store: [194, 99, 114, 6, 111, 109, 109, 111, 110, 128, 97, 114, 101, 129] }, dense: ZeroVec([0, 899, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535, 0, 65535]), suffix_count: 2, delimiter: 47 }"
);
check_data(&data, trie.as_borrowed(), true);
// Non-existent combinations
let mut non_existent: BTreeMap<&str, BTreeMap<&str, usize>> = BTreeMap::new();
non_existent.entry("p1").or_default().insert("rare", 0);
non_existent.entry("p99").or_default().insert("common", 0);
check_data(&non_existent, trie.as_borrowed(), false);
}
#[test]
fn test_delimiter_validation() {
let mut data: BTreeMap<&str, BTreeMap<&str, usize>> = BTreeMap::new();
// Try to use delimiter in suffix
data.entry("test").or_default().insert("bad/suffix", 1);
let result = ZeroAsciiDenseSparse2dTrieOwned::try_from_btree_map_str(&data, b'/');
assert!(result.is_err());
// Try to use delimiter in prefix
let mut data2: BTreeMap<&str, BTreeMap<&str, usize>> = BTreeMap::new();
data2.entry("bad/prefix").or_default().insert("ok", 1);
let result2 = ZeroAsciiDenseSparse2dTrieOwned::try_from_btree_map_str(&data2, b'/');
assert!(result2.is_err());
}
+138
View File
@@ -0,0 +1,138 @@
// This file is part of ICU4X. For terms of use, please see the file
// called LICENSE at the top level of the ICU4X source tree
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
#![allow(non_camel_case_types, non_snake_case)]
use zerotrie::ZeroAsciiIgnoreCaseTrie;
use zerotrie::ZeroTrie;
use zerotrie::ZeroTrieExtendedCapacity;
use zerotrie::ZeroTriePerfectHash;
use zerotrie::ZeroTrieSimpleAscii;
use zerovec::ZeroVec;
#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
#[cfg_attr(feature = "zerofrom", derive(zerofrom::ZeroFrom))]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "databake", derive(databake::Bake))]
#[cfg_attr(feature = "databake", databake(path = crate))]
struct DeriveTest_ZeroTrie_ZeroVec<'data> {
#[cfg_attr(feature = "serde", serde(borrow))]
pub _data: ZeroTrie<ZeroVec<'data, u8>>,
}
#[test]
#[cfg(all(feature = "databake", feature = "alloc"))]
fn bake_ZeroTrie_ZeroVec() {
use databake::*;
extern crate std;
test_bake!(
DeriveTest_ZeroTrie_ZeroVec<'static>,
crate::DeriveTest_ZeroTrie_ZeroVec {
_data: zerotrie::ZeroTrieSimpleAscii {
store: zerovec::ZeroVec::new(),
}
.into_zerotrie()
},
);
}
#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
#[cfg_attr(feature = "zerofrom", derive(zerofrom::ZeroFrom))]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "databake", derive(databake::Bake))]
#[cfg_attr(feature = "databake", databake(path = crate))]
struct DeriveTest_ZeroTrieSimpleAscii_ZeroVec<'data> {
#[cfg_attr(feature = "serde", serde(borrow))]
pub _data: ZeroTrieSimpleAscii<ZeroVec<'data, u8>>,
}
#[test]
#[cfg(all(feature = "databake", feature = "alloc"))]
fn bake_ZeroTrieSimpleAscii_ZeroVec() {
use databake::*;
extern crate std;
test_bake!(
DeriveTest_ZeroTrieSimpleAscii_ZeroVec<'static>,
crate::DeriveTest_ZeroTrieSimpleAscii_ZeroVec {
_data: zerotrie::ZeroTrieSimpleAscii {
store: zerovec::ZeroVec::new(),
}
},
);
}
#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
#[cfg_attr(feature = "zerofrom", derive(zerofrom::ZeroFrom))]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "databake", derive(databake::Bake))]
#[cfg_attr(feature = "databake", databake(path = crate))]
struct DeriveTest_ZeroAsciiIgnoreCaseTrie_ZeroVec<'data> {
#[cfg_attr(feature = "serde", serde(borrow))]
pub _data: ZeroAsciiIgnoreCaseTrie<ZeroVec<'data, u8>>,
}
#[test]
#[cfg(all(feature = "databake", feature = "alloc"))]
fn bake_ZeroAsciiIgnoreCaseTrie_ZeroVec() {
use databake::*;
extern crate std;
test_bake!(
DeriveTest_ZeroAsciiIgnoreCaseTrie_ZeroVec<'static>,
crate::DeriveTest_ZeroAsciiIgnoreCaseTrie_ZeroVec {
_data: zerotrie::ZeroAsciiIgnoreCaseTrie {
store: zerovec::ZeroVec::new(),
}
},
);
}
#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
#[cfg_attr(feature = "zerofrom", derive(zerofrom::ZeroFrom))]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "databake", derive(databake::Bake))]
#[cfg_attr(feature = "databake", databake(path = crate))]
struct DeriveTest_ZeroTriePerfectHash_ZeroVec<'data> {
#[cfg_attr(feature = "serde", serde(borrow))]
pub _data: ZeroTriePerfectHash<ZeroVec<'data, u8>>,
}
#[test]
#[cfg(all(feature = "databake", feature = "alloc"))]
fn bake_ZeroTriePerfectHash_ZeroVec() {
use databake::*;
extern crate std;
test_bake!(
DeriveTest_ZeroTriePerfectHash_ZeroVec<'static>,
crate::DeriveTest_ZeroTriePerfectHash_ZeroVec {
_data: zerotrie::ZeroTriePerfectHash {
store: zerovec::ZeroVec::new(),
}
},
);
}
#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
#[cfg_attr(feature = "zerofrom", derive(zerofrom::ZeroFrom))]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "databake", derive(databake::Bake))]
#[cfg_attr(feature = "databake", databake(path = crate))]
struct DeriveTest_ZeroTrieExtendedCapacity_ZeroVec<'data> {
#[cfg_attr(feature = "serde", serde(borrow))]
pub _data: ZeroTrieExtendedCapacity<ZeroVec<'data, u8>>,
}
#[test]
#[cfg(all(feature = "databake", feature = "alloc"))]
fn bake_ZeroTrieExtendedCapacity_ZeroVec() {
use databake::*;
extern crate std;
test_bake!(
DeriveTest_ZeroTrieExtendedCapacity_ZeroVec<'static>,
crate::DeriveTest_ZeroTrieExtendedCapacity_ZeroVec {
_data: zerotrie::ZeroTrieExtendedCapacity {
store: zerovec::ZeroVec::new(),
}
},
);
}
+46
View File
@@ -0,0 +1,46 @@
// This file is part of ICU4X. For terms of use, please see the file
// called LICENSE at the top level of the ICU4X source tree
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
use zerotrie::ZeroAsciiIgnoreCaseTrie;
mod testdata {
include!("data/data.rs");
}
use testdata::strings_to_litemap;
#[test]
fn test_ignore_case_coverage() {
let litemap = strings_to_litemap(&["", "aBc", "aBcD", "aBce", "aBcF", "aBcghi"]);
// Test both construction paths
ZeroAsciiIgnoreCaseTrie::try_from(&litemap).unwrap();
let trie = litemap
.iter()
.map(|(k, v)| (*k, *v))
.collect::<ZeroAsciiIgnoreCaseTrie<Vec<u8>>>();
// Test lookup
for (k, v) in litemap.iter() {
assert_eq!(trie.get(k), Some(*v), "normal: {k:?}");
let k_upper = k
.iter()
.map(|c| c.to_ascii_uppercase())
.collect::<Vec<u8>>();
assert_eq!(trie.get(k_upper), Some(*v), "upper: {k:?}");
let k_lower = k
.iter()
.map(|c| c.to_ascii_lowercase())
.collect::<Vec<u8>>();
assert_eq!(trie.get(k_lower), Some(*v), "lower: {k:?}");
}
// Test mixed-case strings
let problematic_strs = &["A", "ab", "abc", "aBcd", "aBcgHi"];
for problematic_str in problematic_strs {
let mut litemap = litemap.clone();
litemap.insert(problematic_str.as_bytes(), 100);
ZeroAsciiIgnoreCaseTrie::try_from(&litemap).expect_err(problematic_str);
}
}
+164
View File
@@ -0,0 +1,164 @@
// This file is part of ICU4X. For terms of use, please see the file
// called LICENSE at the top level of the ICU4X source tree
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
use icu_locale_core::extensions::private::Private;
use icu_locale_core::Locale;
use litemap::LiteMap;
use std::collections::BTreeSet;
use writeable::Writeable;
use zerotrie::ZeroTriePerfectHash;
use zerotrie::ZeroTrieSimpleAscii;
use zerovec::VarZeroVec;
mod testdata {
include!("data/data.rs");
}
use testdata::locales_with_aux::{NUM_UNIQUE_BLOBS, STRINGS};
use testdata::strings_to_litemap;
#[test]
#[cfg(target_pointer_width = "64")]
fn test_combined() {
let litemap = strings_to_litemap(STRINGS);
let vzv: VarZeroVec<str> = STRINGS.into();
// Lookup table size:
assert_eq!(vzv.as_bytes().len(), 10219);
// Size including pointer array:
assert_eq!(
vzv.as_bytes().len() + STRINGS.len() * size_of::<usize>(),
18635
);
let trie = ZeroTrieSimpleAscii::try_from(&litemap).unwrap();
// Lookup table size:
assert_eq!(trie.byte_len(), 5104);
// Size including pointer array:
assert_eq!(
trie.byte_len() + NUM_UNIQUE_BLOBS * size_of::<usize>(),
8392
);
let trie = ZeroTriePerfectHash::try_from(&litemap).unwrap();
// Lookup table size:
assert_eq!(trie.byte_len(), 5157);
// Size including pointer array:
assert_eq!(
trie.byte_len() + NUM_UNIQUE_BLOBS * size_of::<usize>(),
8445
);
let total_str_len = litemap.keys().map(|k| k.len()).sum::<usize>();
assert_eq!(total_str_len, 8115);
// Lookup table size:
assert_eq!(total_str_len + STRINGS.len() * size_of::<usize>(), 16531);
// Size including pointer array: (2x for the lookup array and value array)
assert_eq!(
total_str_len + 2 * STRINGS.len() * size_of::<usize>(),
24947
);
// Size including u16 pointer array:
assert_eq!(
total_str_len
+ STRINGS.len() * size_of::<usize>()
+ STRINGS.len() * size_of::<u16>()
+ NUM_UNIQUE_BLOBS * size_of::<usize>(),
21923
);
}
#[test]
#[cfg(target_pointer_width = "64")]
fn test_aux_split() {
let locales: Vec<Locale> = STRINGS.iter().map(|s| s.parse().unwrap()).collect();
let aux_keys: BTreeSet<&Private> = locales.iter().map(|l| &l.extensions.private).collect();
assert_eq!(aux_keys.len(), 6);
let mut cumulative_index = 0;
let mut total_simpleascii_len = 0;
let mut total_perfecthash_len = 0;
let mut total_vzv_len = 0;
let mut unique_locales = BTreeSet::new();
for private in aux_keys.iter() {
let current_locales: Vec<Locale> = locales
.iter()
.filter(|l| l.extensions.private == **private)
.map(|l| {
let mut l = l.clone();
l.extensions.private = Private::default();
l
})
.collect();
let litemap: LiteMap<Vec<u8>, usize> = current_locales
.iter()
.map(|l| {
(l.write_to_string().into_owned().into_bytes(), {
cumulative_index += 1;
cumulative_index - 1
})
})
.collect();
let trie = ZeroTrieSimpleAscii::try_from(&litemap).unwrap();
total_simpleascii_len += trie.byte_len();
let trie = ZeroTriePerfectHash::try_from(&litemap).unwrap();
total_perfecthash_len += trie.byte_len();
for k in litemap.keys() {
unique_locales.insert(k.clone());
}
let strs: Vec<String> = current_locales
.iter()
.map(|l| l.write_to_string().into_owned())
.collect();
let vzv: VarZeroVec<str> = strs.as_slice().into();
total_vzv_len += vzv.as_bytes().len();
}
assert_eq!(cumulative_index, locales.len());
assert_eq!(total_simpleascii_len, 5098);
assert_eq!(total_perfecthash_len, 5302);
assert_eq!(total_vzv_len, 5486);
let total_unique_locale_str_len = unique_locales.iter().map(|v| v.len()).sum::<usize>();
assert_eq!(total_unique_locale_str_len, 945);
// Size including pointer array:
assert_eq!(
total_simpleascii_len + NUM_UNIQUE_BLOBS * size_of::<usize>(),
8386
);
assert_eq!(
total_perfecthash_len + NUM_UNIQUE_BLOBS * size_of::<usize>(),
8590
);
assert_eq!(total_vzv_len + STRINGS.len() * size_of::<usize>(), 13902);
// 2x for the lookup arrays and value arrays
assert_eq!(
total_unique_locale_str_len + 2 * STRINGS.len() * size_of::<usize>(),
17777
);
// Size including u16 pointer array:
assert_eq!(
total_unique_locale_str_len
+ STRINGS.len() * size_of::<usize>()
+ STRINGS.len() * size_of::<u16>()
+ NUM_UNIQUE_BLOBS * size_of::<usize>(),
14753
);
}