📈

AtCoder Heuristic Contestのレーティング予測計算機を作った

2022/05/26に公開

作ったもの

競技プログラミングサイトAtCoderのコンテストの一種であるAtCoder Heuristic Contestレーティング予測計算機を作りました。

https://github.com/3w36zj6/atcoder-heuristic-rating-estimator

https://3w36zj6.github.io/atcoder-heuristic-rating-estimator/

過去のコンテストの出場記録を元に、次回のコンテストでどれくらいのパフォーマンスを得るとどれくらいのレーティングに到達できるかをグラフで確認することができます。

(よろしければstarを頂けると幸いです)

動機

  • Algorithmコンテストのレーティング予測計算サイトは複数存在しているが、Heuristicコンテストのものは自分が探した限りなかったので
  • Svelteのお勉強で何か役に立つものを作りたかった

技術

Svelte

フロントエンドではSvelteというフレームワークを使用しました。

https://svelte.jp/

Svelteは公式Blog曰く、ReactやVueに比べて簡潔にコードが書け、パフォーマンスが優れている点が大きな特徴です。環境構築が簡単でTypeScriptにも対応しており、Svelteは初めてフロントエンド開発をやる方にはおすすめだと思います。

https://svelte.jp/blog/write-less-code

https://svelte.jp/blog/virtual-dom-is-pure-overhead

公式のサンプルコードを見てもらえればわかる通り、HTML, CSS, JavaScript (TypeScript)を合体させたような書き方をします。

https://svelte.dev/examples/hello-world

Svelteの環境構築とデプロイについては以下のScrapsをご覧ください。

https://zenn.dev/3w36zj6/scraps/3e2e153962b9df

Rating計算

公式ドキュメントの通りに実装しました。

https://www.dropbox.com/s/ne358pdixfafppm/AHC_rating.pdf

まずi回目(1\leq i \leq N)のコンテストのパフォーマンスをP_iとします。このときに1\leq i\leq Nを満たす全ての整数i1\leq j\leq 100を満たす全ての整数jに対してP_i-724.4744301\ln{j}を計算し、これらを降順にソートしたものを数列Qとします。

このときQは少なくとも100個の要素を持ち、補正前のRatingであるrは以下の式で与えられます。

r = \frac{\sum_{i=1}^{100}Q_i \times 0.8271973364^i}{\sum_{i=1}^{100}0.8271973364^i}

そして補正後のRatingであるr'は以下の式で与えられます。

r'= \begin{cases} r & r\geq 400 \\ \frac{400}{\exp{\frac{400-r}{400}}} & r < 400 \end{cases}

Chart.js

グラフ描画にはChart.jsを使用しました。綺麗なアニメーション付きのグラフを簡単に表示することができます。Chart.jsではグラフに背景を入れることができないため、背景のRatingの色はcanvasを直接操作して描画しました。

const drawBackground = (target) => {
  const xScale = target.scales["x-axis-0"]
  const yScale = target.scales["y-axis-0"]
  for (const rate of [...Array(10).keys()].map((i) => {
    return i * 400
  })) {
    chartCanvas.getContext("2d").fillStyle = `rgba(${colorRating(rate).join(
      ", "
    )}, 0.2)`
    chartCanvas
      .getContext("2d")
      .fillRect(
        xScale.left,
        Math.min(yScale.getPixelForValue(rate + 400), yScale.bottom),
        xScale.width,
        Math.min(yScale.getPixelForValue(rate), yScale.bottom) -
          Math.min(yScale.getPixelForValue(rate + 400), yScale.bottom)
      )
    chartCanvas.getContext("2d").fillStyle = `rgba(${colorRating(rate).join(
      ", "
    )}, 0.5)`
    chartCanvas
      .getContext("2d")
      .fillRect(
        xScale.getPixelForValue(rate),
        yScale.bottom,
        Math.min(xScale.getPixelForValue(rate + 400), xScale.right) -
          Math.min(xScale.getPixelForValue(rate), xScale.right),
        5
      )
  }
}

Google Apps Script

レーティング予測計算機として使えるものはとりあえず完成したのですが、このままでは過去全てのパフォーマンスを手入力する必要があり面倒なため、指定したアカウントの過去全てのコンテストの記録をAtCoderから取得できるようにしたいです。しかしフロントエンドだけではCORSエラーで不可能なため、Google Apps ScriptでAtCoderのWeb APIを叩くための自分用Web APIを作り、これを経由させてコンテストの記録を取得することにしました。

TypeScriptとVS Codeで開発したかったのでClaspというGoogle製のCLIツールを使用しました。

https://github.com/google/clasp

環境構築とデプロイについては以下のScrapsをご覧ください。

https://zenn.dev/3w36zj6/scraps/6ba3591e995689

AtCoderにはUrlFetchApp.fetchでGETリクエストを投げます。GETのパラメーターを綺麗に埋め込む方法が無さそうなため、テンプレートリテラルでAtCoderのURLに直接記述しています。返ってきたテキストはJSON.parseでJavaScriptオブジェクトに変換します。

GASで返すデータはContentService.createTextOutputGoogleAppsScript.Content.TextOutputオブジェクトを作成し、setContentで返すデータをセットします。今回はAtCoderで受け取ったものをそのまま返すことにしました。

const doGet = (e) => {
    const url: string = `https://atcoder.jp/users/${e.parameter.id}/history/json?contestType=${e.parameter.type}`

    const params: GoogleAppsScript.URL_Fetch.URLFetchRequestOptions = {
        "method": "get",
        "headers": {}
    }

    const response = JSON.parse(UrlFetchApp.fetch(url, params).getContentText())

    const output: GoogleAppsScript.Content.TextOutput = ContentService.createTextOutput()
    output.setMimeType(ContentService.MimeType.JSON)
    output.setContent(JSON.stringify(response))

    return output
}

おわりに

Svelteの簡潔な文法やサンプルコードのページがわかりやすいこともあり簡単にフロントエンド開発を行うことができました。またGoogle Apps ScriptをTypeScriptで書いて簡易Web APIサーバーを立てることができました。この記事がどなたかの参考になれば幸いです。

Discussion