Skip to content

Commit 4b76c46

Browse files
authored
pick -n supports closest name match (clearloop#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

Lines changed: 71 additions & 4 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy