initial commit

This commit is contained in:
Vinzenz Stadtmueller 2025-02-12 19:56:11 +01:00
commit e92edcb8a9
7 changed files with 303 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
*.txt

17
docs/index.md Normal file
View File

@ -0,0 +1,17 @@
# Welcome to MkDocs
For full documentation visit [mkdocs.org](https://www.mkdocs.org).
## Commands
* `mkdocs new [dir-name]` - Create a new project.
* `mkdocs serve` - Start the live-reloading docs server.
* `mkdocs build` - Build the documentation site.
* `mkdocs -h` - Print help message and exit.
## Project layout
mkdocs.yml # The configuration file.
docs/
index.md # The documentation homepage.
... # Other markdown pages, images and other files.

3
go.mod Normal file
View File

@ -0,0 +1,3 @@
module workshop
go 1.23.3

0
go.sum Normal file
View File

234
library.go Normal file
View File

@ -0,0 +1,234 @@
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)
}

47
main.go Normal file
View File

@ -0,0 +1,47 @@
package main
import (
"time"
"math/rand"
)
// Initialize participants and workshops
func initializeParticipantsAndWorkshops(numParticipants, numWorkshops int) ([]Participant, []Workshop) {
rand.Seed(time.Now().UnixNano())
participants := make([]Participant, numParticipants)
for i := range participants {
prefs := rand.Perm(numWorkshops)[:3] // Pick 3 unique workshops
participants[i] = Participant{
ID: i + 1,
Preferences: []int{prefs[0] + 1, prefs[1] + 1, prefs[2] + 1}, // Convert to 1-based index
AssignedTo: -1,
RejectedFrom: make(map[int]bool),
}
}
workshops := make([]Workshop, numWorkshops)
for i := range workshops {
workshops[i] = Workshop{
ID: i + 1,
Capacity: rand.Intn(10) + 15, // Capacity between 15 and 25
Participants: []int{},
}
}
return participants, workshops
}
// Main function
func main() {
numParticipants := 600
numWorkshops := 30
participants, workshops := initializeParticipantsAndWorkshops(numParticipants, numWorkshops)
startTime := time.Now()
StableMatch(participants, workshops)
duration := time.Since(startTime)
PrintAssignments(participants, workshops)
PrintStatistics(participants, workshops, duration)
}

1
mkdocs.yml Normal file
View File

@ -0,0 +1 @@
site_name: My Docs