Skip to content

Commit c8bff80

Browse files
authoredJun 6, 2020
New system (#18)
* Set default rating to 1400. * Add delta comparison utility.
1 parent 7b875ed commit c8bff80

File tree

5 files changed

+78
-12
lines changed

5 files changed

+78
-12
lines changed
 

‎README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ Carrot runs in the browser and fetches all the data it needs from the [Codeforce
1111
It then calculates the rating changes following the algorithm published by Mike Mirzayanov [here](https://codeforces.com/blog/entry/20762), slightly modified so that it matches the current CF algorithm. This updated algorithm is adapted from [TLE](https://github.com/cheran-senthil/TLE/blob/master/tle/util/ranklist/rating_calculator.py).
1212

1313
#### Is this better than [CF-Predictor](https://codeforces.com/blog/entry/50411)?
14-
Not necessarily. The CF-Predictor extension communicates with a server, while Carrot fetches data and performs all calculations in the browser. So the network usage is significantly lower for CF-Predictor. However, Carrot is 100% accurate and it works in real time.
14+
Not necessarily. The CF-Predictor extension communicates with a server, while Carrot fetches data and performs all calculations in the browser. So the network usage is significantly lower for CF-Predictor. However, Carrot is ~~100% accurate~~(see [#18](https://github.com/meooow25/carrot/pull/18)) and it works in real time.
1515

1616
#### How is Carrot fast enough to calculate rating changes of every contestant in real time?
1717
FFT. The answer is always FFT.

‎carrot/src/background/predict-response.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,9 @@ export default class PredictResponse {
3535
break;
3636
case PredictResponse.TYPE_FINAL:
3737
// For an unrated user, user info has missing rating but if the user participates, the
38-
// oldRating on the ratingChange object is set as 1500. So, for FINAL, at the moment
39-
// rating = effectiveRating always, but keeping the code which works for unrated too, as
40-
// things should be.
38+
// oldRating on the ratingChange object is set as the default starting value. So, for
39+
// FINAL, at the moment rating = effectiveRating always, but keeping the code which works
40+
// for unrated too, as things should be.
4141
rank = Rank.forRating(result.rating);
4242
newRank = Rank.forRating(result.effectiveRating + result.delta);
4343
break;

‎carrot/src/background/predict.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import FFTConv from '../util/conv.js';
1010
*/
1111

1212
const PRINT_PERFORMANCE = false;
13-
const DEFAULT_RATING = 1500;
13+
const DEFAULT_RATING = 1400;
1414

1515
export class Contestant {
1616
constructor(party, points, penalty, rating) {

‎carrot/tests/compare.ts

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import * as colors from 'https://deno.land/std/fmt/colors.ts';
2+
import * as api from '../src/background/cf-api.js';
3+
import predict, { Contestant } from '../src/background/predict.js';
4+
5+
/**
6+
* Compares actual vs calculated rating changes for a given finished rated contest.
7+
* Matches upto Educational round 87 (1354). New rating system was imposed after that.
8+
*
9+
* $ deno run --allow-net compare.ts <contest-id>
10+
*/
11+
12+
async function main() {
13+
const contestId = Deno.args[0];
14+
if (!contestId || isNaN(parseInt(contestId))) {
15+
console.error('Missing or bad contest ID');
16+
return;
17+
}
18+
19+
console.info('Contest ' + contestId);
20+
console.info('Fetching rating changes...');
21+
const ratingChanges = await api.contest.ratingChanges(contestId);
22+
if (ratingChanges.length) {
23+
console.log(' Fetched ' + ratingChanges.length + ' rating changes');
24+
} else {
25+
console.error('Empty rating change list');
26+
return;
27+
}
28+
29+
const rating = Object.fromEntries(
30+
ratingChanges.map((r: any) => [r.handle, { old: r.oldRating, new: r.newRating }]));
31+
32+
console.info('Fetching standings...');
33+
let { contest_, problems_, rows } = await api.contest.standings(contestId);
34+
console.info(' Fetched ' + rows.length + ' rows');
35+
rows = rows.filter((row: any) => row.party.members[0].handle in rating);
36+
console.info(' ' + rows.length + ' rows retained from rating change list');
37+
38+
console.info('Calculating deltas...');
39+
const contestants = rows.map((row: any) => {
40+
const handle = row.party.members[0].handle;
41+
return new Contestant(handle, row.points, row.penalty, rating[handle].old);
42+
});
43+
const predictResults = predict(contestants);
44+
45+
const diffs = [];
46+
for (const res of predictResults) {
47+
const actualDelta = rating[res.handle].new - rating[res.handle].old;
48+
if (res.delta != actualDelta) {
49+
diffs.push([res.handle, rating[res.handle].old, actualDelta, res.delta]);
50+
}
51+
}
52+
if (diffs.length) {
53+
console.error(colors.red(`Delta mismatch for ${diffs.length} contestants:`));
54+
console.error(colors.red('[handle, old rating, actual delta, calculated delta]'));
55+
for (const row of diffs.slice(0, 5)) {
56+
console.error(colors.red('[' + row.join(', ') + ']'));
57+
}
58+
if (diffs.length > 5) {
59+
console.log(colors.red(`...and ${diffs.length - 5} more`));
60+
}
61+
} else {
62+
console.info(colors.green('OK all match'));
63+
}
64+
}
65+
66+
main();

‎carrot/tests/test-predict.ts

+7-7
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,13 @@ function predictedDeltas(data: TestDataRow[]): object {
1919

2020
Deno.test('predict_ok', (): void => {
2121
const data: TestDataRow[] = [
22-
['bigbrain', 4000, 10, 3000, -237],
23-
['smartguy', 2500, 50, 2400, -175],
24-
['ordinaryguy', 1500, 80, 1800, -35],
25-
['brick', -100, 300, 500, -50],
26-
['alt', 5000, 0, undefined, 514],
27-
['luckyguy', 2500, 40, 1800, 121],
28-
['unluckyguy', 800, 40, 2000, -145],
22+
['bigbrain', 4000, 10, 3000, -241],
23+
['smartguy', 2500, 50, 2400, -181],
24+
['ordinaryguy', 1500, 80, 1800, -48],
25+
['brick', -100, 300, 500, -54],
26+
['alt', 5000, 0, undefined, 553],
27+
['luckyguy', 2500, 40, 1800, 118],
28+
['unluckyguy', 800, 40, 2000, -160],
2929
];
3030

3131
assertEquals(predictedDeltas(data), expectedDeltas(data));

0 commit comments

Comments
 (0)