1
0
mirror of https://github.com/osmarks/random-stuff synced 2024-12-26 18:10:34 +00:00
random-stuff/rank_ordering_assignment_system.html
2023-06-19 14:09:54 +01:00

116 lines
5.0 KiB
HTML

<body>
<script src="https://unpkg.com/mithril/mithril.js"></script>
<script>
let people = 3
let info = []
let pointsForRank = ""
const regenerateInfo = () => {
if (info.length > people) {
info = info.slice(0, people)
}
if (info.length < people) {
info = JSON.parse(JSON.stringify(info.concat(new Array(people - info.length).fill({ rank: "", weighting: 1 }))))
}
}
regenerateInfo()
const changeCount = ev => {
people = parseInt(ev.target.value)
regenerateInfo()
}
const permutations = xs => {
if (xs.length === 0) { return [[]] }
const result = []
for (let i = 0; i < xs.length; i++) {
const listWithoutIth = xs.slice(0, i).concat(xs.slice(i + 1))
for (const perm of permutations(listWithoutIth)) {
result.push([xs[i]].concat(perm))
}
}
return result
}
const range = n => new Array(n).fill(null).map((x, i) => i)
let result = ""
const targetPerms = 5
const solve = () => {
const points = pointsForRank.split(",").map(parseFloat)
if (points.length !== people) {
throw "There must be as many points defined as ranks."
} else if (!points.every(x => !isNaN(x))) {
throw "Invalid number"
}
const pointsForPerson = []
for (let i = 0; i < info.length; i++) {
const inf = info[i]
const rankOrder = inf.rank.split(",").map(parseFloat)
const sortedCopy = Array.from(rankOrder)
sortedCopy.sort((a, b) => a - b)
console.log(JSON.stringify(sortedCopy), JSON.stringify(range(people).map(x => x + 1)))
if (rankOrder.length !== people) {
throw `Person ${i + 1}'s ranking is too short/long.`
} else if (!rankOrder.every(x => !isNaN(x))) {
throw `Person ${i + 1}'s ranking contains an invalid number.`
} else if (JSON.stringify(sortedCopy) !== JSON.stringify(range(people).map(x => x + 1))) {
throw `Person ${i + 1}'s ranking is not a valid permutation.`
}
const newx = new Array(people).fill(null)
for (let j = 0; j < people; j++) {
const thingWithJthRank = rankOrder[j] - 1
newx[thingWithJthRank] = inf.weighting * points[j]
}
pointsForPerson.push(newx)
}
console.log("got", points, pointsForPerson)
let bestPerms = []
for (const perm of permutations(range(people))) { // ith person gets perm[i]th thing person
const score = perm.map((person, thingIndex) => pointsForPerson[person][thingIndex]).reduce((a, b) => a + b, 0)
for (let i = 0; i < bestPerms.length; i++) {
const [otherPerm, otherScore] = bestPerms[i]
if (otherScore < score) {
bestPerms.splice(i, 1)
break
}
}
if (bestPerms.length < targetPerms) {
bestPerms.push(([perm, score]))
}
}
bestPerms.sort((a, b) => b[1] - a[1])
return m("", [
"Solutions found:",
m("ul", bestPerms.map(([bestPerm, bestScore]) => m("li", bestPerm.map((person, thingIndex) => `Person ${person + 1} gets ${thingIndex + 1}`).join("; ") + ` (${bestScore} points).`)))
])
}
const solveWrapper = () => {
try {
result = solve()
} catch(e) {
console.warn(e)
result = e.toString()
}
}
m.mount(document.body, {
view: function() {
return [
m("p", "Rank orders must be given as comma-separated one-indexed integers. The points for each rank must be given as comma-separated real numbers."),
m("input", { value: pointsForRank, placeholder: "Points for ranks", oninput: ev => { pointsForRank = ev.target.value } }),
m("", [ m("label", "People: "), m("input[type=number]", { value: people, oninput: changeCount }) ]),
m("", info.map((i, index) => m("", [
m("label", `Person ${index + 1}: `),
m("input", { value: i.rank, placeholder: "Ranking", oninput: ev => { i.rank = ev.target.value } }),
m("label", " Weighting: "), m("input[type=number]", { value: i.weighting, oninput: ev => { i.weighting = parseFloat(ev.target.value) }, step: 0.1 })
]))),
m("button", { onclick: solveWrapper }, "Solve"),
m("", result)
]
}
})
</script>
</body>