Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
use super::funejson::Value; ← the fune runtime: the JSON value the test vectors use; fune build keeps it only where a signature takes one
fn shared(a: &[String], b: &[String]) -> Vec<String> {
let mut out: Vec<String> = 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<TimetableClash> {
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<String> {
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<TimetableSession> = 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())
}