Files
itsc-timetable/pages/api/tool.ts
2023-02-16 03:01:04 +08:00

102 lines
3.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import type { NextApiRequest, NextApiResponse } from "next";
import fs from "fs";
import { store, html } from "@/store";
import glob from "glob";
import { promisify } from "util";
const g = promisify(glob);
const read = promisify(fs.readFile);
// 索引与工时
const indexToHour: Record<string, number> = JSON.parse(
fs.readFileSync("./hours.json", "utf8")
).selections;
export default async function handler(
req: NextApiRequest,
res: NextApiResponse<Record<string, string>>
) {
// 读入全部json文件
// users: {姓名: {坐标: 权重}}
const users: Record<string, Record<string, number>> = {};
const files = await g("./json/*.json");
for (const file of files) {
const jsonStr = await read(file, "utf8");
const json: {
user: string;
selections: Record<string, number>;
} = JSON.parse(jsonStr);
// Normalization
// 使用 标准分数 算法 z = (x - mean) / std
const { selections } = json;
const nums = Object.values(selections);
const mean = nums.reduce((a, b) => a + b) / nums.length;
const std = Math.sqrt(
nums.map((x) => Math.pow(x - mean, 2)).reduce((a, b) => a + b) /
nums.length
);
const normalizedNums = nums.map((x) => (std === 0 ? 0 : (x - mean) / std));
for (const index in selections) {
selections[index] = std === 0 ? 0 : (selections[index] - mean) / std;
}
users[json.user] = selections;
}
// 计算每一个格子有多少人选择
// counts: {坐标: {姓名: 权重}}
const counts: Record<string, Record<string, Record<string, number>>> = {};
for (const user in users) {
for (const select in users[user]) {
if (counts[select] === undefined) counts[select] = {};
counts[select][user] = users[user];
}
}
// 构造返回数据, key 为 坐标value 为姓名
const resp: Record<string, string> = {};
// 找到选择数量最少的格子
// sortable: [[坐标, 选择人数]]
// 如果遇到选择数量相同的格子,随机打乱顺序进行迭代
const sortable: [string, number][] = [];
for (const index in counts) {
sortable.push([index, Object.keys(counts[index]).length]);
}
sortable.sort((a, b) => a[1] - b[1] || Math.random() - 0.5);
// 记录这个人的出现次数,还有这个人的工时总数
const hoursCount: Record<string, number> = {};
for (const [smallestIndex, _] of sortable) {
// console.log("smallestIndex", smallestIndex);
// console.log("cell", counts[smallestIndex]);
// 找到格子里权重最低的人选 [姓名, 小时数, 出现次数, 权重]
const weightList: [string, number, number][] = [];
for (const user in counts[smallestIndex]) {
weightList.push([
user,
hoursCount[user] || 0,
counts[smallestIndex][user][smallestIndex],
]);
}
weightList.sort((a, b) => a[1] - b[1] || a[2] - b[2]);
// console.log("weightList", weightList);
const theChoosenUser = weightList[0][0];
// console.log("theChoosenUser", theChoosenUser);
// 记录结果
resp[smallestIndex] = theChoosenUser;
hoursCount[theChoosenUser] =
(hoursCount[theChoosenUser] || 0) + indexToHour[smallestIndex];
}
// console.log("hoursCount", hoursCount);
const sortedHoursCount: [string, number][] = [];
for (const user in hoursCount) {
sortedHoursCount.push([user, hoursCount[user]]);
}
sortedHoursCount.sort((a, b) => a[1] - b[1]);
console.log("sortedHoursCount", sortedHoursCount);
console.log("sortedHoursCount.length", sortedHoursCount.length);
res.status(200).json(resp);
}