konnektoren_core/challenges/
task_pattern.rs1use rand::seq::SliceRandom;
2use rand::thread_rng;
3use schemars::JsonSchema;
4use serde::{Deserialize, Serialize};
5use std::fmt;
6use std::ops::RangeInclusive;
7
8#[derive(Debug, Clone, PartialEq, JsonSchema)]
9#[serde(rename_all = "snake_case")]
10pub enum TaskPattern {
11 Exact(usize),
13 Range(RangeInclusive<i32>),
15 Random(usize, Option<RangeInclusive<i32>>),
17}
18
19impl TaskPattern {
20 pub fn parse(s: &str) -> Result<Self, String> {
21 if let Ok(n) = s.parse::<usize>() {
22 return Ok(TaskPattern::Exact(n));
23 }
24
25 if s.contains(':') {
26 let parts: Vec<&str> = s.split(':').collect();
27 if parts.len() != 2 {
28 return Err("Invalid random pattern".to_string());
29 }
30 let n = parts[0]
31 .parse::<usize>()
32 .map_err(|_| "Invalid number of tasks".to_string())?;
33 if parts[1] == "random" {
34 return Ok(TaskPattern::Random(n, None));
35 }
36 let range = Self::parse_range(parts[1])?;
37 return Ok(TaskPattern::Random(n, Some(range)));
38 }
39
40 Self::parse_range(s).map(TaskPattern::Range)
41 }
42
43 fn parse_range(s: &str) -> Result<RangeInclusive<i32>, String> {
44 let parts: Vec<&str> = s.split("..").collect();
45 match parts.len() {
46 1 => {
47 let n = parts[0]
48 .parse::<i32>()
49 .map_err(|_| "Invalid range".to_string())?;
50 Ok(n..=n)
51 }
52 2 => {
53 let start = parts[0].parse::<i32>().unwrap_or(i32::MIN);
54 let end = parts[1].parse::<i32>().unwrap_or(i32::MAX);
55 if start > end {
56 return Err("Invalid range".to_string());
57 }
58 Ok(start..=end)
59 }
60 _ => Err("Invalid range format".to_string()),
61 }
62 }
63
64 pub fn select_items<T: Clone>(&self, items: &[T]) -> Vec<T> {
65 match self {
66 TaskPattern::Exact(n) => items.iter().take(*n).cloned().collect(),
67 TaskPattern::Range(range) => {
68 let start = *range.start().max(&0) as usize;
69 let end = *range.end().min(&(items.len() as i32 - 1)) as usize;
70 items[start..=end].to_vec()
71 }
72 TaskPattern::Random(n, range_opt) => {
73 let range = range_opt
74 .as_ref()
75 .map(|r| {
76 let start = *r.start().max(&0) as usize;
77 let end = *r.end().min(&(items.len() as i32 - 1)) as usize;
78 start..=end
79 })
80 .unwrap_or(0..=items.len() - 1);
81
82 let mut rng = thread_rng();
83 items[range]
84 .choose_multiple(&mut rng, *n)
85 .cloned()
86 .collect()
87 }
88 }
89 }
90
91 pub fn len(&self) -> usize {
92 match self {
93 TaskPattern::Exact(n) => *n,
94 TaskPattern::Range(range) => {
95 let start = *range.start().max(&0) as usize;
96 let end = *range.end() as usize;
97 end.saturating_sub(start) + 1
98 }
99 TaskPattern::Random(n, _) => *n,
100 }
101 }
102
103 pub fn is_empty(&self) -> bool {
104 match self {
105 TaskPattern::Exact(n) => *n == 0,
106 TaskPattern::Range(range) => range.is_empty(),
107 TaskPattern::Random(n, _) => *n == 0,
108 }
109 }
110}
111
112impl fmt::Display for TaskPattern {
113 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
114 match self {
115 TaskPattern::Exact(n) => write!(f, "{}", n),
116 TaskPattern::Range(range) => {
117 if *range.start() == i32::MIN {
118 write!(f, "..={}", range.end())
119 } else if *range.end() == i32::MAX {
120 write!(f, "{}..=", range.start())
121 } else {
122 write!(f, "{}..={}", range.start(), range.end())
123 }
124 }
125 TaskPattern::Random(n, Some(range)) => {
126 if *range.start() == i32::MIN {
127 write!(f, "{}:..={}", n, range.end())
128 } else if *range.end() == i32::MAX {
129 write!(f, "{}:{}..=", n, range.start())
130 } else {
131 write!(f, "{}:{}..={}", n, range.start(), range.end())
132 }
133 }
134 TaskPattern::Random(n, None) => write!(f, "{}:random", n),
135 }
136 }
137}
138
139impl Serialize for TaskPattern {
140 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
141 where
142 S: serde::Serializer,
143 {
144 match self {
145 TaskPattern::Exact(n) => serializer.serialize_str(&n.to_string()),
146 TaskPattern::Range(range) => {
147 serializer.serialize_str(&format!("{}..{}", range.start(), range.end()))
148 }
149 TaskPattern::Random(n, Some(range)) => {
150 serializer.serialize_str(&format!("{}:{}..{}", n, range.start(), range.end()))
151 }
152 TaskPattern::Random(n, None) => serializer.serialize_str(&format!("{}:random", n)),
153 }
154 }
155}
156
157impl<'de> Deserialize<'de> for TaskPattern {
158 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
159 where
160 D: serde::Deserializer<'de>,
161 {
162 let s = String::deserialize(deserializer)?;
163 TaskPattern::parse(&s).map_err(serde::de::Error::custom)
164 }
165}
166
167impl From<usize> for TaskPattern {
168 fn from(value: usize) -> Self {
169 TaskPattern::Exact(value)
170 }
171}
172
173impl From<String> for TaskPattern {
174 fn from(s: String) -> Self {
175 TaskPattern::parse(&s).unwrap_or(TaskPattern::Exact(0))
176 }
177}
178
179impl<'a> From<&'a str> for TaskPattern {
180 fn from(s: &'a str) -> Self {
181 TaskPattern::parse(s).unwrap_or(TaskPattern::Exact(0))
182 }
183}
184
185#[cfg(test)]
186mod tests {
187 use super::*;
188
189 #[test]
190 fn test_parse_exact() {
191 assert_eq!(TaskPattern::parse("10"), Ok(TaskPattern::Exact(10)));
192 }
193
194 #[test]
195 fn test_parse_range() {
196 assert_eq!(TaskPattern::parse("1..10"), Ok(TaskPattern::Range(1..=10)));
197 assert_eq!(
198 TaskPattern::parse("..10"),
199 Ok(TaskPattern::Range(i32::MIN..=10))
200 );
201 assert_eq!(
202 TaskPattern::parse("10.."),
203 Ok(TaskPattern::Range(10..=i32::MAX))
204 );
205 }
206
207 #[test]
208 fn test_parse_random() {
209 assert_eq!(
210 TaskPattern::parse("5:random"),
211 Ok(TaskPattern::Random(5, None))
212 );
213 assert_eq!(
214 TaskPattern::parse("5:1..10"),
215 Ok(TaskPattern::Random(5, Some(1..=10)))
216 );
217 }
218
219 #[test]
220 fn test_select_items() {
221 let items: Vec<i32> = (1..=20).collect();
222
223 let pattern = TaskPattern::Exact(5);
225 assert_eq!(pattern.select_items(&items).len(), 5);
226
227 let pattern = TaskPattern::Range(5..=10);
229 assert_eq!(pattern.select_items(&items), vec![6, 7, 8, 9, 10, 11]);
230
231 let pattern = TaskPattern::Random(5, None);
233 assert_eq!(pattern.select_items(&items).len(), 5);
234
235 let pattern = TaskPattern::Random(5, Some(1..=10));
236 let selected = pattern.select_items(&items);
237 assert_eq!(selected.len(), 5);
238 assert!(selected.iter().all(|&x| (2..=11).contains(&x)));
239 }
240
241 #[test]
242 fn test_serialize() {
243 let pattern = TaskPattern::Exact(5);
244 assert_eq!(serde_json::to_string(&pattern).unwrap(), "\"5\"");
245
246 let pattern = TaskPattern::Range(5..=10);
247 assert_eq!(serde_json::to_string(&pattern).unwrap(), "\"5..10\"");
248
249 let pattern = TaskPattern::Random(5, Some(1..=10));
250 assert_eq!(serde_json::to_string(&pattern).unwrap(), "\"5:1..10\"");
251
252 let pattern = TaskPattern::Random(5, None);
253 assert_eq!(serde_json::to_string(&pattern).unwrap(), "\"5:random\"");
254 }
255
256 #[test]
257 fn test_deserialize() {
258 let pattern: TaskPattern = serde_json::from_str("\"5\"").unwrap();
259 assert_eq!(pattern, TaskPattern::Exact(5));
260
261 let pattern: TaskPattern = serde_json::from_str("\"5..10\"").unwrap();
262 assert_eq!(pattern, TaskPattern::Range(5..=10));
263
264 let pattern: TaskPattern = serde_json::from_str("\"5:1..10\"").unwrap();
265 assert_eq!(pattern, TaskPattern::Random(5, Some(1..=10)));
266
267 let pattern: TaskPattern = serde_json::from_str("\"5:random\"").unwrap();
268 assert_eq!(pattern, TaskPattern::Random(5, None));
269 }
270
271 #[test]
272 fn test_from_usize() {
273 let pattern: TaskPattern = 5.into();
274 assert_eq!(pattern, TaskPattern::Exact(5));
275 }
276
277 #[test]
278 fn test_from_string() {
279 let pattern: TaskPattern = "5".to_string().into();
280 assert_eq!(pattern, TaskPattern::Exact(5));
281 }
282
283 #[test]
284 fn test_from_str() {
285 let pattern: TaskPattern = "5".into();
286 assert_eq!(pattern, TaskPattern::Exact(5));
287 }
288
289 #[test]
290 fn test_invalid_pattern() {
291 assert_eq!(
292 TaskPattern::parse("invalid"),
293 Err("Invalid range".to_string())
294 );
295 }
296
297 #[test]
298 fn test_invalid_range() {
299 assert_eq!(
300 TaskPattern::parse("10..5"),
301 Err("Invalid range".to_string())
302 );
303 }
304
305 #[test]
306 fn test_invalid_random() {
307 assert_eq!(
308 TaskPattern::parse("5:10..5"),
309 Err("Invalid range".to_string())
310 );
311 }
312
313 #[test]
314 fn test_invalid_random_count() {
315 assert_eq!(
316 TaskPattern::parse("5:random:10"),
317 Err("Invalid random pattern".to_string())
318 );
319 }
320
321 #[test]
322 fn test_invalid_random_range() {
323 assert_eq!(
324 TaskPattern::parse("5:10..5"),
325 Err("Invalid range".to_string())
326 );
327 }
328
329 #[test]
330 fn test_invalid_random_range_count() {
331 assert_eq!(
332 TaskPattern::parse("5:10..5:10"),
333 Err("Invalid random pattern".to_string())
334 );
335 }
336
337 #[test]
338 fn test_invalid_patterns() {
339 assert!(TaskPattern::parse("invalid").is_err());
340 assert!(TaskPattern::parse("10..5").is_err());
341 assert!(TaskPattern::parse("5:10..5").is_err());
342 }
343}