use std::cmp::min;
#[allow(dead_code)]
pub fn binary_search(entries: &[String], input: &str) -> Option<usize> {
entries.binary_search_by(|s| s.as_str().cmp(input)).ok()
}
pub fn substring_search(entries: &[String], input: &str) -> Vec<String> {
let input = &input.to_lowercase();
entries
.iter()
.filter(|entry| entry.to_lowercase().contains(input))
.cloned()
.collect()
}
pub fn sorted_contains(entries: &[String], input: &str) -> bool {
binary_search(entries, input).is_some()
}
pub fn sorted_contains_all(entries: &[String], inputs: &[String]) -> bool {
inputs.iter().all(|input| sorted_contains(entries, input))
}
pub fn fuzzy_search(entries: &[String], input: &str) -> Vec<String> {
let input = &input.to_lowercase();
let mut fuzzy_results: Vec<(&String, u32)> = entries
.iter()
.filter_map(|entry| {
let entry_lower = &entry.to_lowercase();
entry_lower
.contains(input)
.then_some((entry, 100))
.or_else(|| contains_spread(entry_lower, input).map(|score| (entry, 100 - score)))
})
.collect();
if fuzzy_results.is_empty() {
fuzzy_results = entries
.iter()
.filter_map(|entry| {
contains_with_typo(&entry.to_lowercase(), input).map(|score| (entry, 75 - score))
})
.collect()
}
fuzzy_results.sort_by(|a, b| b.1.cmp(&a.1));
fuzzy_results
.into_iter()
.map(|(entry, _)| entry.clone())
.collect()
}
pub fn contains_spread(word: &str, pattern: &str) -> Option<u32> {
if pattern.is_empty() {
return Some(0);
}
let word_bytes = word.as_bytes();
let word_len = word_bytes.len();
let pattern_bytes = pattern.as_bytes();
let pattern_len = pattern_bytes.len();
if word_len < pattern_len {
return None;
}
let mut start = 0;
let mut pat_idx = 0;
for (i, &byte) in word_bytes.iter().enumerate() {
if byte == pattern_bytes[pat_idx] {
if pat_idx == 0 {
start = i;
}
pat_idx += 1;
if pat_idx == pattern_len {
let total_len = i - start + 1;
return Some((total_len - pattern_len) as u32);
}
}
}
None
}
fn contains_with_typo(word: &str, pattern: &str) -> Option<u32> {
if pattern.is_empty() {
return Some(0);
}
let mut modified_pattern = String::with_capacity(pattern.len() - 1);
let mut result = None;
for i in 0..pattern.len() {
modified_pattern.push_str(&pattern[..i]);
modified_pattern.push_str(&pattern[i + 1..]);
result = match (result, contains_spread(word, &modified_pattern)) {
(Some(score_a), Some(score_b)) => Some(min(score_a, score_b)),
(Some(score), None) | (None, Some(score)) => Some(score),
(_, _) => None,
};
modified_pattern.clear();
}
result
}
#[cfg(test)]
mod tests {
use super::*;
fn test_events() -> Vec<String> {
vec![
"alarmtimer:alarmtimer_suspend",
"alarmtimer:alarmtimer_fired",
"amd_cpu:amd_pstate_perf",
"avc:selinux_audited",
"btrfs:btrfs_reserve_extent",
"btrfs:qgroup_num_dirty_extents",
"btrfs:run_delayed_ref_head",
"ext4:ext4_fsmap_low_key",
"ext4:ext4_fc_stats",
"ext4:ext4_mb_new_inode_pa",
"syscalls:sys_enter_timerfd_settime",
"syscalls:sys_enter_settimeofday",
"syscalls:sys_exit_gettid",
"syscalls:sys_exit_kcmp",
"syscalls:sys_exit_pselect6",
"xfs:xfs_swap_extent_before",
"xfs:xfs_bmap_free_defer",
"xhci-hcd:xhci_address_ctrl_ctx",
"alarmtimer:alarmtimer_cancel",
]
.into_iter()
.map(String::from)
.collect()
}
#[test]
fn test_contains_spread_empty() {
assert_eq!(contains_spread("btrfs:btrfs_reserve_extent", ""), Some(0));
}
#[test]
fn test_contains_spread_basic() {
let word = "syscalls:sys_exit_pselect6";
let result_a = contains_spread(word, "exit");
let result_b = contains_spread(word, "sysexit");
assert_eq!(result_a, Some(0));
assert_eq!(result_b, Some(10));
}
#[test]
fn test_contains_spread_complex() {
let word = "xhci-hcd:xhci_address_ctrl_ctx";
let result_a = contains_spread(word, "hides");
let result_b = contains_spread(word, "xxx");
assert_eq!(result_a, Some(14));
assert_eq!(result_b, Some(27));
}
#[test]
fn test_contains_spread_cannot_find() {
let word = "btrfs:btrfs_reserve_extent";
let result_a = contains_spread(word, "trees");
let result_b = contains_spread(word, "z");
assert_eq!(result_a, None);
assert_eq!(result_b, None);
}
#[test]
fn test_contains_spread_unicode() {
let word = "こあういえお";
let input = "あい";
assert_eq!(contains_spread(word, input), Some(6));
}
#[test]
fn test_contains_with_typo_empty() {
assert_eq!(
contains_with_typo("btrfs:btrfs_reserve_extent", ""),
Some(0)
);
}
#[test]
fn test_contains_with_typo_basic() {
let word = "syscalls:sys_exit_pselect6";
let result_a = contains_with_typo(word, "exlt");
let result_b = contains_with_typo(word, "sbsexit");
assert_eq!(result_a, Some(1));
assert_eq!(result_b, Some(11));
}
#[test]
fn test_contains_with_typo_complex() {
let word = "xhci-hcd:xhci_address_ctrl_ctx";
let result_a = contains_with_typo(word, "hizdes");
let result_b = contains_with_typo(word, "xxxx");
assert_eq!(result_a, Some(14));
assert_eq!(result_b, Some(27));
}
#[test]
fn test_contains_with_typo_cannot_find() {
let word = "btrfs:btrfs_reserve_extent";
let result_a = contains_with_typo(word, "btrzz");
let result_b = contains_with_typo(word, "ww");
assert_eq!(result_a, None);
assert_eq!(result_b, None);
}
#[test]
fn test_fuzzy_search_empty() {
let mut events = test_events();
events.sort();
let results = fuzzy_search(&events, "");
assert_eq!(
results.len(),
19,
"Expected all results, got {}",
results.len()
);
}
#[test]
fn test_fuzzy_search_basic() {
let mut events = test_events();
events.sort();
let results = fuzzy_search(&events, "gettid");
assert_eq!(results.len(), 1);
assert_eq!(results[0], "syscalls:sys_exit_gettid".to_string());
}
#[test]
fn test_fuzzy_search_exact_input() {
let mut events = test_events();
events.sort();
let results = fuzzy_search(&events, "alarmtimer_cancel");
assert_eq!(results.len(), 1);
assert_eq!(results[0], "alarmtimer:alarmtimer_cancel".to_string());
}
#[test]
fn test_fuzzy_search_exact_input_multiple_results() {
let mut events = test_events();
events.sort();
let results = fuzzy_search(&events, "alarm");
let expected_matches = [
"alarmtimer:alarmtimer_suspend",
"alarmtimer:alarmtimer_fired",
"alarmtimer:alarmtimer_cancel",
];
for expected in expected_matches.iter() {
assert!(
results.contains(&expected.to_string()),
"Missing expected match: {expected}"
);
}
assert!(results.len() >= expected_matches.len());
}
#[test]
fn test_fuzzy_search_complex_input() {
let mut events = test_events();
events.sort();
let results = fuzzy_search(&events, "alrMcacEL");
assert_eq!(results.len(), 1);
assert_eq!(results[0], "alarmtimer:alarmtimer_cancel".to_string());
}
#[test]
fn test_fuzzy_search_long_complex_input() {
let mut events = test_events();
events.sort();
let results = fuzzy_search(&events, "alrMtIImeralarmmer_cacEL");
assert_eq!(results.len(), 1);
assert_eq!(results[0], "alarmtimer:alarmtimer_cancel".to_string());
}
#[test]
fn test_fuzzy_search_reuse_search() {
let mut events = test_events();
events.sort();
let results = fuzzy_search(&events, "");
assert_eq!(
results.len(),
19,
"Expected all results, got {}",
results.len()
);
let results = fuzzy_search(&events, "gettid");
assert_eq!(results.len(), 1);
assert_eq!(results[0], "syscalls:sys_exit_gettid".to_string());
let results = fuzzy_search(&events, "alarmtimer_cancel");
assert_eq!(results.len(), 1);
assert_eq!(results[0], "alarmtimer:alarmtimer_cancel".to_string());
let results = fuzzy_search(&events, "alarm");
let expected_matches = [
"alarmtimer:alarmtimer_suspend",
"alarmtimer:alarmtimer_fired",
"alarmtimer:alarmtimer_cancel",
];
for expected in expected_matches.iter() {
assert!(
results.contains(&expected.to_string()),
"Missing expected match: {expected}"
);
}
assert!(
results.len() >= expected_matches.len(),
"Expected all results, got {}",
results.len()
);
let results = fuzzy_search(&events, "alrMcacEL");
assert_eq!(results.len(), 1);
assert_eq!(results[0], "alarmtimer:alarmtimer_cancel".to_string());
}
#[test]
fn test_binary_search_basic() {
let mut events = test_events();
events.sort();
let result = binary_search(&events, "alarmtimer:alarmtimer_cancel");
assert_eq!(result, Some(0));
}
#[test]
fn test_binary_search_basic_2() {
let mut events = test_events();
events.sort();
let result = binary_search(&events, "syscalls:sys_enter_settimeofday");
assert_eq!(result, Some(11));
}
#[test]
fn test_sorted_contains() {
let mut events = test_events();
events.sort();
let result1 = sorted_contains(&events, "ext4:ext4_mb_new_inode_pa");
assert!(result1);
let result2 = sorted_contains(&events, "ext4:ext4_mb_new_inode");
assert!(!result2);
}
#[test]
fn test_sorted_contains_all() {
let mut events = test_events();
events.sort();
let result1 = sorted_contains_all(
&events,
&[
"ext4:ext4_mb_new_inode_pa".to_string(),
"ext4:ext4_mb_new_inode".to_string(),
],
);
assert!(!result1);
let result2 = sorted_contains_all(
&events,
&[
"syscalls:sys_enter_timerfd_settime".to_string(),
"alarmtimer:alarmtimer_fired".to_string(),
"ext4:ext4_fc_stats".to_string(),
],
);
assert!(result2);
}
}