package main import ( "encoding/json" "flag" "io/ioutil" "log" "sort" ) /*************************** meeting.go ***************************/ type Invitee struct { Id int `json:"id"` Rank int `json:"rank"` } type Meeting struct { MeetingId int `json:"meeting_id"` OrganiserId int `json:"organiser_id"` Rank int `json:"rank"` BeginTime int64 `json:"begin_time"` EndTime int64 `json:"end_time"` Invitees []*Invitee `json:"invitees"` } func (m *Meeting) AverageInviteeRank() float64 { sum := 0 for _, i := range m.Invitees { sum += i.Rank } return float64(sum) / float64(len(m.Invitees)) } func IsConflicting(m1, m2 *Meeting) bool { if m1.BeginTime < m2.BeginTime { return m1.EndTime > m2.BeginTime } else { return m2.EndTime > m1.BeginTime } } // for sorting based on meeting begin time type MeetingSlice []*Meeting func (ms MeetingSlice) Len() int { return len(ms) } func (ms MeetingSlice) Swap(i, j int) { ms[i], ms[j] = ms[j], ms[i] } func (ms MeetingSlice) Less(i, j int) bool { return ms[i].BeginTime < ms[j].BeginTime } // for sorting based on meeting priority type MeetingPrioritySlice struct { Meetings []*Meeting CRE ConflictResolutionEngine OwnerId int } func (mps MeetingPrioritySlice) Len() int { return len(mps.Meetings) } func (mps MeetingPrioritySlice) Swap(i, j int) { mps.Meetings[i], mps.Meetings[j] = mps.Meetings[j], mps.Meetings[i] } func (mps MeetingPrioritySlice) Less(i, j int) bool { return mps.CRE.Less(mps.OwnerId, mps.Meetings[i], mps.Meetings[j]) } /*************************** calendar.go ***************************/ type Calendar struct { OwnerId int `json:"owner_id"` Meetings []*Meeting `json:"meetings` } type EmptySlot struct { BeginTime int64 `json:"begin_time"` EndTime int64 `json:"end_time"` } func (c *Calendar) findConflicts() [][]*Meeting { conflicts := make([][]*Meeting, 0) for i := 0; i < len(c.Meetings); i++ { for j := i + 1; j < len(c.Meetings); j++ { if IsConflicting(c.Meetings[i], c.Meetings[j]) { conflicts = append(conflicts, []*Meeting{c.Meetings[i], c.Meetings[j]}) } } } return conflicts } // TODO: we currently modify the list of meetings in a // calendar to the resolved list of meetings func (c *Calendar) resolveConflicts(cre ConflictResolutionEngine) []*Meeting { mps := MeetingPrioritySlice{ Meetings: c.Meetings, CRE: cre, OwnerId: c.OwnerId, } sort.Sort(mps) for i := 0; i < len(c.Meetings); i++ { if c.Meetings[i] == nil { continue } for j := i + 1; j < len(c.Meetings); j++ { if c.Meetings[j] == nil { continue } if IsConflicting(c.Meetings[i], c.Meetings[j]) { c.Meetings[j] = nil } } } resolved_meetings := make([]*Meeting, 0) for i := 0; i < len(c.Meetings); i++ { if c.Meetings[i] != nil { // TODO: pointer copy only! resolved_meetings = append(resolved_meetings, c.Meetings[i]) } } c.Meetings = resolved_meetings return c.Meetings } func (c *Calendar) FindFreeSlots() []*EmptySlot { sort.Sort(MeetingSlice(c.Meetings)) empty_slots := make([]*EmptySlot, 0) for i := 1; i < len(c.Meetings); i++ { if c.Meetings[i-1].EndTime != c.Meetings[i].BeginTime { empty_slots = append(empty_slots, &EmptySlot{ BeginTime: c.Meetings[i-1].EndTime, EndTime: c.Meetings[i].BeginTime, }) } } return empty_slots } /*************************** engines.go ***************************/ // return -1, 0, 1 type Rule func(int, *Meeting, *Meeting) int type ConflictResolutionEngine []Rule // lower the value, higher the priority func (cre ConflictResolutionEngine) Less(owner_id int, m1, m2 *Meeting) bool { for _, rule := range cre { ret := rule(owner_id, m1, m2) if ret == 1 { return true } else if ret == -1 { return false } } // if no decision yet, simply return true return true } func Rule1(owner_id int, m1, m2 *Meeting) int { if m1.OrganiserId == m2.OrganiserId { return 0 } else { if m1.OrganiserId == owner_id { return 1 } else if m2.OrganiserId == owner_id { return -1 } else { return 0 } } } func Rule2(owner_id int, m1, m2 *Meeting) int { if m1.Rank == m2.Rank { return 0 } else if m1.Rank < m2.Rank { return 1 } else { return -1 } } func Rule3(owner_id int, m1, m2 *Meeting) int { if len(m1.Invitees) == len(m2.Invitees) { return 0 } else if len(m1.Invitees) > len(m2.Invitees) { return 1 } else { return -1 } } func Rule4(owner_id int, m1, m2 *Meeting) int { m1_avg_rank := m1.AverageInviteeRank() m2_avg_rank := m2.AverageInviteeRank() if m1_avg_rank == m2_avg_rank { return 0 } else if m1_avg_rank < m2_avg_rank { return 1 } else { return -1 } } /*************************** input.json ***************************/ // { // "cmd": 1, // "engine": "simple", // "calendars": [ // { // "owner_id": 1, // "meetings": [ // { // "meeting_id": 1, // "organiser_id": 1, // "rank": 1, // "begin_time": 23, // "end_time": 34, // "invitees": [ // { // "id": 1, // "rank": 1 // } // ] // }, // { // "meeting_id": 2, // "organiser_id": 1, // "rank": 1, // "begin_time": 26, // "end_time": 35, // "invitees": [ // { // "id": 1, // "rank": 1 // } // ] // } // ] // } // ] // } /************************** main.go **************************/ type Command struct { Cmd int `json:"cmd"` Engine string `json:"engine"` Calendars []*Calendar `json:"calendars"` } func main() { output_file := flag.String("o", "output.json", "output file to dump data into") input_file := flag.String("i", "input.json", "input file to read json data from") flag.Parse() jdata, err := ioutil.ReadFile(*input_file) if err != nil { log.Println(err) log.Fatalf("[ERROR] unable to read input file %s", *input_file) } var command Command err = json.Unmarshal(jdata, &command) if err != nil { log.Println(err) log.Fatal("[ERROR] unable to parse input file") } // define engines SimpleEngine := ConflictResolutionEngine([]Rule{Rule1, Rule2, Rule3}) ComplexEngine := ConflictResolutionEngine([]Rule{Rule1, Rule2, Rule3, Rule4}) var cre ConflictResolutionEngine if command.Engine == "simple" { cre = SimpleEngine } else if command.Engine == "complex" { cre = ComplexEngine } else { log.Fatal("[ERROR] unknown conflict resolution engine!") } switch command.Cmd { case 1: // find list of conflicting meetings, given a calendar calendar := command.Calendars[0] data, err := json.MarshalIndent(calendar.findConflicts(), "", " ") if err != nil { log.Println(err) log.Fatal("[ERROR] error in marshaling the conflicting meetings") } ioutil.WriteFile(*output_file, data, 0644) case 2: // find resolved calendar, given a calendar calendar := command.Calendars[0] data, err := json.MarshalIndent(calendar.resolveConflicts(cre), "", " ") if err != nil { log.Println(err) log.Fatal("[ERROR] error in marshaling the resolved calendar") } ioutil.WriteFile(*output_file, data, 0644) case 3: // find free slots in a calendar calendar := command.Calendars[0] calendar.resolveConflicts(cre) data, err := json.MarshalIndent(calendar.FindFreeSlots(), "", " ") if err != nil { log.Println(err) log.Fatal("[ERROR] error in marshaling free meeting slots") } ioutil.WriteFile(*output_file, data, 0644) case 4: // find common free slot of given time in 2 given calendar } log.Println("[_INFO] stored output in", *output_file) }