上間ウェブ店

javascriptでリーグ表に順位をつける方法(勝点方式で、同数の場合は得失点差で、得失点も同数の場合は同順位で、次の順位は同数分下げる)

こんにちは。上間です。

最近リーグ表を作る事がありまして、その順位付けのサンプルプログラムを探してもなかなか見つからなかったんですよ。

最終的にこちらに行き着いたんですが、分解して必要なのに書き換えてみると純粋な順位付けで、同数の場合に得失点差を見るとかしないやつでした。リーグ表って書いてないので当たり前なんですが。

参考のサイトをベースに、タイトルのように順位付けする事ができたので、jsでリーグ表の順位付けってあまりサンプルを見かけないので僕の方で書いて見ようと思います。

やりたい事とやらない事

HTMLに対戦成績と、勝, 負, 引分, 勝点, 得失点差がすでに入っている状態の表から順位をつけます。

  • 勝点方式
  • 勝点が同数の場合は得失点差
  • 得失点も同数の場合は同順位
  • 同順位があった場合の次の順位は同順位の数分下げる

対戦成績から勝ち負けを抽出もできますが、今回は順位付けにフォーカスするために割愛します。
あと、順位による表の並べ替えはしません。1位が上にくるとかはやらないです。

コード

結果から書くとこんな感じです。

<table>
  <tbody>
    <tr>
      <th>チーム名</th>
      <th>チームA</th>
      <th>チームB</th>
      <th>チームC</th>
      <th>チームD</th>
      <th>チームE</th>
      <th>チームF</th>
      <th>勝</th>
      <th>負</th>
      <th>分</th>
      <th>勝点</th>
      <th>得失点</th>
      <th>順位</th>
    </tr>
    <tr>
      <th>チームA</th>
      <td style="background-color:#999;"></td>
      <td>○ 3 - 2</td>
      <td>○ 5 - 2</td>
      <td>× 4 - 6</td>
      <td>○ 4 - 1</td>
      <td>○ 4 - 1</td>
      <td>4</td>
      <td>1</td>
      <td>0</td>
      <td class="win">12</td>
      <td class="diff">8</td>
      <td class="rank"></td>
    </tr>
    <tr>
      <th>チームB</th>
      <td>× 2 - 3</td>
      <td style="background-color:#999;"></td>
      <td>× 2 - 4</td>
      <td>× 2 - 3</td>
      <td>× 0 - 1</td>
      <td>× 0 - 1</td>
      <td>0</td>
      <td>5</td>
      <td>0</td>
      <td class="win">0</td>
      <td class="diff">-6</td>
      <td class="rank"></td>
    </tr>
    <tr>
      <th>チームC</th>
      <td>× 2 - 5</td>
      <td>○ 4 - 2</td>
      <td style="background-color:#999;"></td>
      <td>○ 6 - 5</td>
      <td>○ 5 - 1</td>
      <td>○ 5 - 1</td>
      <td>4</td>
      <td>1</td>
      <td>0</td>
      <td class="win">12</td>
      <td class="diff">8</td>
      <td class="rank"></td>
    </tr>
    <tr>
      <th>チームD</th>
      <td>○ 6 - 4</td>
      <td>○ 3 - 2</td>
      <td>× 5 - 6</td>
      <td style="background-color:#999;"></td>
      <td>○ 8 - 0</td>
      <td>○ 8 - 0</td>
      <td>4</td>
      <td>1</td>
      <td>0</td>
      <td class="win">12</td>
      <td class="diff">18</td>
      <td class="rank"></td>
    </tr>
    <tr>
      <th>チームE</th>
      <td>× 1 - 4</td>
      <td>○ 1 - 0</td>
      <td>× 1 - 5</td>
      <td>× 0 - 8</td>
      <td style="background-color:#999;"></td>
      <td>△ 1 - 1</td>
      <td>1</td>
      <td>3</td>
      <td>1</td>
      <td class="win">4</td>
      <td class="diff">-14</td>
      <td class="rank"></td>
    </tr>
    <tr>
      <th>チームF</th>
      <td>× 1 - 4</td>
      <td>○ 1 - 0</td>
      <td>× 1 - 5</td>
      <td>× 0 - 8</td>
      <td>△ 1 - 1</td>
      <td style="background-color:#999;"></td>
      <td>1</td>
      <td>3</td>
      <td>1</td>
      <td class="win">4</td>
      <td class="diff">-14</td>
      <td class="rank"></td>
    </tr>
  </tbody>
</table>
const wins = document.querySelectorAll('.win')
const makeData = () => {
  const data = []
  Array.from(wins).forEach((e, i) => {
    const teamData = {}
    teamData.team = i
    teamData.win = e.innerText
    teamData.diff = e.nextElementSibling.innerText
    teamData.rank = 0
    data.push(teamData)
  })
  return data
}
const data = makeData()

const ranking = (arr) => {
  const map = arr.map((x, y, z) => {
    let plus = 1
    let filter = z.filter((w) => {
      if (Number(w['win']) === Number(x['win'])) {
        if (Number(w['diff']) > Number(x['diff'])) {
          plus++
        }
      }
      else if (Number(w['win']) > Number(x['win'])) {
        return true
      }
    }).length
    filter += plus
    return filter
  })
  return map
}
const rank = ranking(data)

const ranks = document.querySelectorAll('.rank')
Array.from(ranks).forEach((e, i) => {
  e.innerHTML = rank[i]
})

codepenのも貼っておきますね。

See the Pen league ranking js by uemaSR (@uemaSR) on CodePen.

解説

jsのコードを3つに分けて説明します。

まずは上の方ですね。

勝点のセルを全て抽出して、その隣の得失点差も合わせてデータをとります。

チームの連番と、抽出した勝点, 得失点差, 順位をオブジェクトにして、それを配列にまとめます。

// 勝点のセルを全て選択
const wins = document.querySelectorAll('.win')
// データを抽出する関数
const makeData = () => {
  const data = [] // 最終的に入れる配列を用意
  Array.from(wins).forEach((e, i) => { // 勝点セルのループ
    const teamData = {} // チームずつのオブジェクトを作成
    teamData.team = i // チームの連番をオブジェクトに
    teamData.win = e.innerText // 勝点をオブジェクトに
    teamData.diff = e.nextElementSibling.innerText // 得失点をオブジェクトに
    teamData.rank = 0 // 順位をオブジェクトに(とりあえず0を入れておく)
    data.push(teamData) // 配列に入れていく
  })
  return data
}
const data = makeData() // 上記の関数を発火させて定数にしておく

次に真ん中の方ですね。ここが実際に順位をつけているところです。

流れ的には、さっきとってきたデータから、勝点が多い順に順位をつけて、同数だった場合は得失点差を見て、少ない方に順位をプラス(下げる)。得失点差も同じ場合は同じ順位で、その次の順位は同数の数だけ下げるって感じです。

ちょっと説明が難しいですがコードはこんな感じです。

// 順位付けの関数
const ranking = (arr) => { // 引数arrは上記のデータ
  const map = arr.map((x, y, z) => { // mapは条件から再配置するってイメージ
    let plus = 1 // プログラミングでは最初は0なのでプラス1をするためのやつ。同数の場合に増やすためでもある。
    let filter = z.filter((w) => { // filterは総当りで比較して条件に合えばtrueを返す。抽出条件は下記で。
      // 勝点が同じだった場合
      if (Number(w['win']) === Number(x['win'])) {
        // 得失点差を比べて比較元の方が多い場合
        if (Number(w['diff']) > Number(x['diff'])) {
          plus++ // 後で追加する順位を増やす
        }
      }
      // 比較元が大きかった場合
      else if (Number(w['win']) > Number(x['win'])) {
        // 順位を記録する
        return true
      }
    }).length // これは直前の比較元が大きかった場合の数を記録するってことです
    filter += plus // 数の調整
    return filter
  })
  return map
}
// 発火して定数に入れておく
const rank = ranking(data)

mapとfilterの使い方で四苦八苦しましたが、大雑把に説明すると、mapは再配置、filterは抽出です。

mapのコールバック関数内でfilterを使うので、mapで再配置するアイテムとfilterで全てのアイテムと比較して、条件に合う場合だけtrueを返し、その数を順位として記録しています。

同数の場合はさらに得失点差を比較します。勝点で決まった順位に、同数だった場合は数をプラスして順位を下げる仕組みです。

次に下の方ですが、これはhtmlに順位を書き込む記述ですね。

// 順位を書き込むターゲットを選択
const ranks = document.querySelectorAll('.rank')
// ループで回して書き込みます
Array.from(ranks).forEach((e, i) => {
  e.innerHTML = rank[i]
})

おわりに

なんでこんなにサンプルが少ないんですかね?プログラミングの教材とかにあってもよさそうな感じなんですけど。

本日は以上になります。誰か困っている人の助けになれば幸いです。