use super::funejson::Value; fn shared(a: &[String], b: &[String]) -> Vec { let mut out: Vec = Vec::new(); for x in a { if b.contains(x) && !out.contains(x) { out.push(x.clone()); } } out } /// Every pair of sessions that overlap on the same day and share a room, /// teacher or group. Times are half-open: a session ending at 600 does not /// clash with one starting at 600. /// /// # Panics /// Panics on a repeated id, a day below 1, or a start or end out of range. pub fn timetable_clashes(sessions: &[TimetableSession]) -> Vec { for (k, s) in sessions.iter().enumerate() { if sessions[..k].iter().any(|o| o.id == s.id) { panic!("session id \"{}\" appears twice", s.id); } if s.day < 1 { panic!("day must be a whole number of 1 or more, received {} for \"{}\"", s.day, s.id); } if s.start < 0 || s.start > 1439 { panic!("start must be a whole minute from 0 to 1439, received {} for \"{}\"", s.start, s.id); } if s.end <= s.start || s.end > 1440 { panic!( "end must be a whole minute after start and no later than 1440, received {} for \"{}\"", s.end, s.id ); } } let mut found: Vec<(i64, i64, usize, usize, TimetableClash)> = Vec::new(); for i in 0..sessions.len() { for j in (i + 1)..sessions.len() { let (a, b) = (&sessions[i], &sessions[j]); if a.day != b.day { continue; } let start = a.start.max(b.start); let end = a.end.min(b.end); if start >= end { continue; } let rooms = shared(&a.rooms, &b.rooms); let teachers = shared(&a.teachers, &b.teachers); let groups = shared(&a.groups, &b.groups); if rooms.is_empty() && teachers.is_empty() && groups.is_empty() { continue; } found.push(( a.day, start, i, j, TimetableClash { first: a.id.clone(), second: b.id.clone(), day: a.day, start, end, rooms, teachers, groups, }, )); } } found.sort_by(|x, y| (x.0, x.1, x.2, x.3).cmp(&(y.0, y.1, y.2, y.3))); found.into_iter().map(|f| f.4).collect() } fn whole(value: &Value, message: &str, id: &str) -> i64 { match value { Value::Float(f) if f.fract() != 0.0 => panic!("{}, received {} for \"{}\"", message, f, id), _ => value.as_i64(), } } fn strings(value: &Value) -> Vec { value.as_arr().iter().map(|v| v.as_str().to_string()).collect() } fn strings_to_value(list: &[String]) -> Value { Value::Arr(list.iter().map(|s| Value::str(s)).collect()) } pub fn timetable_clash_to_value(c: &TimetableClash) -> Value { Value::obj(vec![ ("first", Value::str(&c.first)), ("second", Value::str(&c.second)), ("day", Value::Int(c.day)), ("start", Value::Int(c.start)), ("end", Value::Int(c.end)), ("rooms", strings_to_value(&c.rooms)), ("teachers", strings_to_value(&c.teachers)), ("groups", strings_to_value(&c.groups)), ]) } pub fn fune_vector(args: &[Value]) -> Value { let sessions: Vec = args[0] .as_arr() .iter() .map(|v| { let id = v.get("id").as_str().to_string(); TimetableSession { day: whole(v.get("day"), "day must be a whole number of 1 or more", &id), start: whole(v.get("start"), "start must be a whole minute from 0 to 1439", &id), end: whole(v.get("end"), "end must be a whole minute after start and no later than 1440", &id), rooms: strings(v.get("rooms")), teachers: strings(v.get("teachers")), groups: strings(v.get("groups")), id, } }) .collect(); Value::Arr(timetable_clashes(&sessions).iter().map(timetable_clash_to_value).collect()) }