use crate::cache::models::Problem;
use crate::err::Error;
use clap::Args;
static QUERY_HELP: &str = r#"Filter questions by conditions:
Uppercase means negative
e = easy E = m+h
m = medium M = e+h
h = hard H = e+m
d = done D = not done
l = locked L = not locked
s = starred S = not starred"#;
#[derive(Args)]
pub struct PickArgs {
#[arg(value_parser = clap::value_parser!(i32))]
pub id: Option<i32>,
#[arg(short = 'n', long)]
pub name: Option<String>,
#[arg(short = 'p', long)]
pub plan: Option<String>,
#[arg(short, long, help = QUERY_HELP)]
pub query: Option<String>,
#[arg(short, long)]
pub tag: Option<String>,
#[arg(short = 'd', long)]
pub daily: bool,
}
impl PickArgs {
pub async fn run(&self) -> Result<(), Error> {
use crate::cache::Cache;
use rand::Rng;
let cache = Cache::new()?;
let mut problems = cache.get_problems()?;
if problems.is_empty() {
cache.download_problems().await?;
return Box::pin(self.run()).await;
}
#[cfg(feature = "pym")]
{
if let Some(ref plan) = self.plan {
let ids = crate::pym::exec(plan)?;
crate::helper::squash(&mut problems, ids)?;
}
}
if let Some(ref tag) = self.tag {
let ids = cache.clone().get_tagged_questions(tag).await?;
crate::helper::squash(&mut problems, ids)?;
}
if let Some(ref query) = self.query {
crate::helper::filter(&mut problems, query.to_string());
}
let daily_id = if self.daily {
Some(cache.get_daily_problem_id().await?)
} else {
None
};
let fid = if let Some(ref quesname) = self.name {
closest_named_problem(&problems, quesname).unwrap_or(1)
} else {
self.id.or(daily_id).unwrap_or_else(|| {
let problem = &problems[rand::rng().random_range(0..problems.len())];
problem.fid
})
};
let r = cache.get_question(fid).await;
match r {
Ok(q) => println!("{}", q.desc()),
Err(e) => {
eprintln!("{:?}", e);
if let Error::Reqwest(_) = e {
Box::pin(self.run()).await?;
}
}
}
Ok(())
}
}
fn closest_named_problem(problems: &Vec<Problem>, lookup_name: &str) -> Option<i32> {
let max_name_size: usize = problems.iter().map(|p| p.name.len()).max()?;
let mut table: Vec<usize> = vec![0; (max_name_size + 1) * (lookup_name.len() + 1)];
assert!(!problems.is_empty());
let mut max_score = 0;
let mut current_problem = &problems[0];
for problem in problems {
if problem.name == lookup_name {
return Some(problem.fid);
}
let this_lcs = longest_common_subsequence(&mut table, &problem.name, lookup_name);
let this_score = this_lcs * (max_name_size - problem.name.len());
if this_score > max_score {
max_score = this_score;
current_problem = problem;
}
}
Some(current_problem.fid)
}
fn longest_common_subsequence(table: &mut [usize], text1: &str, text2: &str) -> usize {
assert!(table.len() >= (text1.len() + 1) * (text2.len() + 1));
let height: usize = text1.len() + 1;
let width: usize = text2.len() + 1;
for i in 0..height {
table[i * width + (width - 1)] = 0;
}
for j in 0..width {
table[((height - 1) * width) + j] = 0;
}
let mut i: usize = height - 1;
let mut j: usize;
for c0 in text1.chars().rev() {
i -= 1;
j = width - 1;
for c1 in text2.chars().rev() {
j -= 1;
if c0.to_lowercase().next() == c1.to_lowercase().next() {
table[i * width + j] = 1 + table[(i + 1) * width + j + 1];
} else {
let a = table[(i + 1) * width + j];
let b = table[i * width + j + 1];
table[i * width + j] = std::cmp::max(a, b);
}
}
}
table[0]
}