Skip to content

Commit 6613b28

Browse files
committed
Introduce the concept of sort keys.
Progress towards #2525. The basic idea is to replace a lot of scattered scoring logic (which might become more complex when we introduce more scoring options such as optimization problems) by encoding sufficient information within the existing rank cache. The information is encoded into a sort key that is designed to be sorted descendingly. The sort key is a tuple (serialized as string into the database) of fixed precision decimals. Each tuple uses 9 decimals and is left padded with `0`s, enabling sorting via standard database query operations. For ascendingly sorted tuple entries (e.g. penalty time), values are subtracted from a very large constant - this ensures that that the key can be sorted as a whole. For example, with ICPC scoring, the top 3 teams from NWERC 2024 receive these sort keys: ``` 00000000000000000000013.000000000,99999999999999999998725.000000000,99999999999999999999711.000000000 00000000000000000000011.000000000,99999999999999999998918.000000000,99999999999999999999701.000000000 00000000000000000000011.000000000,99999999999999999998916.000000000,99999999999999999999706.000000000 ``` This mechanism should facilitate the implementation of other planned scoring methods, particularly partial scoring and optimization problems, with reasonable complexity in the business logic. We do use `bcmath` with fixed precision to avoid numerical precision issues caused by the order of floating point operations. While not critical currently, this will be essential when handling non-integers, such as those in optimization problems. The new mechanism also caches some computations and thus improves scoreboard computation efficiency.
1 parent a82a322 commit 6613b28

15 files changed

+432
-346
lines changed

doc/manual/install-domserver.rst

+2-2
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,14 @@ GNU/Linux, or one of its derivative distributions like Ubuntu::
3737

3838
sudo apt install libcgroup-dev make acl zip unzip pv mariadb-server apache2 \
3939
php php-fpm php-gd php-cli php-intl php-mbstring php-mysql \
40-
php-curl php-json php-xml php-zip composer ntp python3-yaml
40+
php-curl php-json php-xml php-zip composer ntp python3-yaml php-bcmath
4141

4242
The following command can be used on Fedora, and related distributions like
4343
Red Hat Enterprise Linux and Rocky Linux (before V9)::
4444

4545
sudo dnf install libcgroup-devel make acl zip unzip pv mariadb-server httpd \
4646
php-gd php-cli php-intl php-mbstring php-mysqlnd php-fpm \
47-
php-xml php-zip composer chronyd python3-pyyaml
47+
php-xml php-zip composer chronyd python3-pyyaml php-bcmath
4848

4949
`nginx` can be used as an alternate web server.
5050

webapp/composer.json

+1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
],
4141
"require": {
4242
"php": "^8.1.0",
43+
"ext-bcmath": "*",
4344
"ext-ctype": "*",
4445
"ext-curl": "*",
4546
"ext-fileinfo": "*",

webapp/composer.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace DoctrineMigrations;
6+
7+
use Doctrine\DBAL\Schema\Schema;
8+
use Doctrine\Migrations\AbstractMigration;
9+
10+
/**
11+
* Auto-generated Migration: Please modify to your needs!
12+
*/
13+
final class Version20250309122806 extends AbstractMigration
14+
{
15+
public function getDescription(): string
16+
{
17+
return 'Add sort keys to rankcache, allowing us to support different scoring functions efficiently and elegantly';
18+
}
19+
20+
public function up(Schema $schema): void
21+
{
22+
// this up() migration is auto-generated, please modify it to your needs
23+
$this->addSql('ALTER TABLE rankcache ADD sort_key_public TEXT DEFAULT \'\' NOT NULL COMMENT \'Opaque sort key for public audience.\', ADD sort_key_restricted TEXT DEFAULT \'\' NOT NULL COMMENT \'Opaque sort key for restricted audience.\'');
24+
$this->addSql('CREATE INDEX sortKeyPublic ON rankcache (sort_key_public)');
25+
$this->addSql('CREATE INDEX sortKeyRestricted ON rankcache (sort_key_restricted)');
26+
}
27+
28+
public function down(Schema $schema): void
29+
{
30+
// this down() migration is auto-generated, please modify it to your needs
31+
$this->addSql('DROP INDEX sortKeyPublic ON rankcache');
32+
$this->addSql('DROP INDEX sortKeyRestricted ON rankcache');
33+
$this->addSql('ALTER TABLE rankcache DROP sort_key_public, DROP sort_key_restricted');
34+
}
35+
36+
public function isTransactional(): bool
37+
{
38+
return false;
39+
}
40+
}

webapp/src/Controller/Jury/ImportExportController.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -412,7 +412,7 @@ protected function getResultsHtml(
412412
$filter = new Filter();
413413
$filter->categories = $categoryIds;
414414
$scoreboard = $this->scoreboardService->getScoreboard($contest, true, $filter);
415-
$teams = $scoreboard->getTeams();
415+
$teams = $scoreboard->getTeamsInDescendingOrder();
416416

417417
$teamNames = [];
418418
foreach ($teams as $team) {

webapp/src/Entity/RankCache.php

+39
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<?php declare(strict_types=1);
22
namespace App\Entity;
33

4+
use Doctrine\DBAL\Platforms\AbstractMySQLPlatform;
45
use Doctrine\ORM\Mapping as ORM;
56

67
/**
@@ -18,6 +19,8 @@
1819
#[ORM\Index(columns: ['cid', 'points_public', 'totaltime_public', 'totalruntime_public'], name: 'order_public')]
1920
#[ORM\Index(columns: ['cid'], name: 'cid')]
2021
#[ORM\Index(columns: ['teamid'], name: 'teamid')]
22+
#[ORM\Index(columns: ['sort_key_public'], name: 'sortKeyPublic')]
23+
#[ORM\Index(columns: ['sort_key_restricted'], name: 'sortKeyRestricted')]
2124
class RankCache
2225
{
2326
#[ORM\Column(options: [
@@ -62,6 +65,20 @@ class RankCache
6265
#[ORM\JoinColumn(name: 'teamid', referencedColumnName: 'teamid', onDelete: 'CASCADE')]
6366
private Team $team;
6467

68+
#[ORM\Column(
69+
type: 'text',
70+
length: AbstractMySQLPlatform::LENGTH_LIMIT_TEXT,
71+
options: ['comment' => 'Opaque sort key for public audience.', 'default' => '']
72+
)]
73+
private string $sortKeyPublic = '';
74+
75+
#[ORM\Column(
76+
type: 'text',
77+
length: AbstractMySQLPlatform::LENGTH_LIMIT_TEXT,
78+
options: ['comment' => 'Opaque sort key for restricted audience.', 'default' => '']
79+
)]
80+
private string $sortKeyRestricted = '';
81+
6582
public function setPointsRestricted(int $pointsRestricted): RankCache
6683
{
6784
$this->points_restricted = $pointsRestricted;
@@ -149,4 +166,26 @@ public function getTeam(): Team
149166
{
150167
return $this->team;
151168
}
169+
170+
public function setSortKeyPublic(string $sortKeyPublic): RankCache
171+
{
172+
$this->sortKeyPublic = $sortKeyPublic;
173+
return $this;
174+
}
175+
176+
public function getSortKeyPublic(): string
177+
{
178+
return $this->sortKeyPublic;
179+
}
180+
181+
public function setSortKeyRestricted(string $sortKeyRestricted): RankCache
182+
{
183+
$this->sortKeyRestricted = $sortKeyRestricted;
184+
return $this;
185+
}
186+
187+
public function getSortKeyRestricted(): string
188+
{
189+
return $this->sortKeyRestricted;
190+
}
152191
}

webapp/src/Service/AwardService.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ protected function loadAwards(Contest $contest, Scoreboard $scoreboard): void
1616
{
1717
$group_winners = $problem_winners = $problem_shortname = [];
1818
$groups = [];
19-
foreach ($scoreboard->getTeams() as $team) {
19+
foreach ($scoreboard->getTeamsInDescendingOrder() as $team) {
2020
$teamid = $team->getExternalid();
2121
if ($scoreboard->isBestInCategory($team)) {
2222
$catId = $team->getCategory()->getExternalid();

0 commit comments

Comments
 (0)