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}