Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wrote Naive algorithm #5

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ doc = true
serde = "~1.0"
serde_derive = "~1.0"
serde_json = "~1.0"
itertools = "0.8.0"

[badges]
is-it-maintained-issue-resolution = { repository = "YACS-RCOS/Scheduler-rs" }
is-it-maintained-open-issues = { repository = "YACS-RCOS/Scheduler-rs" }
maintenance = { status = "actively-developed" }
maintenance = { status = "actively-developed" }
15 changes: 14 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
# Scheduler-rs
> *A university schedule solver written in Rust.*


*Part of [yacs](https://yacs.io/)*

[![Build Status](https://travis-ci.org/YACS-RCOS/Scheduler-rs.svg?branch=master)](
https://travis-ci.org/YACS-RCOS/Scheduler-rs)

[![Documentation](https://img.shields.io/badge/docs-blue.svg)](
https://yacs-rcos.github.io/Scheduler-rs/doc/scheduler/index.html)

Expand All @@ -22,3 +23,15 @@
https://www.rust-lang.org/)


### Contributing
- Download rust
- Make a fork
- Clone repository with `git clone https://github.com/YOUR_USERNAME/Scheduler-rs.git`
- Make edits
- Open pull request

### File Structure
- `lib.rs` - External API
- `solver.rs` - Does math to solve an arbitrary set of schedules
- `model.rs` - Internal API's to objects in library

7 changes: 7 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,10 @@ pub mod model;
/// This module contains methods for solving producing valid schedules based on
/// lists of Scheduleables. (See module level documentation)
pub mod solver;

/// ## The Private Models used in computation
pub mod models;

/// ## Solver module, with implementation using private library instead
/// of public serialization stuff
pub mod solve;
13 changes: 12 additions & 1 deletion src/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ pub type TimeUnit = u64;
/// - Fencing practice is an event.
#[derive(Eq, Serialize, Deserialize, Clone, Debug, Hash)]
pub struct Event {
/// UUID string provided by YACS
pub uuid: String,
/// Offset from the start of the owning scheduleable.
pub offset: TimeUnit,
/// Duration of event
pub duration: TimeUnit,
/// Time from start -> start of next event
pub repeat: TimeUnit,
Expand All @@ -26,7 +28,9 @@ pub struct Event {
/// scheduleable options.
#[derive(Clone, Eq, Serialize, Deserialize, Debug, Hash)]
pub struct ScheduleableOption {
/// UUID string provided by YACS
pub uuid: String,
/// Events in this option
pub events: Vec<Event>,
}

Expand All @@ -38,21 +42,28 @@ pub struct ScheduleableOption {
/// - A sport is a scheduleable.
#[derive(Clone, Eq, Serialize, Deserialize, Debug, Hash)]
pub struct Scheduleable {
/// UUID string provided by YACS
pub uuid: String,
/// Start time of schedule; maintain the invariant that the events of this
/// schedulable's options all happen at or after this time
pub start: TimeUnit,
/// Duration of this scheduleable.
/// This should be at least the difference between the start of this
/// scheduleable and the end of the last event or event repeat.
pub duration: TimeUnit,
/// potential permutations of this scheduleable
pub options: Vec<ScheduleableOption>,
}


/// Labeled ScheduleabelOption, used in solver.
#[derive(Clone, Eq, PartialEq, Debug, Hash)]
pub struct InfoScheduleableOption<'option_lifetime> {
/// Data of this struct
pub inner: &'option_lifetime ScheduleableOption,
/// Start from this struct's parent scheduleble
start: TimeUnit,
/// End of this struct's parent scheduleable
end: TimeUnit,
}

Expand Down Expand Up @@ -146,4 +157,4 @@ impl Event {
false
}

}
}
59 changes: 59 additions & 0 deletions src/models/event.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
use super::{ TimeUnit };
use std::cmp::PartialEq;

/// An event in a schedule. Conceptually, this is an ordered
/// pair of integers, the first corresponding to the beginning of
/// event, the second the end time. To save space and reduce
/// runtime, we store events as a start time, duration, and
/// a repeat interval. Thus, an event struct can represent
/// multiple 'Events'.
///
/// Note that the internal representation is inclusive on the
/// start and exclusive on the end. That is, the end of the event
/// is the start + duration, and the end of one event can equal
/// the `start` of another without being incompatible
///
#[derive(Clone, Copy, Debug, Eq)]
pub struct Event {
/// Start of event
start: TimeUnit,
/// Duration of the event
duration: TimeUnit,
}

impl PartialEq for Event {

fn eq(&self, other: &Self) -> bool {
other.start == self.start
&& other.duration == self.duration
}
}

impl Event {

/// Create a new event
pub fn new(start: TimeUnit, duration: TimeUnit) -> Event {
Event { start, duration }
}

/// Returns true if this event contains data which is valid
pub fn is_valid(&self) -> bool {
true
}

/// Returns the time that this event starts at
pub fn start(&self) -> TimeUnit {
self.start
}

/// Returns the time this event goes on for
pub fn duration(&self) -> TimeUnit {
self.duration
}

/// Returns the time that this event ends at
pub fn end(&self) -> TimeUnit {
self.duration + self.start
}
}

19 changes: 19 additions & 0 deletions src/models/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/// Time type, whose value corresponds to a Unix Timestamp
pub type TimeUnit = u64;
/// Time unit that represents forever from now
pub const FOREVER: TimeUnit = std::u64::MAX;
/// A second
pub const SECOND: TimeUnit = 1;
/// A minute
pub const MINUTE: TimeUnit = 60 * SECOND;
/// An hour
pub const HOUR: TimeUnit = 60 * MINUTE;
/// A week
pub const WEEK: TimeUnit = 7 * HOUR;

mod event;
mod schedule;
pub use crate::model::*;
pub use crate::model::Event as ApiEvent;
pub use self::event::Event;
pub use self::schedule::Schedule;
144 changes: 144 additions & 0 deletions src/models/schedule.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
use super::*;
use std::cmp::PartialEq;
use itertools::Itertools;
use std::collections::HashMap;

/// State of a schedule, i.e. whether the schedule
/// has been validated, sorted, etc.
/// Hierarchy is:
/// Unchecked means haven't been sorted or validated
/// Sorted means haven't validated, but have sorted
/// Invalid/Valid means has been sorted, and validated
#[derive(Clone, Copy, Debug, Eq)]
enum ScheduleState {
Invalid,
Valid,
Unchecked,
Sorted,
}

impl PartialEq for ScheduleState {

fn eq(&self, other: &Self) -> bool {
return self == other
}
}

/// An unordered set of events.
/// Underlying data structure is sorted
/// for runtime efficiency
///
#[derive(Clone, Debug, Eq)]
pub struct Schedule {
/// Universally unique id for this schedule
pub uuids: Vec<String>,
/// Events in this schedule
pub events: Vec<Event>,
state: ScheduleState,
}

impl PartialEq for Schedule {
fn eq(&self, other: &Self) -> bool {
self.uuids == other.uuids
}
}

impl Schedule {

/// New Schedule Object
pub fn new(uuid: String, events: Vec<Event>) -> Schedule {
let uuids = vec![uuid];
Schedule {
uuids,
events,
state: ScheduleState::Unchecked,
}
}

/// New Schedule as composition of other schedules
/// Schedule returned will already be sorted by time,
/// but will NOT be validated.
pub fn compose(schedules: &Vec<&Schedule>) -> Schedule {

let mut uuid_count = 0;
let mut unique_uuids: HashMap<String,u16> = HashMap::new();
let mut valid = true;

let uuids: Vec<String> = schedules.iter()
.map(|sched| {
if sched.state == ScheduleState::Invalid {
valid = false;
}
let id_list = sched.uuids.clone();
for id in id_list.iter() { // Store unique id's found
unique_uuids.insert(id.to_string(), 0);
}
uuid_count += id_list.len(); // to compare against total found
id_list
}).kmerge()
.collect();

if uuid_count > uuids.len() {
valid = false;
}

if valid {
let events: Vec<Event> = schedules.iter()
.map(|sched| sched.events.clone())
.kmerge_by(|a, b| a.start() < b.start())
.collect();

Self {
uuids,
events,
state: ScheduleState::Sorted,
}
} else {
Self {
uuids,
events: Vec::new(),
state: ScheduleState::Invalid,
}
}
}

/// Returns whether or not this object is valid
pub fn is_valid(&mut self) -> bool {
use ScheduleState::*;
match self.state {
Invalid => false,
Valid => true,
Unchecked => {
self.sort_items();
self.validate_sorted()
},
Sorted => {
self.validate_sorted()
}
}
}

/// Validates a sorted schedule
fn validate_sorted(&mut self) -> bool {
use ScheduleState::*;
let mut valid = true;
let events = &self.events;

// Pairwise iterate through and check if the event finish times are in
// chronological order
for (f1, b2) in events.windows(2).map(|a| (a[0].end(), a[1].start())) {
if f1 > b2 {
valid = false;
break;
}
}

self.state = if valid { Valid } else { Invalid };
valid
}

fn sort_items(&mut self) {
self.events.sort_by(|a, b| a.start().cmp(&b.start()));
}

}
22 changes: 22 additions & 0 deletions src/solve.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
use crate::models::{Schedule};

/// Naively solves for all possibilities.
/// We can later filter the output to what we want,
/// but this naive implementation is guarranteed to be correct
pub fn solve_naive(schedules: &Vec<Schedule>) -> Vec<Schedule> {
let mut output = schedules.clone();

for input_schedule in schedules {
let mut running = vec![];
for schedule in output.iter() {
let mut current = Schedule::compose(&vec![&schedule, &input_schedule]);
if current.is_valid() {
running.push(current);
}
}
output.append(&mut running);
}
output
}