leetcode_cli/cmds/
pick.rs1use super::Command;
3use crate::cache::models::Problem;
4use crate::err::Error;
5use async_trait::async_trait;
6use clap::{Arg, ArgAction, ArgMatches, Command as ClapCommand};
7pub struct PickCommand;
34
35static QUERY_HELP: &str = r#"Filter questions by conditions:
36Uppercase means negative
37e = easy E = m+h
38m = medium M = e+h
39h = hard H = e+m
40d = done D = not done
41l = locked L = not locked
42s = starred S = not starred"#;
43
44#[async_trait]
45impl Command for PickCommand {
46 fn usage() -> ClapCommand {
48 ClapCommand::new("pick")
49 .about("Pick a problem")
50 .visible_alias("p")
51 .arg(
52 Arg::new("name")
53 .short('n')
54 .long("name")
55 .value_parser(clap::value_parser!(String))
56 .help("Problem name")
57 .num_args(1),
58 )
59 .arg(
60 Arg::new("id")
61 .value_parser(clap::value_parser!(i32))
62 .help("Problem id")
63 .num_args(1),
64 )
65 .arg(
66 Arg::new("plan")
67 .short('p')
68 .long("plan")
69 .num_args(1)
70 .help("Invoking python scripts to filter questions"),
71 )
72 .arg(
73 Arg::new("query")
74 .short('q')
75 .long("query")
76 .num_args(1)
77 .help(QUERY_HELP),
78 )
79 .arg(
80 Arg::new("tag")
81 .short('t')
82 .long("tag")
83 .num_args(1)
84 .help("Filter questions by tag"),
85 )
86 .arg(
87 Arg::new("daily")
88 .short('d')
89 .long("daily")
90 .help("Pick today's daily challenge")
91 .action(ArgAction::SetTrue),
92 )
93 }
94
95 async fn handler(m: &ArgMatches) -> Result<(), Error> {
97 use crate::cache::Cache;
98 use rand::Rng;
99
100 let cache = Cache::new()?;
101 let mut problems = cache.get_problems()?;
102 if problems.is_empty() {
103 cache.download_problems().await?;
104 Self::handler(m).await?;
105 return Ok(());
106 }
107
108 #[cfg(feature = "pym")]
111 {
112 if m.contains_id("plan") {
113 let ids = crate::pym::exec(m.get_one::<String>("plan").unwrap_or(&"".to_string()))?;
114 crate::helper::squash(&mut problems, ids)?;
115 }
116 }
117
118 if m.contains_id("tag") {
120 let ids = cache
121 .clone()
122 .get_tagged_questions(m.get_one::<String>("tag").unwrap_or(&"".to_string()))
123 .await?;
124 crate::helper::squash(&mut problems, ids)?;
125 }
126
127 if m.contains_id("query") {
129 let query = m.get_one::<String>("query").ok_or(Error::NoneError)?;
130 crate::helper::filter(&mut problems, query.to_string());
131 }
132
133 let daily = m.get_one::<bool>("daily").unwrap_or(&false);
134 let daily_id = if *daily {
135 Some(cache.get_daily_problem_id().await?)
136 } else {
137 None
138 };
139
140 let fid = match m.contains_id("name") {
141 true => {
143 match m.get_one::<String>("name") {
144 Some(quesname) => closest_named_problem(&problems, quesname).unwrap_or(1),
145 None => {
146 let problem = &problems[rand::thread_rng().gen_range(0..problems.len())];
148 problem.fid
149 }
150 }
151 }
152 false => {
153 m.get_one::<i32>("id")
154 .copied()
155 .or(daily_id)
156 .unwrap_or_else(|| {
157 let problem = &problems[rand::thread_rng().gen_range(0..problems.len())];
159 problem.fid
160 })
161 }
162 };
163
164 let r = cache.get_question(fid).await;
165
166 match r {
167 Ok(q) => println!("{}", q.desc()),
168 Err(e) => {
169 eprintln!("{:?}", e);
170 if let Error::Reqwest(_) = e {
171 Self::handler(m).await?;
172 }
173 }
174 }
175
176 Ok(())
177 }
178}
179
180fn closest_named_problem(problems: &Vec<Problem>, lookup_name: &str) -> Option<i32> {
185 let max_name_size: usize = problems.iter().map(|p| p.name.len()).max()?;
186 let mut table: Vec<usize> = vec![0; (max_name_size + 1) * (lookup_name.len() + 1)];
189
190 assert!(!problems.is_empty());
192 let mut max_score = 0;
193 let mut current_problem = &problems[0];
194 for problem in problems {
195 if problem.name == lookup_name {
197 return Some(problem.fid);
198 }
199
200 let this_lcs = longest_common_subsequence(&mut table, &problem.name, lookup_name);
201 let this_score = this_lcs * (max_name_size - problem.name.len());
202
203 if this_score > max_score {
204 max_score = this_score;
205 current_problem = problem;
206 }
207 }
208
209 Some(current_problem.fid)
210}
211
212fn longest_common_subsequence(table: &mut [usize], text1: &str, text2: &str) -> usize {
215 assert!(table.len() >= (text1.len() + 1) * (text2.len() + 1));
216 let height: usize = text1.len() + 1;
217 let width: usize = text2.len() + 1;
218
219 for i in 0..height {
221 table[i * width + (width - 1)] = 0;
222 }
223 for j in 0..width {
224 table[((height - 1) * width) + j] = 0;
225 }
226
227 let mut i: usize = height - 1;
228 let mut j: usize;
229 for c0 in text1.chars().rev() {
230 i -= 1;
231 j = width - 1;
232 for c1 in text2.chars().rev() {
233 j -= 1;
234 if c0.to_lowercase().next() == c1.to_lowercase().next() {
235 table[i * width + j] = 1 + table[(i + 1) * width + j + 1];
236 } else {
237 let a = table[(i + 1) * width + j];
238 let b = table[i * width + j + 1];
239 table[i * width + j] = std::cmp::max(a, b);
240 }
241 }
242 }
243 table[0]
244}