package main import ( "fmt" "sync" "time" "log/slog" ) // Participant struct type Participant struct { ID int Preferences []int AssignedTo int RejectedFrom map[int]bool mu sync.Mutex } // Workshop struct type Workshop struct { ID int Capacity int Participants []int mu sync.Mutex } // Concurrent Stable Matching Algorithm func StableMatch(participants []Participant, workshops []Workshop) { var wg sync.WaitGroup numWorkers := 10 tasks := make(chan *Participant, len(participants)) worker := func() { defer wg.Done() for p := range tasks { assignParticipant(p, &workshops, participants) } } for i := 0; i < numWorkers; i++ { wg.Add(1) go worker() } for i := range participants { tasks <- &participants[i] } close(tasks) wg.Wait() } // Assign a participant to a workshop func assignParticipant(p *Participant, workshops *[]Workshop, participants []Participant) { p.mu.Lock() defer p.mu.Unlock() assigned := false for _, workshopID := range p.Preferences { if p.RejectedFrom[workshopID] { continue } if workshopID-1 < 0 || workshopID-1 >= len(*workshops) { continue // Prevent index out of range } w := &(*workshops)[workshopID-1] w.mu.Lock() if len(w.Participants) < w.Capacity { p.AssignedTo = workshopID w.Participants = append(w.Participants, p.ID) w.mu.Unlock() assigned = true break } else { weakestID, weakestIndex := findWeakestParticipant(w.Participants, participants, workshopID) if weakestIndex != -1 && isPreferred(p.ID, weakestID, workshopID, participants) { replaceParticipant(weakestID, p, w, weakestIndex, participants) w.mu.Unlock() assigned = true break } else { p.RejectedFrom[workshopID] = true } } w.mu.Unlock() } if !assigned { for i := range *workshops { w := &(*workshops)[i] w.mu.Lock() if len(w.Participants) < w.Capacity { p.AssignedTo = w.ID w.Participants = append(w.Participants, p.ID) w.mu.Unlock() slog.Info("Participant is assigned to Workshop as fallback\n", slog.Int("ParticipantId", p.ID), slog.Int("WorkshopId", w.ID)) assigned = true break } w.mu.Unlock() } } if !assigned { slog.Info("āŒ Participant could not be assigned\n", slog.Int("ParticipantId", p.ID)) p.AssignedTo = -1 } } // Replace weakest participant in a workshop func replaceParticipant(weakestID int, newP *Participant, w *Workshop, weakestIndex int, participants []Participant) { for i, id := range w.Participants { if id == weakestID { w.Participants = append(w.Participants[:i], w.Participants[i+1:]...) break } } w.Participants[weakestIndex] = newP.ID newP.AssignedTo = w.ID participants[weakestID-1].AssignedTo = -1 participants[weakestID-1].RejectedFrom[w.ID] = true } // Find the weakest participant in a workshop func findWeakestParticipant(participantIDs []int, participants []Participant, workshopID int) (int, int) { weakestID := -1 weakestIndex := -1 lowestRank := 4 for i, id := range participantIDs { if id-1 < 0 || id-1 >= len(participants) { continue } for rank, pref := range participants[id-1].Preferences { if pref == workshopID && rank < lowestRank { lowestRank = rank weakestID = id weakestIndex = i } } } return weakestID, weakestIndex } // Check if participant A is preferred over B for a workshop func isPreferred(aID, bID, workshopID int, participants []Participant) bool { if aID-1 < 0 || aID-1 >= len(participants) || bID-1 < 0 || bID-1 >= len(participants) { return false } aRank, bRank := 4, 4 for rank, pref := range participants[aID-1].Preferences { if pref == workshopID { aRank = rank } } for rank, pref := range participants[bID-1].Preferences { if pref == workshopID { bRank = rank } } return aRank < bRank } // Print final assignments func PrintAssignments(participants []Participant, workshops []Workshop) { fmt.Println("\nFinal Assignments:") for _, p := range participants { fmt.Printf("Participant %d → Workshop %d\n", p.ID, p.AssignedTo) } fmt.Println("\nWorkshops and their Participants:") for _, w := range workshops { fmt.Printf("Workshop %d: %v\n", w.ID, w.Participants) } } // Print statistics func PrintStatistics(participants []Participant, workshops []Workshop, duration time.Duration) { totalParticipants := len(participants) totalWorkshops := len(workshops) assignedCount := 0 unassignedCount := 0 firstChoiceCount := 0 secondChoiceCount := 0 thirdChoiceCount := 0 fallbackCount := 0 // Count participant assignment stats for _, p := range participants { if p.AssignedTo != -1 { assignedCount++ // Check which preference was assigned if p.AssignedTo == p.Preferences[0] { firstChoiceCount++ } else if p.AssignedTo == p.Preferences[1] { secondChoiceCount++ } else if p.AssignedTo == p.Preferences[2] { thirdChoiceCount++ } else { fallbackCount++ } } else { unassignedCount++ } } // Calculate workshop utilization fmt.Println("\nWorkshop Utilization:") for _, w := range workshops { usage := float64(len(w.Participants)) / float64(w.Capacity) * 100 fmt.Printf("Workshop %d: %d/%d participants (%.2f%% full)\n", w.ID, len(w.Participants), w.Capacity, usage) } // Print summary statistics fmt.Println("\nšŸ”¹ Matching Statistics:") fmt.Printf("ā³ Runtime: %d ms\n", duration.Milliseconds()) fmt.Printf("šŸ“Œ Total Participants: %d\n", totalParticipants) fmt.Printf("šŸ« Total Workshops: %d\n", totalWorkshops) fmt.Printf("āœ… Assigned Participants: %d (%.2f%%)\n", assignedCount, float64(assignedCount)/float64(totalParticipants)*100) fmt.Printf("āŒ Unassigned Participants: %d (%.2f%%)\n", unassignedCount, float64(unassignedCount)/float64(totalParticipants)*100) fmt.Printf("šŸ„‡ First Choice Assigned: %d (%.2f%%)\n", firstChoiceCount, float64(firstChoiceCount)/float64(totalParticipants)*100) fmt.Printf("🄈 Second Choice Assigned: %d (%.2f%%)\n", secondChoiceCount, float64(secondChoiceCount)/float64(totalParticipants)*100) fmt.Printf("šŸ„‰ Third Choice Assigned: %d (%.2f%%)\n", thirdChoiceCount, float64(thirdChoiceCount)/float64(totalParticipants)*100) fmt.Printf("āš ļø Assigned via Fallback: %d (%.2f%%)\n", fallbackCount, float64(fallbackCount)/float64(totalParticipants)*100) }