solverforge_core/score/
bendable.rs

1use super::Score;
2use crate::SolverForgeError;
3use rust_decimal::Decimal;
4use serde::{Deserialize, Serialize};
5use std::cmp::Ordering;
6use std::fmt;
7use std::ops::{Add, Neg, Sub};
8
9#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
10pub struct BendableScore {
11    pub hard_scores: Vec<i64>,
12    pub soft_scores: Vec<i64>,
13}
14
15impl BendableScore {
16    pub fn of(hard_scores: Vec<i64>, soft_scores: Vec<i64>) -> Self {
17        Self {
18            hard_scores,
19            soft_scores,
20        }
21    }
22
23    pub fn zero(hard_levels: usize, soft_levels: usize) -> Self {
24        Self {
25            hard_scores: vec![0; hard_levels],
26            soft_scores: vec![0; soft_levels],
27        }
28    }
29
30    pub fn of_hard(hard_level: usize, hard_levels: usize, soft_levels: usize, score: i64) -> Self {
31        let mut hard_scores = vec![0; hard_levels];
32        if hard_level < hard_levels {
33            hard_scores[hard_level] = score;
34        }
35        Self {
36            hard_scores,
37            soft_scores: vec![0; soft_levels],
38        }
39    }
40
41    pub fn of_soft(soft_level: usize, hard_levels: usize, soft_levels: usize, score: i64) -> Self {
42        let mut soft_scores = vec![0; soft_levels];
43        if soft_level < soft_levels {
44            soft_scores[soft_level] = score;
45        }
46        Self {
47            hard_scores: vec![0; hard_levels],
48            soft_scores,
49        }
50    }
51
52    pub fn hard_levels_size(&self) -> usize {
53        self.hard_scores.len()
54    }
55
56    pub fn soft_levels_size(&self) -> usize {
57        self.soft_scores.len()
58    }
59
60    pub fn parse(text: &str) -> Result<Self, SolverForgeError> {
61        let text = text.trim();
62
63        // Format: [h0/h1/.../hn]hard/[s0/s1/.../sm]soft
64        let hard_end = text.find("]hard/[").ok_or_else(|| {
65            SolverForgeError::Serialization(format!("Invalid BendableScore format: {}", text))
66        })?;
67
68        let hard_part = &text[1..hard_end];
69        let soft_start = hard_end + 7;
70        let soft_end = text.len() - 5;
71        let soft_part = &text[soft_start..soft_end];
72
73        let hard_scores = if hard_part.is_empty() {
74            vec![]
75        } else {
76            hard_part
77                .split('/')
78                .map(|s| s.trim().parse::<i64>())
79                .collect::<Result<Vec<_>, _>>()
80                .map_err(|e| {
81                    SolverForgeError::Serialization(format!("Invalid hard score: {}", e))
82                })?
83        };
84
85        let soft_scores = if soft_part.is_empty() {
86            vec![]
87        } else {
88            soft_part
89                .split('/')
90                .map(|s| s.trim().parse::<i64>())
91                .collect::<Result<Vec<_>, _>>()
92                .map_err(|e| {
93                    SolverForgeError::Serialization(format!("Invalid soft score: {}", e))
94                })?
95        };
96
97        Ok(Self {
98            hard_scores,
99            soft_scores,
100        })
101    }
102}
103
104impl Score for BendableScore {
105    fn is_feasible(&self) -> bool {
106        self.hard_scores.iter().all(|&s| s >= 0)
107    }
108
109    fn is_solution_initialized(&self) -> bool {
110        true
111    }
112
113    fn zero() -> Self {
114        Self {
115            hard_scores: vec![],
116            soft_scores: vec![],
117        }
118    }
119
120    fn negate(&self) -> Self {
121        Self {
122            hard_scores: self.hard_scores.iter().map(|&s| -s).collect(),
123            soft_scores: self.soft_scores.iter().map(|&s| -s).collect(),
124        }
125    }
126
127    fn add(&self, other: &Self) -> Self {
128        Self {
129            hard_scores: self
130                .hard_scores
131                .iter()
132                .zip(other.hard_scores.iter())
133                .map(|(&a, &b)| a + b)
134                .collect(),
135            soft_scores: self
136                .soft_scores
137                .iter()
138                .zip(other.soft_scores.iter())
139                .map(|(&a, &b)| a + b)
140                .collect(),
141        }
142    }
143
144    fn subtract(&self, other: &Self) -> Self {
145        Self {
146            hard_scores: self
147                .hard_scores
148                .iter()
149                .zip(other.hard_scores.iter())
150                .map(|(&a, &b)| a - b)
151                .collect(),
152            soft_scores: self
153                .soft_scores
154                .iter()
155                .zip(other.soft_scores.iter())
156                .map(|(&a, &b)| a - b)
157                .collect(),
158        }
159    }
160}
161
162impl PartialOrd for BendableScore {
163    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
164        Some(self.cmp(other))
165    }
166}
167
168impl Ord for BendableScore {
169    fn cmp(&self, other: &Self) -> Ordering {
170        for (a, b) in self.hard_scores.iter().zip(other.hard_scores.iter()) {
171            match a.cmp(b) {
172                Ordering::Equal => continue,
173                ord => return ord,
174            }
175        }
176        for (a, b) in self.soft_scores.iter().zip(other.soft_scores.iter()) {
177            match a.cmp(b) {
178                Ordering::Equal => continue,
179                ord => return ord,
180            }
181        }
182        Ordering::Equal
183    }
184}
185
186impl fmt::Display for BendableScore {
187    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
188        let hard = self
189            .hard_scores
190            .iter()
191            .map(|s| s.to_string())
192            .collect::<Vec<_>>()
193            .join("/");
194        let soft = self
195            .soft_scores
196            .iter()
197            .map(|s| s.to_string())
198            .collect::<Vec<_>>()
199            .join("/");
200        write!(f, "[{}]hard/[{}]soft", hard, soft)
201    }
202}
203
204impl Add for BendableScore {
205    type Output = Self;
206    fn add(self, other: Self) -> Self {
207        Score::add(&self, &other)
208    }
209}
210
211impl Sub for BendableScore {
212    type Output = Self;
213    fn sub(self, other: Self) -> Self {
214        Score::subtract(&self, &other)
215    }
216}
217
218impl Neg for BendableScore {
219    type Output = Self;
220    fn neg(self) -> Self {
221        Score::negate(&self)
222    }
223}
224
225// BendableDecimalScore
226
227#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
228pub struct BendableDecimalScore {
229    pub hard_scores: Vec<Decimal>,
230    pub soft_scores: Vec<Decimal>,
231}
232
233impl BendableDecimalScore {
234    pub fn of(hard_scores: Vec<Decimal>, soft_scores: Vec<Decimal>) -> Self {
235        Self {
236            hard_scores,
237            soft_scores,
238        }
239    }
240
241    pub fn zero(hard_levels: usize, soft_levels: usize) -> Self {
242        Self {
243            hard_scores: vec![Decimal::ZERO; hard_levels],
244            soft_scores: vec![Decimal::ZERO; soft_levels],
245        }
246    }
247
248    pub fn hard_levels_size(&self) -> usize {
249        self.hard_scores.len()
250    }
251
252    pub fn soft_levels_size(&self) -> usize {
253        self.soft_scores.len()
254    }
255
256    pub fn parse(text: &str) -> Result<Self, SolverForgeError> {
257        let text = text.trim();
258
259        let hard_end = text.find("]hard/[").ok_or_else(|| {
260            SolverForgeError::Serialization(format!(
261                "Invalid BendableDecimalScore format: {}",
262                text
263            ))
264        })?;
265
266        let hard_part = &text[1..hard_end];
267        let soft_start = hard_end + 7;
268        let soft_end = text.len() - 5;
269        let soft_part = &text[soft_start..soft_end];
270
271        let hard_scores = if hard_part.is_empty() {
272            vec![]
273        } else {
274            hard_part
275                .split('/')
276                .map(|s| s.trim().parse::<Decimal>())
277                .collect::<Result<Vec<_>, _>>()
278                .map_err(|e| {
279                    SolverForgeError::Serialization(format!("Invalid hard score: {}", e))
280                })?
281        };
282
283        let soft_scores = if soft_part.is_empty() {
284            vec![]
285        } else {
286            soft_part
287                .split('/')
288                .map(|s| s.trim().parse::<Decimal>())
289                .collect::<Result<Vec<_>, _>>()
290                .map_err(|e| {
291                    SolverForgeError::Serialization(format!("Invalid soft score: {}", e))
292                })?
293        };
294
295        Ok(Self {
296            hard_scores,
297            soft_scores,
298        })
299    }
300}
301
302impl Score for BendableDecimalScore {
303    fn is_feasible(&self) -> bool {
304        self.hard_scores.iter().all(|&s| s >= Decimal::ZERO)
305    }
306
307    fn is_solution_initialized(&self) -> bool {
308        true
309    }
310
311    fn zero() -> Self {
312        Self {
313            hard_scores: vec![],
314            soft_scores: vec![],
315        }
316    }
317
318    fn negate(&self) -> Self {
319        Self {
320            hard_scores: self.hard_scores.iter().map(|&s| -s).collect(),
321            soft_scores: self.soft_scores.iter().map(|&s| -s).collect(),
322        }
323    }
324
325    fn add(&self, other: &Self) -> Self {
326        Self {
327            hard_scores: self
328                .hard_scores
329                .iter()
330                .zip(other.hard_scores.iter())
331                .map(|(&a, &b)| a + b)
332                .collect(),
333            soft_scores: self
334                .soft_scores
335                .iter()
336                .zip(other.soft_scores.iter())
337                .map(|(&a, &b)| a + b)
338                .collect(),
339        }
340    }
341
342    fn subtract(&self, other: &Self) -> Self {
343        Self {
344            hard_scores: self
345                .hard_scores
346                .iter()
347                .zip(other.hard_scores.iter())
348                .map(|(&a, &b)| a - b)
349                .collect(),
350            soft_scores: self
351                .soft_scores
352                .iter()
353                .zip(other.soft_scores.iter())
354                .map(|(&a, &b)| a - b)
355                .collect(),
356        }
357    }
358}
359
360impl PartialOrd for BendableDecimalScore {
361    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
362        Some(self.cmp(other))
363    }
364}
365
366impl Ord for BendableDecimalScore {
367    fn cmp(&self, other: &Self) -> Ordering {
368        for (a, b) in self.hard_scores.iter().zip(other.hard_scores.iter()) {
369            match a.cmp(b) {
370                Ordering::Equal => continue,
371                ord => return ord,
372            }
373        }
374        for (a, b) in self.soft_scores.iter().zip(other.soft_scores.iter()) {
375            match a.cmp(b) {
376                Ordering::Equal => continue,
377                ord => return ord,
378            }
379        }
380        Ordering::Equal
381    }
382}
383
384impl fmt::Display for BendableDecimalScore {
385    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
386        let hard = self
387            .hard_scores
388            .iter()
389            .map(|s| s.to_string())
390            .collect::<Vec<_>>()
391            .join("/");
392        let soft = self
393            .soft_scores
394            .iter()
395            .map(|s| s.to_string())
396            .collect::<Vec<_>>()
397            .join("/");
398        write!(f, "[{}]hard/[{}]soft", hard, soft)
399    }
400}
401
402impl Add for BendableDecimalScore {
403    type Output = Self;
404    fn add(self, other: Self) -> Self {
405        Score::add(&self, &other)
406    }
407}
408
409impl Sub for BendableDecimalScore {
410    type Output = Self;
411    fn sub(self, other: Self) -> Self {
412        Score::subtract(&self, &other)
413    }
414}
415
416impl Neg for BendableDecimalScore {
417    type Output = Self;
418    fn neg(self) -> Self {
419        Score::negate(&self)
420    }
421}
422
423#[cfg(test)]
424mod tests {
425    use super::*;
426
427    mod bendable {
428        use super::*;
429
430        #[test]
431        fn test_of() {
432            let score = BendableScore::of(vec![-1, 0], vec![10, 20, 30]);
433            assert_eq!(score.hard_scores, vec![-1, 0]);
434            assert_eq!(score.soft_scores, vec![10, 20, 30]);
435        }
436
437        #[test]
438        fn test_zero() {
439            let score = BendableScore::zero(2, 3);
440            assert_eq!(score.hard_scores, vec![0, 0]);
441            assert_eq!(score.soft_scores, vec![0, 0, 0]);
442        }
443
444        #[test]
445        fn test_levels_size() {
446            let score = BendableScore::of(vec![1, 2], vec![3, 4, 5]);
447            assert_eq!(score.hard_levels_size(), 2);
448            assert_eq!(score.soft_levels_size(), 3);
449        }
450
451        #[test]
452        fn test_is_feasible() {
453            assert!(BendableScore::of(vec![0, 0], vec![-100]).is_feasible());
454            assert!(BendableScore::of(vec![1, 0], vec![-100]).is_feasible());
455            assert!(!BendableScore::of(vec![-1, 0], vec![100]).is_feasible());
456            assert!(!BendableScore::of(vec![0, -1], vec![100]).is_feasible());
457        }
458
459        #[test]
460        fn test_comparison() {
461            assert!(
462                BendableScore::of(vec![1, 0], vec![0]) > BendableScore::of(vec![0, 100], vec![100])
463            );
464            assert!(
465                BendableScore::of(vec![0, 1], vec![0]) > BendableScore::of(vec![0, 0], vec![100])
466            );
467            assert!(
468                BendableScore::of(vec![0, 0], vec![10]) > BendableScore::of(vec![0, 0], vec![5])
469            );
470        }
471
472        #[test]
473        fn test_arithmetic() {
474            let a = BendableScore::of(vec![-2, 1], vec![10, 20]);
475            let b = BendableScore::of(vec![-1, 1], vec![5, 10]);
476
477            let sum = a.clone() + b.clone();
478            assert_eq!(sum.hard_scores, vec![-3, 2]);
479            assert_eq!(sum.soft_scores, vec![15, 30]);
480
481            let diff = a.clone() - b;
482            assert_eq!(diff.hard_scores, vec![-1, 0]);
483            assert_eq!(diff.soft_scores, vec![5, 10]);
484
485            let neg = -a;
486            assert_eq!(neg.hard_scores, vec![2, -1]);
487            assert_eq!(neg.soft_scores, vec![-10, -20]);
488        }
489
490        #[test]
491        fn test_parse() {
492            let score = BendableScore::parse("[-1/0]hard/[10/20/30]soft").unwrap();
493            assert_eq!(score.hard_scores, vec![-1, 0]);
494            assert_eq!(score.soft_scores, vec![10, 20, 30]);
495
496            let empty = BendableScore::parse("[]hard/[]soft").unwrap();
497            assert!(empty.hard_scores.is_empty());
498            assert!(empty.soft_scores.is_empty());
499        }
500
501        #[test]
502        fn test_display() {
503            let score = BendableScore::of(vec![-1, 0], vec![10, 20]);
504            assert_eq!(format!("{}", score), "[-1/0]hard/[10/20]soft");
505        }
506
507        #[test]
508        fn test_json_serialization() {
509            let score = BendableScore::of(vec![-1, 0], vec![10, 20]);
510            let json = serde_json::to_string(&score).unwrap();
511            let parsed: BendableScore = serde_json::from_str(&json).unwrap();
512            assert_eq!(parsed, score);
513        }
514    }
515
516    mod bendable_decimal {
517        use super::*;
518
519        #[test]
520        fn test_of() {
521            let score = BendableDecimalScore::of(
522                vec![Decimal::new(-10, 1), Decimal::ZERO],
523                vec![Decimal::new(100, 1)],
524            );
525            assert_eq!(score.hard_scores.len(), 2);
526            assert_eq!(score.soft_scores.len(), 1);
527        }
528
529        #[test]
530        fn test_is_feasible() {
531            assert!(
532                BendableDecimalScore::of(vec![Decimal::ZERO], vec![Decimal::new(-100, 0)])
533                    .is_feasible()
534            );
535            assert!(!BendableDecimalScore::of(vec![Decimal::new(-1, 0)], vec![]).is_feasible());
536        }
537
538        #[test]
539        fn test_parse() {
540            let score = BendableDecimalScore::parse("[-1.5/0]hard/[10.25]soft").unwrap();
541            assert_eq!(score.hard_scores, vec![Decimal::new(-15, 1), Decimal::ZERO]);
542            assert_eq!(score.soft_scores, vec![Decimal::new(1025, 2)]);
543        }
544
545        #[test]
546        fn test_display() {
547            let score = BendableDecimalScore::of(
548                vec![Decimal::new(-15, 1), Decimal::ZERO],
549                vec![Decimal::new(1025, 2)],
550            );
551            assert_eq!(format!("{}", score), "[-1.5/0]hard/[10.25]soft");
552        }
553    }
554}