Functional Weave
Code in TypeScript

education.timetable-clash@1.0.0

impl/rust.rs

4,326 bytes · the Rust implementation · view raw

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())
}