Skip to content

perf: compute standings tiebreaker values once and fix Buchholz complexity#7652

Open
Rathoz wants to merge 1 commit into
mainfrom
standings-tiebreaker-single-compute
Open

perf: compute standings tiebreaker values once and fix Buchholz complexity#7652
Rathoz wants to merge 1 commit into
mainfrom
standings-tiebreaker-single-compute

Conversation

@Rathoz

@Rathoz Rathoz commented Jun 12, 2026

Copy link
Copy Markdown
Collaborator

Summary

Two related cuts to tiebreaker computation cost during standings parsing:

  • Every tiebreaker was computed twice per opponent per round. StandingsParser.calculateTiebreakerValues called both valueOf and display, and the default display calls valueOf again (and the diff/winrate overrides recomputed their stats). display(state, opponent, value) now receives the already-computed value; the default implementation and all overriding tiebreakers (Match/Diff, Match/WinRate, Game/Diff, Game/WinRate, Game/Rounds/Diff) use it instead of recomputing. resolveTieForGroup continues to use stored tiebreakerValues/valueOf as before.
  • Buchholz was O(N²·M) with Opponent.same in the inner loops. It now builds the enemy list as a name set via Opponent.toName (with an Opponent.same fallback for renamed-team cases coming from match2 records) and memoizes the result per opponent entry, mirroring the FnUtil.memoize pattern in Tiebreaker/Game/Util.

Stored tiebreakerValues and display strings are unchanged.

Based on standings-tests-ai (#7647), whose tiebreaker and parser specs lock the expected values and display output.

How did you test this change?

  • busted --run=ci: 605 successes / 0 failures, including standings_tiebreaker_spec.lua (buchholz, matchdiff/gamediff/rounddiff display cases) and standings_parser_spec.lua (tiebreakerValues assertions) unchanged
  • luacheck with lua/.luacheckrc: 0 warnings on all touched files

🤖 Generated with Claude Code

@Rathoz Rathoz requested review from a team as code owners June 12, 2026 15:53
@Rathoz Rathoz force-pushed the standings-tiebreaker-single-compute branch from ec216eb to a67a167 Compare June 12, 2026 16:10
return '-'
end
return MathUtil.formatPercentage(self:valueOf(state, opponent), 2)
return MathUtil.formatPercentage(value ~= nil and value or self:valueOf(state, opponent), 2)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

value ~= nil and is redundant

function TiebreakerMatchWinRate:display(state, opponent)
return MathUtil.formatPercentage(self:valueOf(state, opponent), 2)
function TiebreakerMatchWinRate:display(state, opponent, value)
return MathUtil.formatPercentage(value ~= nil and value or self:valueOf(state, opponent), 2)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same

if value == nil then
value = self:valueOf(state, opponent)
end
return tostring(value)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return tostring(value or self:valueOf(state, opponent))

plus kick the if above

Base automatically changed from standings-tests-ai to main July 2, 2026 09:51
…exity

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 2, 2026 09:54
@Rathoz Rathoz force-pushed the standings-tiebreaker-single-compute branch from a67a167 to cc9b45c Compare July 2, 2026 09:54

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR optimizes standings tiebreaker computation by avoiding repeated valueOf() calls during display rendering and by reducing Buchholz’s inner-loop cost via memoization and name-set lookups.

Changes:

  • Compute each tiebreaker’s numeric value once per opponent/round and pass it into display(...) to avoid recomputation.
  • Update default and overridden display(...) implementations to accept the precomputed value.
  • Rework Buchholz enemy detection to use memoized enemy-name sets (with a fallback for renamed teams).

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
lua/wikis/commons/Standings/Parser.lua Computes tiebreaker value once and passes it to display(...).
lua/wikis/commons/Standings/Tiebreaker/Interface.lua Extends display(...) to accept an optional precomputed value.
lua/wikis/commons/Standings/Tiebreaker/Match/WinRate.lua Updates win-rate display to use the passed-in value.
lua/wikis/commons/Standings/Tiebreaker/Match/Diff.lua Updates display signature to accept the passed-in value.
lua/wikis/commons/Standings/Tiebreaker/Game/WinRate.lua Updates win-rate display to use the passed-in value.
lua/wikis/commons/Standings/Tiebreaker/Game/Diff.lua Updates display signature to accept the passed-in value.
lua/wikis/commons/Standings/Tiebreaker/Game/Rounds/Diff.lua Updates display signature to accept the passed-in value.
lua/wikis/commons/Standings/Tiebreaker/Buchholz.lua Improves Buchholz complexity using memoized enemy-name sets with rename fallback.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +33 to +36
---@param value integer?
---@return string
function TiebreakerMatchWinRate:display(state, opponent)
return MathUtil.formatPercentage(self:valueOf(state, opponent), 2)
function TiebreakerMatchWinRate:display(state, opponent, value)
return MathUtil.formatPercentage(value ~= nil and value or self:valueOf(state, opponent), 2)
Comment on lines 37 to +41
local games = TiebreakerGameUtil.getGames(opponent)
if games == 0 then
return '-'
end
return MathUtil.formatPercentage(self:valueOf(state, opponent), 2)
return MathUtil.formatPercentage(value ~= nil and value or self:valueOf(state, opponent), 2)
Comment on lines +39 to +43
---@param value integer?
---@return string
function StandingsTiebreaker:display(state, opponent)
return tostring(self:valueOf(state, opponent))
function StandingsTiebreaker:display(state, opponent, value)
if value == nil then
value = self:valueOf(state, opponent)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants