Skip to content

Commit 4b76c46

Browse files
authored
pick -n supports closest name match (#138)
1 parent 64a9d10 commit 4b76c46

File tree

1 file changed

+71
-4
lines changed

1 file changed

+71
-4
lines changed

src/cmds/pick.rs

+71-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//! Pick command
22
use super::Command;
3+
use crate::cache::models::Problem;
34
use crate::err::Error;
45
use async_trait::async_trait;
56
use clap::{Arg, ArgAction, ArgMatches, Command as ClapCommand};
@@ -136,12 +137,12 @@ impl Command for PickCommand {
136137
};
137138

138139
let fid = match m.contains_id("name") {
139-
//check for name specified
140+
// check for name specified, or closest name
140141
true => {
141142
match m.get_one::<String>("name").map(|name| name) {
142-
Some(quesname) => match cache.get_problem_id_from_name(quesname) {
143-
Ok(p) => p,
144-
Err(_) => 1,
143+
Some(quesname) => match closest_named_problem(&problems, quesname) {
144+
Some(p) => p,
145+
None => 1,
145146
},
146147
None => {
147148
// Pick random without specify id
@@ -177,3 +178,69 @@ impl Command for PickCommand {
177178
Ok(())
178179
}
179180
}
181+
182+
// Returns the closest problem according to a scoring algorithm
183+
// taking into account both the longest common subsequence and the size
184+
// problem string (to compensate for smaller strings having smaller lcs).
185+
// Returns None if there are no problems in the problem list
186+
fn closest_named_problem(problems: &Vec<Problem>, lookup_name: &str) -> Option<i32> {
187+
let max_name_size: usize = problems.iter().map(|p| p.name.len()).max()?;
188+
// Init table to the max name length of all the problems to share
189+
// the same table allocation
190+
let mut table: Vec<usize> = vec![0; (max_name_size + 1) * (lookup_name.len() + 1)];
191+
192+
// this is guaranteed because of the earlier max None propegation
193+
assert!(problems.len() > 0);
194+
let mut max_score = 0;
195+
let mut current_problem = &problems[0];
196+
for problem in problems {
197+
// In case bug becomes bugged, always return the matching string
198+
if problem.name == lookup_name {
199+
return Some(problem.fid);
200+
}
201+
202+
let this_lcs = longest_common_subsequence(&mut table, &problem.name, lookup_name);
203+
let this_score = this_lcs * (max_name_size - problem.name.len());
204+
205+
if this_score > max_score {
206+
max_score = this_score;
207+
current_problem = &problem;
208+
}
209+
}
210+
211+
Some(current_problem.fid)
212+
}
213+
214+
// Longest commong subsequence DP approach O(nm) space and time. Table must be at least
215+
// (text1.len() + 1) * (text2.len() + 1) length or greater and is mutated every call
216+
fn longest_common_subsequence(table: &mut Vec<usize>, text1: &str, text2: &str) -> usize {
217+
assert!(table.len() >= (text1.len() + 1) * (text2.len() + 1));
218+
let height: usize = text1.len() + 1;
219+
let width: usize = text2.len() + 1;
220+
221+
// initialize base cases to 0
222+
for i in 0..height {
223+
table[i * width + (width - 1)] = 0;
224+
}
225+
for j in 0..width {
226+
table[((height - 1) * width) + j] = 0;
227+
}
228+
229+
let mut i: usize = height - 1;
230+
let mut j: usize;
231+
for c0 in text1.chars().rev() {
232+
i -= 1;
233+
j = width - 1;
234+
for c1 in text2.chars().rev() {
235+
j -= 1;
236+
if c0.to_lowercase().next() == c1.to_lowercase().next() {
237+
table[i * width + j] = 1 + table[(i + 1) * width + j + 1];
238+
} else {
239+
let a = table[(i + 1) * width + j];
240+
let b = table[i * width + j + 1];
241+
table[i * width + j] = std::cmp::max(a, b);
242+
}
243+
}
244+
}
245+
table[0]
246+
}

0 commit comments

Comments
 (0)