1use crate::cache::models::Problem;
3use crate::err::Error;
4use clap::Args;
5
6static QUERY_HELP: &str = r#"Filter questions by conditions:
7Uppercase means negative
8e = easy E = m+h
9m = medium M = e+h
10h = hard H = e+m
11d = done D = not done
12l = locked L = not locked
13s = starred S = not starred"#;
14
15#[derive(Args)]
17pub struct PickArgs {
18 #[arg(value_parser = clap::value_parser!(i32))]
20 pub id: Option<i32>,
21
22 #[arg(short = 'n', long)]
24 pub name: Option<String>,
25
26 #[arg(short = 'p', long)]
28 pub plan: Option<String>,
29
30 #[arg(short, long, help = QUERY_HELP)]
32 pub query: Option<String>,
33
34 #[arg(short, long)]
36 pub tag: Option<String>,
37
38 #[arg(short = 'd', long)]
40 pub daily: bool,
41}
42
43impl PickArgs {
44 pub async fn run(&self) -> Result<(), Error> {
46 use crate::cache::Cache;
47 use rand::Rng;
48
49 let cache = Cache::new()?;
50 let mut problems = cache.get_problems()?;
51 if problems.is_empty() {
52 cache.download_problems().await?;
53 return Box::pin(self.run()).await;
54 }
55
56 #[cfg(feature = "pym")]
59 {
60 if let Some(ref plan) = self.plan {
61 let ids = crate::pym::exec(plan)?;
62 crate::helper::squash(&mut problems, ids)?;
63 }
64 }
65
66 if let Some(ref tag) = self.tag {
68 let ids = cache.clone().get_tagged_questions(tag).await?;
69 crate::helper::squash(&mut problems, ids)?;
70 }
71
72 if let Some(ref query) = self.query {
74 crate::helper::filter(&mut problems, query.to_string());
75 }
76
77 let daily_id = if self.daily {
78 Some(cache.get_daily_problem_id().await?)
79 } else {
80 None
81 };
82
83 let fid = if let Some(ref quesname) = self.name {
84 closest_named_problem(&problems, quesname).unwrap_or(1)
86 } else {
87 self.id.or(daily_id).unwrap_or_else(|| {
88 let problem = &problems[rand::rng().random_range(0..problems.len())];
90 problem.fid
91 })
92 };
93
94 let r = cache.get_question(fid).await;
95
96 match r {
97 Ok(q) => println!("{}", q.desc()),
98 Err(e) => {
99 eprintln!("{:?}", e);
100 if let Error::Reqwest(_) = e {
101 Box::pin(self.run()).await?;
102 }
103 }
104 }
105
106 Ok(())
107 }
108}
109
110fn closest_named_problem(problems: &Vec<Problem>, lookup_name: &str) -> Option<i32> {
115 let max_name_size: usize = problems.iter().map(|p| p.name.len()).max()?;
116 let mut table: Vec<usize> = vec![0; (max_name_size + 1) * (lookup_name.len() + 1)];
119
120 assert!(!problems.is_empty());
122 let mut max_score = 0;
123 let mut current_problem = &problems[0];
124 for problem in problems {
125 if problem.name == lookup_name {
127 return Some(problem.fid);
128 }
129
130 let this_lcs = longest_common_subsequence(&mut table, &problem.name, lookup_name);
131 let this_score = this_lcs * (max_name_size - problem.name.len());
132
133 if this_score > max_score {
134 max_score = this_score;
135 current_problem = problem;
136 }
137 }
138
139 Some(current_problem.fid)
140}
141
142fn longest_common_subsequence(table: &mut [usize], text1: &str, text2: &str) -> usize {
145 assert!(table.len() >= (text1.len() + 1) * (text2.len() + 1));
146 let height: usize = text1.len() + 1;
147 let width: usize = text2.len() + 1;
148
149 for i in 0..height {
151 table[i * width + (width - 1)] = 0;
152 }
153 for j in 0..width {
154 table[((height - 1) * width) + j] = 0;
155 }
156
157 let mut i: usize = height - 1;
158 let mut j: usize;
159 for c0 in text1.chars().rev() {
160 i -= 1;
161 j = width - 1;
162 for c1 in text2.chars().rev() {
163 j -= 1;
164 if c0.to_lowercase().next() == c1.to_lowercase().next() {
165 table[i * width + j] = 1 + table[(i + 1) * width + j + 1];
166 } else {
167 let a = table[(i + 1) * width + j];
168 let b = table[i * width + j + 1];
169 table[i * width + j] = std::cmp::max(a, b);
170 }
171 }
172 }
173 table[0]
174}