如题

4 条评论

  • @ 2025-3-28 21:45:18

    这是 Hydro 的 RP 计算 Typescript 文件 但是我没看懂 有没有大佬研究一下

    • @ 2025-3-28 21:44:35

      /* eslint-disable no-cond-assign / / eslint-disable no-await-in-loop */ // rp文件 import { NumericDictionary, unionWith } from 'lodash'; import { FilterQuery } from 'mongodb'; import { Tdoc, Udoc } from '../interface'; import difficultyAlgorithm from '../lib/difficulty'; import rating from '../lib/rating'; import { PRIV, STATUS } from '../model/builtin'; import * as contest from '../model/contest'; import domain from '../model/domain'; import problem from '../model/problem'; import UserModel from '../model/user'; import db from '../service/db';

      export const description = 'Calculate rp of a domain, or all domains';

      type ND = NumericDictionary;

      interface RpDef { run(domainIds: string[], udict: ND, report: Function): Promise; hidden: boolean; base: number; }

      const { log, max, min } = Math;

      export const RpTypes: Record<string, RpDef> = { problem: { async run(domainIds, udict, report) { const problems = await problem.getMulti('', { domainId: { in: domainIds }, nAccept: { gt: 0 }, hidden: false }).toArray(); if (problems.length) await report({ message: Found ${problems.length} problems in ${domainIds[0]} }); for (const pdoc of problems) { const cursor = problem.getMultiStatus( pdoc.domainId, { docId: pdoc.docId, rid: { ne: null }, uid: { ne: pdoc.owner }, score: { $gt: 0 }, }, ); const difficulty = +pdoc.difficulty || difficultyAlgorithm(pdoc.nSubmit, pdoc.nAccept) || 5; const p = difficulty / 100; let psdoc; while (psdoc = await cursor.next()) { udict[psdoc.uid] += min(psdoc.score, 100) * p; } } for (const key in udict) udict[key] = max(0, min(udict[key], log(udict[key]) / log(1.03))); }, hidden: false, base: 0, }, contest: { async run(domainIds, udict, report) { const contests: Tdoc<30>[] = await contest.getMulti('', { domainId: { $in: domainIds }, rated: true }) .limit(10).toArray() as any; if (contests.length) await report({ message: Found ${contests.length} contests in ${domainIds[0]} }); for (const tdoc of contests.reverse()) { const start = Date.now(); const cursor = contest.getMultiStatus(tdoc.domainId, { docId: tdoc.docId, journal: { $ne: null }, }).sort(contest.RULES[tdoc.rule].statusSort); if (!await cursor.count()) continue; const [rankedTsdocs] = await contest.RULES[tdoc.rule].ranked(tdoc, cursor); const users = rankedTsdocs.map((i) => ({ uid: i[1].uid, rank: i[0], old: udict[i[1].uid] })); // FIXME sum(rating.new) always less than sum(rating.old) for (const udoc of rating(users)) udict[udoc.uid] = udoc.new; await report({ case: { status: STATUS.STATUS_ACCEPTED, message: `Contest ${tdoc.title} finished`, time: Date.now() - start, memory: 0, score: 0, }, }); } for (const key in udict) udict[key] = max(1, udict[key] / 4 - 375); }, hidden: false, base: 1500, }, delta: { async run(domainIds, udict) { const dudocs = unionWith( await domain.getMultiUserInDomain( '', { domainId: { in: domainIds }, rpdelta: { exists: true } }, ).toArray(), (a, b) => a.uid === b.uid, ); for (const dudoc of dudocs) udict[dudoc.uid] = dudoc.rpdelta; }, hidden: true, base: 0, }, }; global.Hydro.model.rp = RpTypes;

      export async function calcLevel(domainId: string, report: Function) { const filter = { rp: { $gt: 0 } }; const ducnt = await domain.getMultiUserInDomain(domainId, filter).count(); await domain.setMultiUserInDomain(domainId, {}, { level: 0, rank: null }); if (!ducnt) return; let last = { rp: null }; let rank = 0; let count = 0; const coll = db.collection('domain.user'); const ducur = domain.getMultiUserInDomain(domainId, filter).project({ rp: 1 }).sort({ rp: -1 }); let bulk = coll.initializeUnorderedBulkOp(); // eslint-disable-next-line no-constant-condition while (true) { const dudoc = await ducur.next(); if (!dudoc) break; if ([0, 1].includes(dudoc.uid)) continue; count++; if (!dudoc.rp) dudoc.rp = null; if (dudoc.rp !== last.rp) rank = count; bulk.find({ _id: dudoc._id }).updateOne({ $set: { rank } }); last = dudoc; if (count % 100 === 0) report({ message: #${count}: Rank ${rank} }); } await bulk.execute(); const levels = global.Hydro.model.builtin.LEVELS; bulk = coll.initializeUnorderedBulkOp(); for (let i = 0; i < levels.length; i++) { const query: FilterQuery = { domainId, and: [{ rank: { lte: (levels[i] * count) / 100 } }], }; if (i < levels.length - 1) query.and.push({ rank: { gt: (levels[i + 1] * count) / 100 } }); bulk.find(query).update({ $set: { level: i } }); } await bulk.execute(); }

      async function runInDomain(id: string, report: Function) { const info = await domain.getUnion(id); if (info) info.union.unshift(id); const domainIds = info ? info.union : [id]; const results: Record<keyof typeof RpTypes, ND> = {}; const udict = new Proxy({}, { get: (self, key) => self[key] || 0 }); for (const type in RpTypes) { results[type] = new Proxy({}, { get: (self, key) => self[key] || RpTypes[type].base }); await RpTypes[type].run(domainIds, results[type], report); for (const uid in results[type]) { const udoc = await UserModel.getById(id, +uid); if (!udoc?.hasPriv(PRIV.PRIV_USER_PROFILE)) continue; await domain.updateUserInDomain(id, +uid, { set: { [`rpInfo.{type}`]: results[type][uid] } }); udict[+uid] += results[type][uid]; } } await domain.setMultiUserInDomain(id, {}, { rp: 0 }); const bulk = db.collection('domain.user').initializeUnorderedBulkOp(); for (const uid in udict) { bulk.find({ domainId: id, uid: +uid }).upsert().update({ $set: { rp: Math.max(0, udict[uid]) } }); } if (bulk.length) await bulk.execute(); await calcLevel(id, report); }

      export async function run({ domainId }, report: Function) { if (!domainId) { const domains = await domain.getMulti().toArray(); await report({ message: Found ${domains.length} domains }); for (const i in domains) { const start = new Date().getTime(); await runInDomain(domains[i]._id, report); await report({ case: { status: STATUS.STATUS_ACCEPTED, message: Domain ${domains[i]._id} finished, time: new Date().getTime() - start, memory: 0, score: 0, }, progress: Math.floor(((+i + 1) / domains.length) * 100), }); } } else await runInDomain(domainId, report); return true; }

      export const validate = { domainId: 'string?', };

      global.Hydro.script.rp = { run, description, validate };

      • @ 2025-5-31 18:52:22

        这段代码实现了一个基于用户行为计算领域(domain)内用户RP(Rating Points)的系统,支持多种计算方式并可扩展。以下是对其核心逻辑的分步解析:

        一、整体架构 模块组成 RpTypes:定义三种RP计算方式(problem/contest/delta) calcLevel:根据RP值计算用户等级和排名 runInDomain:处理单个领域的RP计算 run:入口函数,支持批量或单领域计算 二、核心计算类型(RpTypes)

        1. Problem-Based RP 触发条件:用户解决带有有效分数的题目 计算逻辑: 获取领域内所有被接受的问题(nAccept > 0) 对每个问题: 计算题目难度(difficultyAlgorithm) 获取所有非题主的高分提交(score > 0) 用户得分 = min(提交分数, 100) * (难度/100) 累加到用户RP字典(udict) 最终RP值通过log(rp)/log(1.03)进行指数衰减限制
        2. Contest-Based RP 触发条件:用户参与评级竞赛 计算逻辑: 获取领域内最近10场评级竞赛 对每场竞赛: 获取有效参赛记录(journal != null) 按竞赛规则排序并计算排名 使用Elo-like算法(rating函数)更新用户RP 最终RP值通过rp/4 - 375进行线性缩放
        3. Delta-Based RP 触发条件:直接读取预设的RP变更值 计算逻辑: 从domain.user集合中直接加载rpdelta字段值 适用于手动调整或特殊场景 三、等级与排名计算(calcLevel) 排名分配: 按RP降序排序,相同RP共享排名 使用MongoDB批量操作优化性能 等级划分: 根据预设的LEVELS百分比阈值分配等级 示例:前10%用户为Level 3,中间30%为Level 2等 四、关键实现细节 性能优化: 使用initializeUnorderedBulkOp批量更新数据库 竞赛处理时反向遍历(reverse())保证时效性 数据过滤: 跳过系统用户(uid: 0/1) 仅处理有效数据(hidden: false) 扩展性设计: 新增计算类型只需实现RpDef接口 通过unionWith支持联盟领域计算 ————from AI
    • @ 2025-3-28 12:10:19

      你交了几道题+你AC了几道题

    • @ 2025-3-19 14:19:23

      每道题之和吧(猜的)

      ❤️ 1
      🤔 1
      • 1

      信息

      ID
      30
      时间
      1000ms
      内存
      256MiB
      难度
      6
      标签
      递交数
      219
      已通过
      74
      上传者