RP是怎么计算的啊

5 comments

  • @ 2022-9-3 13:05:55
    /* 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<number>;
    
    interface RpDef {
        run(domainIds: string[], udict: ND, report: Function): Promise<void>;
        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<Udoc> = {
                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 };
    
    

    这是 Hydro 的 RP 计算 Typescript 文件,就看你能不能看懂了,之前摆弄 Hydro 开源 OJ 的时候发现的。

    👍 1
    • @ 2022-8-28 14:59:24

      好问题

      • @ 2022-8-16 21:21:23

        先这样,然后那样,再+-*/%<<>>&&||=#@()[]{}......,就成了☺

        🤣 4
        😄 1
        • @ 2022-8-14 19:33:51

          先这样,然后那样,再加起来,就算好了。 666

          • @ 2022-8-14 15:02:03

            先这样,然后那样,再加起来,就算好了。

            🤣 15
            👍 5
            👎 4
            • 1