Compare commits
7 Commits
171c22c338
...
main
Author | SHA1 | Date | |
---|---|---|---|
d78e07f513
|
|||
fc073de1de
|
|||
1d945977dc
|
|||
1fda84bdef
|
|||
6cd253d5f0
|
|||
97e16e6be2
|
|||
a444c7efd3
|
119
brute_force.py
Normal file
119
brute_force.py
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
import numpy as np
|
||||||
|
import json
|
||||||
|
import itertools
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
rgen = np.random.default_rng(seed=42)
|
||||||
|
|
||||||
|
|
||||||
|
def load_stats():
|
||||||
|
db = Path("local_team.db")
|
||||||
|
players_list = "prefs_page/src/players.json"
|
||||||
|
with open(players_list, "r") as f:
|
||||||
|
players = json.load(f)
|
||||||
|
|
||||||
|
preferences = {}
|
||||||
|
|
||||||
|
for line in open(db, "r"):
|
||||||
|
date, person, prefs = line.split("\t")
|
||||||
|
if not person.strip() or not prefs.strip():
|
||||||
|
continue
|
||||||
|
preferences[person] = [p.strip() for p in prefs.split(",")]
|
||||||
|
|
||||||
|
for player in players:
|
||||||
|
if player not in preferences:
|
||||||
|
preferences[player] = []
|
||||||
|
|
||||||
|
return players, preferences
|
||||||
|
|
||||||
|
|
||||||
|
# synthetical data
|
||||||
|
# rgen = np.random.default_rng(seed=42)
|
||||||
|
|
||||||
|
# n_prefs = 8
|
||||||
|
# preferences = {
|
||||||
|
# player: rgen.choice(players, size=n_prefs, replace=False) for player in players
|
||||||
|
# }
|
||||||
|
|
||||||
|
|
||||||
|
def team_table_json():
|
||||||
|
players, preferences = load_stats()
|
||||||
|
best = apply_brute_force(players, preferences)
|
||||||
|
data = {k: {} for k in best}
|
||||||
|
for k, v in best.items():
|
||||||
|
mean, team0, team1 = v[0]
|
||||||
|
overall_matches = 0
|
||||||
|
overall_preference_statements = 0
|
||||||
|
for i, team in enumerate([team0, team1]):
|
||||||
|
tablename = f"Team {i+1}"
|
||||||
|
data[k][tablename] = []
|
||||||
|
for p in sorted(list(team)):
|
||||||
|
prefs = preferences[p]
|
||||||
|
matches = sum([pref in team for pref in preferences[p]])
|
||||||
|
data[k][tablename].append([p, matches, len(prefs)])
|
||||||
|
overall_matches += matches
|
||||||
|
overall_preference_statements += len(prefs)
|
||||||
|
# data[k]["overall_matches"] = overall_matches
|
||||||
|
# data[k]["overall_preference_statements"] = overall_preference_statements
|
||||||
|
|
||||||
|
with open("prefs_page/src/table.json", "w") as f:
|
||||||
|
json.dump(data, f)
|
||||||
|
|
||||||
|
|
||||||
|
def unique_names(team):
|
||||||
|
"""check if first names are unique"""
|
||||||
|
return len(set(team)) == len(set([p.split()[0] for p in team]))
|
||||||
|
|
||||||
|
|
||||||
|
def apply_brute_force(players, preferences):
|
||||||
|
def evaluate_teams(team0, team1):
|
||||||
|
scores = []
|
||||||
|
percentages = []
|
||||||
|
for team in [team0, team1]:
|
||||||
|
for p in team:
|
||||||
|
scores.append(sum([pref in team for pref in preferences[p]]))
|
||||||
|
if len(preferences[p]) > 0:
|
||||||
|
percentages.append(scores[-1] / len(preferences[p]))
|
||||||
|
return np.mean(scores), np.mean(percentages) * 100
|
||||||
|
|
||||||
|
best = {
|
||||||
|
f"{i},{j}": [(0, [], [])]
|
||||||
|
for i, j in itertools.product(["total", "relative"], ["unique", "non-unique"])
|
||||||
|
}
|
||||||
|
|
||||||
|
for team0 in itertools.combinations(players, 9):
|
||||||
|
team1 = {player for player in players if player not in team0}
|
||||||
|
score, percentage = evaluate_teams(team0, team1)
|
||||||
|
for k, v in best.items():
|
||||||
|
if k.startswith("total"):
|
||||||
|
meassure = score
|
||||||
|
elif k.startswith("relative"):
|
||||||
|
meassure = percentage
|
||||||
|
if meassure > best[k][0][0]:
|
||||||
|
if k.endswith(",unique") and not (
|
||||||
|
unique_names(team0) and unique_names(team1)
|
||||||
|
):
|
||||||
|
continue
|
||||||
|
best[k] = [(meassure, team0, team1)]
|
||||||
|
elif meassure == best[k][0][0] and set(team0) != set(best[k][0][1]):
|
||||||
|
if k.endswith(",unique") and not (
|
||||||
|
unique_names(team0) and unique_names(team1)
|
||||||
|
):
|
||||||
|
continue
|
||||||
|
best[k].append((meassure, team0, team1))
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
for k, v in best.items():
|
||||||
|
print("##", k)
|
||||||
|
for result in v:
|
||||||
|
print(result[0])
|
||||||
|
print(sorted(result[1]))
|
||||||
|
print(sorted(result[2]))
|
||||||
|
print()
|
||||||
|
|
||||||
|
# team_table(score, team0, team1)
|
||||||
|
return best
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
team_table_json()
|
@@ -1,5 +1,5 @@
|
|||||||
#root {
|
#root {
|
||||||
max-width: 60vw;
|
max-width: 70vw;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 2rem;
|
padding: 2rem;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@@ -11,4 +11,5 @@
|
|||||||
|
|
||||||
.read-the-docs {
|
.read-the-docs {
|
||||||
color: #888;
|
color: #888;
|
||||||
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
@@ -17,6 +17,7 @@ import {
|
|||||||
FormHelperText,
|
FormHelperText,
|
||||||
Stack,
|
Stack,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
|
import TeamTables from "./Table";
|
||||||
|
|
||||||
function getStyles(name: string, players: readonly string[], theme: Theme) {
|
function getStyles(name: string, players: readonly string[], theme: Theme) {
|
||||||
return {
|
return {
|
||||||
@@ -29,7 +30,8 @@ function getStyles(name: string, players: readonly string[], theme: Theme) {
|
|||||||
async function submit(
|
async function submit(
|
||||||
person: string,
|
person: string,
|
||||||
players: string[],
|
players: string[],
|
||||||
setResponseStatus: (value: number) => void
|
setResponseStatus: (value: number) => void,
|
||||||
|
setDialog: (value: boolean) => void
|
||||||
) {
|
) {
|
||||||
// console.log(JSON.stringify({ person: person, players: players }));
|
// console.log(JSON.stringify({ person: person, players: players }));
|
||||||
const response = await fetch("https://0124816.xyz/team/submit/", {
|
const response = await fetch("https://0124816.xyz/team/submit/", {
|
||||||
@@ -40,6 +42,7 @@ async function submit(
|
|||||||
body: JSON.stringify({ person: person, players: players }),
|
body: JSON.stringify({ person: person, players: players }),
|
||||||
});
|
});
|
||||||
setResponseStatus(response.status);
|
setResponseStatus(response.status);
|
||||||
|
setDialog(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
type SubmitButtonProps = {
|
type SubmitButtonProps = {
|
||||||
@@ -49,17 +52,20 @@ type SubmitButtonProps = {
|
|||||||
|
|
||||||
function SubmitButton(props: SubmitButtonProps) {
|
function SubmitButton(props: SubmitButtonProps) {
|
||||||
const [responseStatus, setResponseStatus] = React.useState(0);
|
const [responseStatus, setResponseStatus] = React.useState(0);
|
||||||
|
const [dialog, setDialog] = React.useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
color={responseStatus === 200 ? "success" : "primary"}
|
color={responseStatus === 200 ? "success" : "primary"}
|
||||||
onClick={() => submit(props.person, props.players, setResponseStatus)}
|
onClick={() =>
|
||||||
|
submit(props.person, props.players, setResponseStatus, setDialog)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
submit
|
submit
|
||||||
</Button>
|
</Button>
|
||||||
<Dialog open={responseStatus === 200}>
|
<Dialog onClose={() => setDialog(false)} open={dialog}>
|
||||||
<DialogTitle>thank you. please leave now.</DialogTitle>
|
<DialogTitle>thank you. please leave now.</DialogTitle>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</div>
|
</div>
|
||||||
@@ -194,8 +200,17 @@ function App() {
|
|||||||
{SubmitButton({ person, players })}
|
{SubmitButton({ person, players })}
|
||||||
<p>now: click submit.</p>
|
<p>now: click submit.</p>
|
||||||
</div>
|
</div>
|
||||||
|
<p className="read-the-docs">
|
||||||
|
suggested teams are not updated right away. don't try to hack the
|
||||||
|
algorithm ;D
|
||||||
|
</p>
|
||||||
|
{TeamTables()}
|
||||||
<p className="read-the-docs">
|
<p className="read-the-docs">
|
||||||
something not working? message <a href="https://t.me/x0124816">me</a>.
|
something not working? message <a href="https://t.me/x0124816">me</a>.
|
||||||
|
sources:{" "}
|
||||||
|
<a href="https://git.0124816.xyz/julius/teambuilding" key="gitea">
|
||||||
|
<img src="gitea.svg" alt="gitea" height="20" />
|
||||||
|
</a>
|
||||||
</p>
|
</p>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
151
prefs_page/src/Table.tsx
Normal file
151
prefs_page/src/Table.tsx
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import Table from "@mui/material/Table";
|
||||||
|
import TableBody from "@mui/material/TableBody";
|
||||||
|
import TableCell from "@mui/material/TableCell";
|
||||||
|
import TableContainer from "@mui/material/TableContainer";
|
||||||
|
import TableHead from "@mui/material/TableHead";
|
||||||
|
import TableRow from "@mui/material/TableRow";
|
||||||
|
import Paper from "@mui/material/Paper";
|
||||||
|
import data from "./table.json";
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Checkbox,
|
||||||
|
FormControlLabel,
|
||||||
|
Stack,
|
||||||
|
Switch,
|
||||||
|
Typography,
|
||||||
|
} from "@mui/material";
|
||||||
|
|
||||||
|
interface Data {
|
||||||
|
[index: string]: Teams;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Teams {
|
||||||
|
teamname: Row;
|
||||||
|
}
|
||||||
|
|
||||||
|
type Row = [player: string, fulfilled: number, total: number];
|
||||||
|
|
||||||
|
function RenderTable(data: object) {
|
||||||
|
let nMatches: number = 0;
|
||||||
|
let nTotal: number = 0;
|
||||||
|
let nPlayers: number = 0;
|
||||||
|
let relativeScore: number = 0.0;
|
||||||
|
for (const rows of Object.values(data)) {
|
||||||
|
for (const row of rows) {
|
||||||
|
nMatches += row[1];
|
||||||
|
nTotal += row[2];
|
||||||
|
relativeScore += row[1] / row[2];
|
||||||
|
nPlayers++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Paper elevation={2} sx={{ p: 2, display: "inline-block" }}>
|
||||||
|
average wishes fulfilled:{" "}
|
||||||
|
<Typography sx={{ fontFamily: "monospace" }}>
|
||||||
|
{(nMatches / nPlayers).toPrecision(2)} (
|
||||||
|
{((relativeScore / nPlayers) * 100).toPrecision(3)}%)
|
||||||
|
</Typography>
|
||||||
|
</Paper>
|
||||||
|
{Object.entries(data).map(([teamname, rows]) => (
|
||||||
|
<>
|
||||||
|
<h1>{teamname}</h1>
|
||||||
|
<TableContainer component={Paper}>
|
||||||
|
<Table
|
||||||
|
sx={{ m: "8px auto" }}
|
||||||
|
size="small"
|
||||||
|
aria-label="simple table"
|
||||||
|
>
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>player</TableCell>
|
||||||
|
<TableCell>wishes fulfilled</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{(rows as Row[]).map((row: Row) => (
|
||||||
|
<TableRow
|
||||||
|
key={row[0]}
|
||||||
|
sx={{ "&:last-child td, &:last-child th": { border: 0 } }}
|
||||||
|
>
|
||||||
|
<TableCell key={row[0] + "-cell"}>{row[0]}</TableCell>
|
||||||
|
<TableCell key={row[0] + "-cell-2"}>
|
||||||
|
{"♥️".repeat(row[1]) + "♡".repeat(row[2] - row[1])}
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
</>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function TeamTables() {
|
||||||
|
const [total, setTotal] = useState("relative");
|
||||||
|
const [unique, setUnique] = useState("non-unique");
|
||||||
|
|
||||||
|
const handleTotalChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
if (event.target.checked) {
|
||||||
|
setTotal("relative");
|
||||||
|
} else {
|
||||||
|
setTotal("total");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleUniqueChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
if (event.target.checked) {
|
||||||
|
setUnique("unique");
|
||||||
|
} else {
|
||||||
|
setUnique("non-unique");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let tabledata: Data = data as unknown as Data;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
m: "auto",
|
||||||
|
display: "flex",
|
||||||
|
flexWrap: "wrap",
|
||||||
|
justifyContent: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Paper elevation={2} sx={{ m: 1, p: 1 }}>
|
||||||
|
<Typography className="read-the-docs">according to</Typography>
|
||||||
|
<Stack
|
||||||
|
direction="row"
|
||||||
|
spacing={1}
|
||||||
|
sx={{ p: "0 16px", m: "0 8px", alignItems: "center" }}
|
||||||
|
>
|
||||||
|
<Typography>total</Typography>
|
||||||
|
<Switch defaultChecked onChange={handleTotalChange} />
|
||||||
|
<Typography>relative</Typography>
|
||||||
|
</Stack>
|
||||||
|
<Typography className="read-the-docs">
|
||||||
|
number of wishes fulfilled
|
||||||
|
</Typography>
|
||||||
|
</Paper>
|
||||||
|
|
||||||
|
<Paper elevation={2} sx={{ m: 1, p: 1, justifyContent: "center" }}>
|
||||||
|
<FormControlLabel
|
||||||
|
sx={{ padding: 2, m: 1 }}
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
checked={unique === "unique"}
|
||||||
|
onChange={handleUniqueChange}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label="unique first names"
|
||||||
|
/>
|
||||||
|
</Paper>
|
||||||
|
</Box>
|
||||||
|
{RenderTable(tabledata[[total, unique].join(",") as string])}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
Reference in New Issue
Block a user