Skip to content

Commit

Permalink
Merge branch 'names-optimizations'
Browse files Browse the repository at this point in the history
  • Loading branch information
Byron committed Jul 31, 2023
2 parents 5272d41 + 6ab652e commit 7b8683e
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 17 deletions.
17 changes: 15 additions & 2 deletions src/names.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
/// An iterator over all possible permutations of hyphens (`-`) and underscores (`_`) of a crate name.
///
/// For instance, the name `parking_lot` is turned into the sequence `parking_lot` and `parking_lot`, while
/// The sequence yields the input name first, then an all-hyphens variant of it followed by an
/// all-underscores variant to maximize the chance of finding a match. Then follow all remaining permutations.
///
/// For instance, the name `parking_lot` is turned into the sequence `parking_lot` and `parking-lot`, while
/// `serde-yaml` is turned into `serde-yaml` and `serde_yaml`.
/// Finally, `a-b_c` is returned as `a-b_c`, `a-b-c`, `a_b_c`, `a_b-c`.
#[derive(Clone)]
pub struct Names {
count: Option<u16>,
Expand Down Expand Up @@ -60,8 +64,10 @@ impl Iterator for Names {
return None;
}

//map the count so the first value is the last one (all "-"), the second one is the first one (all "_")...
let used_count = *count as isize - 1 + self.max_count as isize;
for (sep_index, char_index) in self.separator_indexes[..self.separator_count].iter().enumerate() {
let char = if *count & (1 << sep_index) == 0 { b'-' } else { b'_' };
let char = if used_count & (1 << sep_index) == 0 { b'_' } else { b'-' };
// SAFETY: We validated that `char_index` is a valid UTF-8 codepoint
#[allow(unsafe_code)]
unsafe {
Expand All @@ -78,4 +84,11 @@ impl Iterator for Names {
}
}
}

fn count(self) -> usize
where
Self: Sized,
{
self.max_count as usize
}
}
44 changes: 29 additions & 15 deletions tests/names/mod.rs
Original file line number Diff line number Diff line change
@@ -1,58 +1,72 @@
use crates_index::Names;

#[test]
fn empty_string() {
assert_eq!(Names::new("").unwrap().count(), 1);
fn empty_string_is_nothing_special() {
assert_eq!(assert_count(Names::new("").unwrap()), 1);
}

#[test]
fn name_without_separators_yields_name() {
assert_eq!(Names::new("serde").unwrap().count(), 1);
assert_eq!(assert_count(Names::new("serde").unwrap()), 1);
}

#[test]
fn permutation_count() {
assert_eq!(Names::new("a-b").unwrap().count(), 2);
assert_eq!(Names::new("a-b_c").unwrap().count(), 4);
assert_eq!(Names::new("a_b_c").unwrap().count(), 4);
assert_eq!(Names::new("a_b_c-d").unwrap().count(), 8);
fn permutation_counts() {
assert_eq!(assert_count(Names::new("a-b").unwrap()), 2);
assert_eq!(assert_count(Names::new("a-b_c").unwrap()), 4);
assert_eq!(assert_count(Names::new("a_b_c").unwrap()), 4);
assert_eq!(assert_count(Names::new("a_b_c-d").unwrap()), 8);
}

#[test]
fn max_permutation_count_causes_error() {
assert_eq!(
Names::new("a-b-c-d-e-f-g-h-i-j-k-l-m-n-o-p")
.expect("15 separators are fine")
.count(),
assert_count(Names::new("a-b-c-d-e-f-g-h-i-j-k-l-m-n-o-p").expect("15 separators are fine")),
32768
);
assert!(
Names::new("a-b-c-d-e-f-g-h-i-j-k-l-m-n-o-p-q-r").is_none(),
"17 are not fine anymore"
"16 are not fine anymore"
);
}

#[test]
fn permutations() {
for (name, expected) in [
("parking_lot", &["parking_lot", "parking-lot"] as &[_]), // the input name is always the first one returned.
(
"a-b_c-d", // input name -> all-hyphens -> all_underscores -> rest
&[
"a-b_c-d", "a-b-c-d", "a_b_c_d", "a-b_c_d", "a_b-c_d", "a-b-c_d", "a_b_c-d", "a_b-c-d",
],
),
("a_b", &["a_b", "a-b"]),
("a-b", &["a-b", "a_b"]),
("a-b-c", &["a-b-c", "a_b-c", "a-b_c", "a_b_c"]),
("a-b-c", &["a-b-c", "a_b_c", "a-b_c", "a_b-c"]),
(
"a-b-c-d",
&[
"a-b-c-d", "a_b-c-d", "a-b_c-d", "a_b_c-d", "a-b-c_d", "a_b-c_d", "a-b_c_d", "a_b_c_d",
"a-b-c-d", "a_b_c_d", "a-b_c_d", "a_b-c_d", "a-b-c_d", "a_b_c-d", "a-b_c-d", "a_b-c-d",
],
),
(
"a_b_c_d",
&[
"a_b_c_d", "a-b-c-d", "a_b-c-d", "a-b_c-d", "a_b_c-d", "a-b-c_d", "a_b-c_d", "a-b_c_d",
"a_b_c_d", "a-b-c-d", "a-b_c_d", "a_b-c_d", "a-b-c_d", "a_b_c-d", "a-b_c-d", "a_b-c-d",
],
),
] {
let names: Vec<String> = Names::new(name).unwrap().collect();
assert_eq!(&names, expected);
}
}

fn assert_count(names: Names) -> usize {
let expected = names.clone().collect::<Vec<_>>().len();
assert_eq!(
names.count(),
expected,
"the computed count should match the actual one"
);
expected
}

0 comments on commit 7b8683e

Please sign in to comment.