[fuzzy] add fuzzy search algorithm

Compare the query and choice/item character by character. sorting is not
implemented yet.
This commit is contained in:
zhang_ji 2016-06-22 16:00:51 +08:00
parent 6816c22d00
commit 74b7ed6da0
4 changed files with 94 additions and 10 deletions

View file

@ -1,6 +1,7 @@
// An item is line of text that read from `find` command or stdin together with
// the internal states, such as selected or not
use std;
pub struct Item {
pub text: String,
pub selected: bool,
@ -22,26 +23,29 @@ impl Item {
}
}
pub type Score = (usize, usize); // score (matched-len, start pos)
pub type Range = (usize, usize); // (start, end), end is excluded
pub struct MatchedItem {
pub index: usize, // index of current item in items
pub rank: [i32; 5], // the scores in different criteria
pub matched_range_bytes: (i32, i32), // range of bytes that metched the pattern
pub score: Score,
pub matched_range_chars: Range, // range of chars that metched the pattern
}
impl MatchedItem {
pub fn new(index: usize) -> Self {
MatchedItem {
index: index,
rank: [0, 0, 0, 0, 0],
matched_range_bytes: (0, 0),
score: (std::usize::MAX, 0),
matched_range_chars: (0, 0),
}
}
pub fn set_matched_range(&mut self, start: i32, end: i32) {
self.matched_range_bytes = (start, end);
pub fn set_matched_range(&mut self, range: Range) {
self.matched_range_chars = range;
}
pub fn set_rank(&mut self, pos: usize, val: i32) {
self.rank[pos] = val;
pub fn set_score(&mut self, score: Score) {
self.score = score;
}
}

View file

@ -7,6 +7,7 @@ mod input;
mod matcher;
mod event;
mod model;
mod score;
use std::sync::Arc;
use std::thread;

View file

@ -7,6 +7,7 @@ use std::sync::mpsc::Sender;
use event::Event;
use item::{Item, MatchedItem};
use util::eventbox::EventBox;
use score;
pub struct Matcher {
tx_output: Sender<MatchedItem>, // channel to send output to
@ -41,14 +42,28 @@ impl Matcher {
item.starts_with(&self.query)
}
fn match_item(&self, index: usize, item: &str) -> Option<MatchedItem> {
let matched_result = score::compute_match_length(item, &self.query);
if matched_result == None {
return None;
}
let (matched_start, matched_len) = matched_result.unwrap();
let mut item = MatchedItem::new(index);
item.set_matched_range((matched_start as usize, (matched_start + matched_len) as usize));
item.set_score((matched_len, matched_start));
Some(item)
}
pub fn process(&mut self) {
let items = self.items.read().unwrap();
for item in items[self.item_pos..].into_iter() {
// process the matcher
//self.tx_output.send(string.clone());
if self.match_str(&item.text) {
if let Some(matched) = self.match_item(self.item_pos, &item.text) {
self.num_matched += 1;
let _ = self.tx_output.send(MatchedItem::new(self.item_pos));
let _ = self.tx_output.send(matched);
}
self.item_pos += 1;

64
src/score.rs Normal file
View file

@ -0,0 +1,64 @@
/// score is responsible for calculating the scores of the similarity between
/// the query and the choice.
///
/// It is modeled after https://github.com/felipesere/icepick.git
// return (start, matched_len)
pub fn compute_match_length(choice: &str, query: &str) -> Option<(usize, usize)> {
if query.len() <= 0 {
return Some((0, 0));
}
let impossible_match = choice.len() + 1;
let mut matched_start = impossible_match;
let mut matched_end = impossible_match;
let mut choice_chars = choice.chars().enumerate().peekable();
let mut query_chars = query.chars().enumerate().peekable();
loop {
if query_chars.peek() == None {
return Some((matched_start, matched_end - matched_start+1));
}
if choice_chars.peek() == None {
return None;
}
let &(idx_choice, c) = choice_chars.peek().unwrap();
let &(_, q) = query_chars.peek().unwrap();
if c == q {
if matched_start == impossible_match { matched_start = idx_choice; }
let _ = query_chars.next();
}
matched_end = idx_choice;
let _ = choice_chars.next();
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_compute_match_length() {
let choice_1 = "I am a 中国人.";
let query_1 = "a人";
assert_eq!(super::compute_match_length(&choice_1, &query_1), Some((2, 8)));
let choice_2 = "Choice did not matter";
let query_2 = "";
assert_eq!(super::compute_match_length(&choice_2, &query_2), Some((0, 0)));
let choice_3 = "abcdefg";
let query_3 = "hi";
assert_eq!(super::compute_match_length(&choice_3, &query_3), None);
let choice_4 = "Partial match did not count";
let query_4 = "PP";
assert_eq!(compute_match_length(&choice_4, &query_4), None);
}
}