mod cf_api;
mod dataset;
pub use dataset::{get_dataset_from_disk, CachedDataset, ClosureDataset, Dataset};
use reqwest::blocking::Client;
use serde::{Deserialize, Serialize};
use std::path::Path;
fn one() -> f64 {
1.0
}
fn is_one(&weight: &f64) -> bool {
weight == one()
}
#[derive(Serialize, Deserialize)]
pub struct Contest {
pub name: String,
pub url: Option<String>,
#[serde(default = "one", skip_serializing_if = "is_one")]
pub weight: f64,
pub time_seconds: u64,
pub standings: Vec<(String, usize, usize)>,
}
impl Contest {
pub fn new(index: usize) -> Self {
Self {
name: format!("Round #{}", index),
url: None,
weight: 1.,
time_seconds: index as u64 * 86_400,
standings: vec![],
}
}
pub fn find_contestant(&mut self, handle: &str) -> Option<usize> {
self.standings.iter().position(|x| x.0 == handle)
}
pub fn has_contestant(&mut self, handle: &str) -> bool {
self.find_contestant(handle).is_some()
}
pub fn remove_contestant(&mut self, handle: &str) -> Option<(String, usize, usize)> {
let pos = self.find_contestant(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));
}
}
#[derive(Serialize, Deserialize)]
pub struct ContestSummary {
pub name: String,
pub url: Option<String>,
pub weight: f64,
pub time_seconds: u64,
pub num_contestants: usize,
}
impl ContestSummary {
pub fn new(contest: &Contest) -> Self {
Self {
name: contest.name.clone(),
url: contest.url.clone(),
weight: contest.weight,
time_seconds: contest.time_seconds,
num_contestants: contest.standings.len(),
}
}
}
fn write_to_json<T: Serialize + ?Sized>(
value: &T,
path: impl AsRef<Path>,
) -> Result<(), &'static str> {
let cached_json = serde_json::to_string_pretty(&value).map_err(|_| "Serialization error")?;
std::fs::write(path.as_ref(), cached_json).map_err(|_| "File writing error")
}
fn write_to_csv<T: Serialize>(values: &[T], path: impl AsRef<Path>) -> Result<(), &'static str> {
let file = std::fs::File::create(path.as_ref()).map_err(|_| "Output file not found")?;
let mut writer = csv::Writer::from_writer(file);
values
.iter()
.try_for_each(|val| writer.serialize(val))
.map_err(|_| "Failed to serialize row")
}
pub fn write_slice_to_file<T: Serialize>(values: &[T], path: impl AsRef<Path>) {
let path = path.as_ref();
let write_res = match path.extension().and_then(|s| s.to_str()) {
Some("json") => write_to_json(values, path),
Some("csv") => write_to_csv(values, path),
_ => Err("Invalid or missing filename extension"),
};
match write_res {
Ok(()) => println!("Successfully wrote to {:?}", path),
Err(msg) => eprintln!("WARNING: failed write to {:?} because {}", path, msg),
};
}
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.weight, 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);
}
}