1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
mod cf_api;
mod dataset;
pub use dataset::{get_dataset_from_disk, CachedDataset, ClosureDataset, Dataset};
use reqwest::blocking::Client;
use serde::{Deserialize, Serialize};
fn one() -> f64 {
1.0
}
fn is_one(&weight: &f64) -> bool {
weight == one()
}
#[derive(Serialize, Deserialize)]
pub struct Contest {
pub id: usize,
pub name: String,
pub time_seconds: u64,
pub standings: Vec<(String, usize, usize)>,
#[serde(default = "one", skip_serializing_if = "is_one")]
pub weight: f64,
}
impl Contest {
pub fn with_id(id: usize) -> Self {
Self {
id,
name: format!("Round #{}", id),
time_seconds: id as u64 * 86_400,
standings: vec![],
weight: 1.,
}
}
pub fn remove_contestant(&mut self, handle: &str) -> Option<(String, usize, usize)> {
let pos = self.standings.iter().position(|x| x.0 == handle)?;
let contestant = self.standings.remove(pos);
for (_, lo, hi) in self.standings.iter_mut() {
if *hi >= pos {
*hi -= 1;
if *lo > pos {
*lo -= 1;
}
}
}
Some(contestant)
}
pub fn push_contestant(&mut self, handle: impl Into<String>) {
let place = self.standings.len();
self.standings.push((handle.into(), place, place));
}
}
pub fn get_dataset_from_codeforces_api(
contest_id_file: impl AsRef<std::path::Path>,
) -> impl Dataset<Item = Contest> {
let client = Client::new();
let contests_json =
std::fs::read_to_string(contest_id_file).expect("Failed to read contest IDs from file");
let contest_ids: Vec<usize> = serde_json::from_str(&contests_json)
.expect("Failed to parse JSON contest IDs as a Vec<usize>");
dataset::ClosureDataset::new(contest_ids.len(), move |i| {
cf_api::fetch_cf_contest(&client, contest_ids[i])
})
}
pub fn get_dataset_by_name(
dataset_name: &str,
) -> Result<Box<dyn Dataset<Item = Contest> + Send + Sync>, String> {
const CF_IDS: &str = "../data/codeforces/contest_ids.json";
let dataset_dir = format!("../cache/{}", dataset_name);
Ok(if dataset_name == "codeforces" {
Box::new(get_dataset_from_codeforces_api(CF_IDS).cached(dataset_dir))
} else {
Box::new(get_dataset_from_disk(dataset_dir))
})
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_codeforces_data() {
let dataset = get_dataset_by_name("codeforces").unwrap();
let first_contest = dataset.get(0);
let first_winner = &first_contest.standings[0];
assert_eq!(first_contest.id, 1);
assert_eq!(first_contest.standings.len(), 66);
assert_eq!(first_winner.0, "vepifanov");
assert_eq!(first_winner.1, 0);
assert_eq!(first_winner.2, 0);
}
}