Skip to content

Instantly share code, notes, and snippets.

@mangalaman93
Created August 25, 2017 23:42
Show Gist options
  • Save mangalaman93/bba6675c3ca7273e27506b6a967664b1 to your computer and use it in GitHub Desktop.
Save mangalaman93/bba6675c3ca7273e27506b6a967664b1 to your computer and use it in GitHub Desktop.
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)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment