declare global { interface StringConstructor { random: (digit?: number, dict?: string) => string; } interface String { format: (...args: Array) => string; formatFromArray: (args: any[]) => string; rawformat: (object: any) => string; } interface ArrayConstructor { isDiff: (a: any[], b: any[]) => boolean; } interface Math { sum: (...args: Array) => number; } interface SetConstructor { isSuperset: (set: Set, subset: Set | Array) => boolean; intersection: (setA: Set | Array, setB: Set | Array) => Set; union: (setA: Set | Array, setB: Set | Array) => Set; } } const defaultDict = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890'; String.random = function random(digit = 32, dict = defaultDict) { let str = ''; for (let i = 1; i <= digit; i++) str += dict[Math.floor(Math.random() * dict.length)]; return str; }; String.prototype.format ||= function formatStr(...args) { let result = this; if (args.length) { if (args.length === 1 && typeof args[0] === 'object') { const t = args[0]; for (const key in t) { if (!key.startsWith('_') && t[key] !== undefined) { const reg = new RegExp(`(\\{${key}\\})`, 'g'); result = result.replace(reg, t[key]); } } } else return this.formatFromArray(args); } return result; }; String.prototype.formatFromArray = function formatStr(args) { let result = this; for (let i = 0; i < args.length; i++) { if (args[i] !== undefined) { const reg = new RegExp(`(\\{)${i}(\\})`, 'g'); result = result.replace(reg, args[i]); } } return result; }; String.prototype.rawformat = function rawFormat(object) { const res = this.split('{@}'); return [res[0], object, res[1]].join(); }; Array.isDiff = function isDiff(a, b) { if (a.length !== b.length) return true; a.sort(); b.sort(); for (const i in a) { if (a[i] !== b[i]) return true; } return false; }; // @ts-ignore Date.prototype.format = function formatDate(fmt = '%Y-%m-%d %H:%M:%S') { let m = this.getMonth() + 1; if (m < 10) m = `0${m}`; let d = this.getDate(); if (d < 10) d = `0${d}`; let H = this.getHours(); if (H < 10) H = `0${H}`; let M = this.getMinutes(); if (M < 10) M = `0${M}`; let S = this.getSeconds(); if (S < 10) S = `0${S}`; return fmt .replace('%Y', this.getFullYear()) .replace('%m', m) .replace('%d', d) .replace('%H', H) .replace('%M', M) .replace('%S', S); }; Math.sum = function sum(...args) { let s = 0; for (const i of args) { if (i instanceof Array) { for (const j of i) { s += j; } } else s += i; } return s; }; Set.isSuperset = function isSuperset(set, subset) { for (const elem of subset) { if (!set.has(elem)) return false; } return true; }; Set.union = function Union(setA: Set | Array, setB: Set | Array) { const union = new Set(setA); for (const elem of setB) union.add(elem); return union; }; Set.intersection = function Intersection(A: Set | Array = [], B: Set | Array = []) { const intersection = new Set(); if (A instanceof Array) A = new Set(A); if (B instanceof Array) B = new Set(B); for (const elem of B) if (A.has(elem)) intersection.add(elem); return intersection; }; export function sleep(timeout: number) { return new Promise((resolve) => { setTimeout(() => resolve(true), timeout); }); } function deepen(modifyString: (source: string) => string) { function modifyObject(source: T): T { if (typeof source !== 'object' || !source) return source; if (Array.isArray(source)) return source.map(modifyObject) as any; const result = {} as T; for (const key in source) { result[modifyString(key)] = modifyObject(source[key]); } return result; } return function t(source: T): T { if (typeof source === 'string') return modifyString(source) as any; return modifyObject(source); }; } export function noop() { } export const camelCase = deepen((source) => source.replace(/[_-][a-z]/g, (str) => str.slice(1).toUpperCase())); export const paramCase = deepen((source) => source.replace(/_/g, '-').replace(/(? `-${str.toLowerCase()}`)); export const snakeCase = deepen((source) => source.replace(/-/g, '_').replace(/(? `_${str.toLowerCase()}`)); const TIME_RE = /^([0-9]+(?:\.[0-9]*)?)([mu]?)s?$/i; const TIME_UNITS = { '': 1000, m: 1, u: 0.001 }; const MEMORY_RE = /^([0-9]+(?:\.[0-9]*)?)([kmg])b?$/i; const MEMORY_UNITS = { k: 1 / 1024, m: 1, g: 1024 }; export function parseTimeMS(str: string | number, throwOnError = true) { if (typeof str === 'number' || Number.isSafeInteger(+str)) return +str; const match = TIME_RE.exec(str); if (!match && throwOnError) throw new Error(`${str} error parsing time`); if (!match) return 1000; return Math.floor(parseFloat(match[1]) * TIME_UNITS[match[2].toLowerCase()]); } export function parseMemoryMB(str: string | number, throwOnError = true) { if (typeof str === 'number' || Number.isSafeInteger(+str)) return +str; const match = MEMORY_RE.exec(str); if (!match && throwOnError) throw new Error(`${str} error parsing memory`); if (!match) return 256; return Math.ceil(parseFloat(match[1]) * MEMORY_UNITS[match[2].toLowerCase()]); } function _digit2(number: number) { return number < 10 ? `0${number}` : number.toString(); } export function formatSeconds(_seconds: string | number = '0', showSeconds = true) { const seconds = +_seconds; let res = '{0}:{1}'.format( showSeconds ? _digit2(Math.floor(seconds / 3600)) : Math.floor(seconds / 3600), _digit2(Math.floor((seconds % 3600) / 60)), ); if (showSeconds) res += `:${_digit2(seconds % 60)}`; return res; } export function size(s: number, base = 1) { s *= base; const unit = 1024; const unitNames = ['Bytes', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']; for (const unitName of unitNames) { if (s < unit) return '{0} {1}'.format(Math.round(s * 10) / 10, unitName); s /= unit; } return `${Math.round(s * unit)} ${unitNames[unitNames.length - 1]}`; } export type StringKeys = { [K in keyof O]: string extends O[K] ? K : never }[keyof O]; const fSortR = /[^\d]+|\d+/g; export function sortFiles(files: string[]): string[]; export function sortFiles(files: { _id: string }[], key?: '_id'): { _id: string }[]; export function sortFiles>(files: T[], key: StringKeys): T[]; export function sortFiles(files: Record[] | string[], key = '_id') { if (!files?.length) return []; const isString = typeof files[0] === 'string'; const result = files .map((i) => (isString ? { name: i, _weights: i.match(fSortR) } : { ...i, _weights: (i[key] || i.name).match(fSortR) })) .sort((a, b) => { let pos = 0; const weightsA = a._weights; const weightsB = b._weights; let weightA = weightsA[pos]; let weightB = weightsB[pos]; while (weightA && weightB) { const v = weightA - weightB; if (!Number.isNaN(v) && v !== 0) return v; if (weightA !== weightB) return weightA > weightB ? 1 : -1; pos += 1; weightA = weightsA[pos]; weightB = weightsB[pos]; } return weightA ? 1 : -1; }); return result.map((x) => (isString ? x.name : (delete x._weights && x))); } interface MatchRule { regex: RegExp; output: ((a: RegExpExecArray) => string)[]; id: (a: RegExpExecArray) => number; subtask: (a: RegExpExecArray) => number; preferredScorerType: (a: RegExpExecArray) => 'min' | 'max' | 'sum'; } const SubtaskMatcher: MatchRule[] = [ { regex: /^(([A-Za-z0-9]*?)(?:(\d*)[-_])?(\d+))\.(in|txt)$/, output: [ (a) => `${a[1]}.out`, (a) => `${a[1]}.ans`, (a) => `${a[1]}.out`.replace(/input/g, 'output'), (a) => (a[1].includes('input') ? `${a[1]}.txt`.replace(/input/g, 'output') : null), ], id: (a) => +a[4], subtask: (a) => +(a[3] || 1), preferredScorerType: (a) => (a[3] ? 'min' : 'sum'), }, { regex: /^([^\d]*)\.in(\d+)$/, output: [ (a) => `${a[1]}.ou${a[2]}`, (a) => `${a[1]}.ou${a[2]}`.replace(/input/g, 'output'), ], id: (a) => +a[2], subtask: () => 1, preferredScorerType: () => 'sum', }, { regex: /^([^\d]*)([0-9]+)([-_])([0-9]+)\.in$/, output: [ (a) => `${a[1]}${a[2]}${a[3]}${a[4]}.out`, (a) => `${a[1]}${a[2]}${a[3]}${a[4]}.ans`, ], id: (a) => +a[4], subtask: (a) => +a[2], preferredScorerType: () => 'min', }, { regex: /^(([0-9]+)[-_](?:.*))\.in$/, output: [ (a) => `${a[1]}.out`, (a) => `${a[1]}.ans`, ], id: (a) => +a[2], subtask: () => 1, preferredScorerType: () => 'sum', }, ]; function* getScore(totalScore: number, count: number) { const base = Math.floor(totalScore / count); const extra = count - (totalScore % count); for (let i = 0; i < count; i++) { if (i >= extra) yield base + 1; else yield base; } } interface ParsedCase { id?: number; time?: number | string; memory?: number | string; score?: number; input?: string; output?: string; } interface ParsedSubtask { cases: ParsedCase[]; type: 'min' | 'max' | 'sum'; time?: number | string; memory?: number | string; score?: number; id?: number; if?: number[]; } export function readSubtasksFromFiles(files: string[], config) { const subtask: Record = {}; for (const s of config.subtasks || []) if (s.id) subtask[s.id] = s; for (const file of files) { let match = false; for (const rule of SubtaskMatcher) { const data = rule.regex.exec(file); if (!data) continue; const sid = rule.subtask(data); const c = { input: file, output: '', id: rule.id(data) }; const type = rule.preferredScorerType(data); for (const func of rule.output) { if (config.noOutputFile) c.output = '/dev/null'; else c.output = func(data); if (c.output === '/dev/null' || files.includes(c.output)) { match = true; if (!subtask[sid]) { subtask[sid] = { time: config.time, memory: config.memory, type, cases: [c], }; } else if (!subtask[sid].cases) subtask[sid].cases = [c]; else subtask[sid].cases.push(c); break; } } if (match) break; } } return Object.values(subtask); } export interface NormalizedCase extends Required { time: number; memory: number; } export interface NormalizedSubtask extends Required { cases: NormalizedCase[]; time: number; memory: number; } export function normalizeSubtasks( subtasks: ParsedSubtask[], checkFile: (name: string, errMsg: string) => string, time: number | string = '1000ms', memory: number | string = '256m', ignoreParseError = false, timeRate = 1, memoryRate = 1, ): NormalizedSubtask[] { subtasks.sort((a, b) => (a.id - b.id)); const subtaskScore = getScore( Math.max(100 - Math.sum(subtasks.map((i) => i.score || 0)), 0), subtasks.filter((i) => !i.score).length, ); return subtasks.map((s, id) => { s.cases.sort((a, b) => (a.id - b.id)); const score = s.score || subtaskScore.next().value as number; const caseScore = getScore( Math.max(score - Math.sum(s.cases.map((i) => i.score || 0)), 0), s.cases.filter((i) => !i.score).length, ); return { id: id + 1, type: 'min', if: [], ...s, score, time: parseTimeMS(s.time || time, !ignoreParseError) * timeRate, memory: parseMemoryMB(s.memory || memory, !ignoreParseError) * memoryRate, cases: s.cases.map((c, index) => ({ id: index + 1, ...c, score: c.score || (s.type === 'sum' ? caseScore.next().value as number : score), time: parseTimeMS(c.time || s.time || time, !ignoreParseError) * timeRate, memory: parseMemoryMB(c.memory || s.memory || memory, !ignoreParseError) * memoryRate, input: c.input ? checkFile(c.input, 'Cannot find input file {0}.') : '/dev/null', output: c.output ? checkFile(c.output, 'Cannot find output file {0}.') : '/dev/null', })) as NormalizedCase[], }; }); }