|
| 1 | +import sequtils, std/enumerate, random, strutils |
| 2 | +const PAIRS = 10 |
| 3 | +const m_names = ["abe", "bob", "col", "dan", "ed", "fred", "gav", "hal", "ian", "jon"] |
| 4 | +const f_names = ["abi", "bea", "cath", "dee", "eve", "fay", "gay", "hope", |
| 5 | + "ivy", "jan"] |
| 6 | +const m_prefs = [ |
| 7 | +["abi", "eve", "cath", "ivy", "jan", "dee", "fay", "bea", "hope", "gay"], |
| 8 | +["cath", "hope", "abi", "dee", "eve", "fay", "bea", "jan", "ivy", "gay"], |
| 9 | +["hope", "eve", "abi", "dee", "bea", "fay", "ivy", "gay", "cath", "jan"], |
| 10 | +["ivy", "fay", "dee", "gay", "hope", "eve", "jan", "bea", "cath", "abi"], |
| 11 | +["jan", "dee", "bea", "cath", "fay", "eve", "abi", "ivy", "hope", "gay"], |
| 12 | +["bea", "abi", "dee", "gay", "eve", "ivy", "cath", "jan", "hope", "fay"], |
| 13 | +["gay", "eve", "ivy", "bea", "cath", "abi", "dee", "hope", "jan", "fay"], |
| 14 | +["abi", "eve", "hope", "fay", "ivy", "cath", "jan", "bea", "gay", "dee"], |
| 15 | +["hope", "cath", "dee", "gay", "bea", "abi", "fay", "ivy", "jan", "eve"], |
| 16 | +["abi", "fay", "jan", "gay", "eve", "bea", "dee", "cath", "ivy", "hope"] |
| 17 | +] |
| 18 | +const f_prefs = [ |
| 19 | +["bob", "fred", "jon", "gav", "ian", "abe", "dan", "ed", "col", "hal"], |
| 20 | +["bob", "abe", "col", "fred", "gav", "dan", "ian", "ed", "jon", "hal"], |
| 21 | +["fred", "bob", "ed", "gav", "hal", "col", "ian", "abe", "dan", "jon"], |
| 22 | +["fred", "jon", "col", "abe", "ian", "hal", "gav", "dan", "bob", "ed"], |
| 23 | +["jon", "hal", "fred", "dan", "abe", "gav", "col", "ed", "ian", "bob"], |
| 24 | +["bob", "abe", "ed", "ian", "jon", "dan", "fred", "gav", "col", "hal"], |
| 25 | +["jon", "gav", "hal", "fred", "bob", "abe", "col", "ed", "dan", "ian"], |
| 26 | +["gav", "jon", "bob", "abe", "ian", "dan", "hal", "ed", "col", "fred"], |
| 27 | +["ian", "col", "hal", "gav", "fred", "bob", "abe", "ed", "jon", "dan"], |
| 28 | +["ed", "hal", "gav", "abe", "bob", "jon", "col", "ian", "fred", "dan"] |
| 29 | +] |
| 30 | + |
| 31 | +# recipient's preferences hold the preference score for each contender's id |
| 32 | +func get_rec_prefs[N: static int](prefs: array[N, array[N, string]], |
| 33 | + names: openArray[string]): seq[seq[int]] {.compileTime.} = |
| 34 | + for pref_seq in prefs: |
| 35 | + var p = newSeq[int](PAIRS) |
| 36 | + for contender in 0..<PAIRS: |
| 37 | + p[contender] = pref_seq.find(m_names[contender]) |
| 38 | + result.add(p) |
| 39 | + |
| 40 | +# contender's preferences hold the recipient ids in descending order of preference |
| 41 | +func get_cont_prefs(prefs: array[PAIRS, array[PAIRS, string]], names: openArray[ |
| 42 | + string]): seq[seq[int]] {.compileTime.} = |
| 43 | + for pref_seq in prefs: |
| 44 | + var p: seq[int] |
| 45 | + for pref in pref_seq: |
| 46 | + p.add(names.find(pref)) |
| 47 | + result.add(p) |
| 48 | + |
| 49 | +const RECIPIENT_PREFS = get_rec_prefs(f_prefs, m_names) |
| 50 | +const CONTENDER_PREFS = get_cont_prefs(m_prefs, f_names) |
| 51 | + |
| 52 | +proc print_couples(cont_pairs: seq[int]) = |
| 53 | + for c, r in enumerate(cont_pairs): |
| 54 | + echo m_names[c] & " 💑" & f_names[cont_pairs[c]] |
| 55 | + |
| 56 | +func pair(): (seq[int], seq[int]) = |
| 57 | + # double booking to avoid inverse lookup using find |
| 58 | + var rec_pairs = newSeqWith(10, -1) |
| 59 | + var cont_pairs = newSeqWith(10, -1) |
| 60 | + proc engage(c, r: int) = |
| 61 | + #echo f_names[r] & " accepted " & m_names[c] |
| 62 | + cont_pairs[c] = r |
| 63 | + rec_pairs[r] = c |
| 64 | + var cont_queue = newSeqWith(10, 0) |
| 65 | + while cont_pairs.contains(-1): |
| 66 | + for c in 0..<PAIRS: |
| 67 | + if cont_pairs[c] == -1: |
| 68 | + let r = CONTENDER_PREFS[c][cont_queue[c]] #proposing to first in queue |
| 69 | + cont_queue[c]+=1 #increment contender's queue for future iterations |
| 70 | + let cur_pair = rec_pairs[r] # current pair's index or -1 = vacant |
| 71 | + if cur_pair == -1: |
| 72 | + engage(c, r) |
| 73 | + # contender is more preferable than current |
| 74 | + elif RECIPIENT_PREFS[r][c] < RECIPIENT_PREFS[r][cur_pair]: |
| 75 | + cont_pairs[cur_pair] = -1 # vacate current pair |
| 76 | + #echo m_names[cur_pair] & " was dumped by " & f_names[r] |
| 77 | + engage(c, r) |
| 78 | + result = (cont_pairs, rec_pairs) |
| 79 | + |
| 80 | +proc rand_pair(max: int): (int, int) = |
| 81 | + let a = rand(max) |
| 82 | + var b = rand(max-1) |
| 83 | + if b == a: |
| 84 | + b = max |
| 85 | + result = (a,b) |
| 86 | + |
| 87 | +proc perturb_pairs(cont_pairs, rec_pairs: var seq[int]) = |
| 88 | + randomize() |
| 89 | + let (a,b) = rand_pair(PAIRS-1) |
| 90 | + echo "Swapping " & m_names[a] & " & " & m_names[b] & " partners" |
| 91 | + swap(cont_pairs[a], cont_pairs[b]) |
| 92 | + swap(rec_pairs[cont_pairs[a]], rec_pairs[cont_pairs[b]]) |
| 93 | + |
| 94 | +proc check_stability(cont_pairs, rec_pairs: seq[int]): bool = |
| 95 | + for c in 0..<PAIRS: # each contender |
| 96 | + let cur_p_score = CONTENDER_PREFS[c].find(cont_pairs[c]) # pref. score for current pair |
| 97 | + for preferred_id in 0..<cur_p_score: # try every recipient with higher score |
| 98 | + let check_r = CONTENDER_PREFS[c][preferred_id] |
| 99 | + let cur_r_p = rec_pairs[check_r] # current pair of checked recipient |
| 100 | + # if score of the cur_r_p is worse (>) than score of checked contender |
| 101 | + if RECIPIENT_PREFS[check_r][cur_r_p] > RECIPIENT_PREFS[check_r][c]: |
| 102 | + echo m_names[c] & " prefers " & f_names[check_r] & " over " & f_names[cont_pairs[c]] |
| 103 | + echo f_names[check_r] & " prefers " & m_names[c] & " over " & m_names[cur_r_p] |
| 104 | + return false # unstable |
| 105 | + result = true |
| 106 | + |
| 107 | +when isMainModule: |
| 108 | + var (cont_pairs, rec_pairs) = pair() |
| 109 | + print_couples(cont_pairs) |
| 110 | + echo "Current pair analysis:" |
| 111 | + echo if check_stability(cont_pairs, rec_pairs): |
| 112 | + "✓ Stable" |
| 113 | + else: |
| 114 | + "✗ Unstable" |
| 115 | + perturb_pairs(cont_pairs, rec_pairs) |
| 116 | + print_couples(cont_pairs) |
| 117 | + echo "Current pair analysis:" |
| 118 | + echo if check_stability(cont_pairs, rec_pairs): |
| 119 | + "✓ Stable" |
| 120 | + else: |
| 121 | + "✗ Unstable" |
0 commit comments