soccer_table/
lib.rs

1mod parsing;
2mod table_row;
3
4use parsing::{list_relevant_files, read_lines, MatchResult};
5use std::collections::{hash_map::Entry, HashMap};
6use std::fmt::{Display, Error, Formatter};
7use std::path::Path;
8use table_row::TableRow;
9
10pub struct Table {
11    rows: Vec<TableRow>,
12}
13
14impl Table {
15    pub fn ranked(&self) -> Vec<TableRow> {
16        let mut rows = self.rows.clone();
17        rows.sort();
18        rows.iter_mut()
19            .enumerate()
20            .map(|(i, r)| {
21                r.rank = (i as u8) + 1;
22                r.clone()
23            })
24            .collect()
25    }
26}
27
28impl Display for Table {
29    fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
30        let title = format!(
31            "{:>3} {:30} {:>3} {:>3} {:>3} {:>3} {:>3} {:>3} {:>3}",
32            "#", "Team", "P", "W", "T", "L", "+", "-", "="
33        );
34        let separator = "-".repeat(title.chars().count());
35        f.write_str(&format!("{}\n", &title))?;
36        f.write_fmt(format_args!("{}\n", separator))?;
37        let rows = self.ranked();
38        for r in rows {
39            f.write_fmt(format_args!(
40                "{:>3} {:30} {:>3} {:>3} {:>3} {:>3} {:>3} {:>3} {:>3}\n",
41                r.rank,
42                r.team,
43                r.points,
44                r.wins,
45                r.defeats,
46                r.ties,
47                r.goals_scored,
48                r.goals_conceded,
49                r.goals_diff
50            ))?;
51        }
52        Ok(())
53    }
54}
55
56pub fn compute_table(dir: &Path, day: Option<usize>) -> Result<Table, String> {
57    let mut lines: Vec<String> = Vec::new();
58    if let Ok(files) = list_relevant_files(dir, day) {
59        for p in files {
60            lines.append(&mut read_lines(&p))
61        }
62    }
63
64    let mut single_rows: Vec<TableRow> = Vec::new();
65    let results = MatchResult::parse_all(lines)?;
66    for result in results {
67        match result {
68            Ok(r) => {
69                let (home, away) = TableRow::from(r);
70                single_rows.push(home);
71                single_rows.push(away);
72            }
73            Err(e) => return Err(e),
74        }
75    }
76
77    let grouped = group_by_team(single_rows);
78    let res: Vec<Result<TableRow, String>> = grouped
79        .iter()
80        .map(|(k, v)| {
81            v.iter()
82                .try_fold(TableRow::new(k), |acc, r| acc.combine(r.clone()))
83        })
84        .collect();
85
86    let mut rows: Vec<TableRow> = Vec::new();
87    let mut errs: Vec<String> = Vec::new();
88    for r in res {
89        match r {
90            Ok(row) => rows.push(row),
91            Err(err) => errs.push(err),
92        }
93    }
94    if errs.is_empty() {
95        Ok(Table { rows })
96    } else {
97        Err(format!("combining results: {}", errs.join(", ")))
98    }
99}
100
101fn group_by_team(rows: Vec<TableRow>) -> HashMap<String, Vec<TableRow>> {
102    let mut rows_by_team: HashMap<String, Vec<TableRow>> = HashMap::new();
103    for row in rows {
104        match rows_by_team.entry(row.team.clone()) {
105            Entry::Occupied(mut e) => {
106                e.get_mut().push(row.clone());
107            }
108            Entry::Vacant(e) => {
109                e.insert(vec![row.clone()]);
110            }
111        }
112    }
113    rows_by_team
114}
115
116#[cfg(test)]
117mod tests {
118    use super::*;
119
120    #[test]
121    fn test_group_by_team() {
122        let rows = vec![
123            TableRow::new("A"),
124            TableRow::new("B"),
125            TableRow::new("C"),
126            TableRow::new("A"),
127            TableRow::new("B"),
128            TableRow::new("A"),
129        ];
130        let grouped = group_by_team(rows);
131        assert_eq!(grouped.get("A").unwrap().len(), 3);
132        assert_eq!(grouped.get("B").unwrap().len(), 2);
133        assert_eq!(grouped.get("C").unwrap().len(), 1);
134    }
135
136    #[test]
137    fn test_ranking() {
138        let names = vec!["A", "B", "C", "D", "E"];
139        let table = Table {
140            rows: names.iter().map(|n| TableRow::new(n)).collect(),
141        };
142        let ranked = table.ranked();
143        for i in 0..ranked.len() {
144            assert_eq!(ranked.get(i).unwrap().rank as usize, i + 1);
145        }
146    }
147}