CodeIQ:レースゲームの最終順位を決めよう

私自身が表題の問題を解いた時のプログラムについて解説します。
問題の詳細は「レースゲームの最終順位を決めよう」(CodeIQ)を参照してください。

問題の概要

問題を引用します。
あなたはレースゲームの開発に携わっており、プレイヤーの最終順位を決めるアルゴリズム開発を任されました。
そのゲームは、複数のレースを行い、各レースの順位から最終順位を決めるのですが、順位はそのまま合計したり、算術平均してもおかしな結果になってしまいます。

これは、順位は大小には意味があっても、間隔には意味はないため、1位+2位が3位を意味しないように、足し算引き算が成立しないことに起因します。
そこで、ひとまず「調和平均」を用いて、解決してみることにしました。

求められるプログラムの前提条件は、以下の通りとなります。

標準入力から、各レースの順位(スペース区切り)が、次のようにプレイヤーの人数分複数行送られる
(プレイヤー1) 1回目のレースの順位 2回目のレースの順位 3回目のレースの順位 ...
(プレイヤー2) 1回目のレースの順位 2回目のレースの順位 3回目のレースの順位 ...
:
:

入力されるテキストの行番号をプレイヤーの番号とみなす
プレイヤーの人数は、2人以上12人以下とする
レースの回数は、2回以上4回以下とする
各レースにおける順位に、重複や欠落はないものとする
最終順位は、プレイヤーごとの全レースの順位の調和平均(Wikipediaへのリンク)が低い順に求める
このとき調和平均が同じプレイヤー同士は、同じ順位とし、下の順位に合わせる。
※例えば、プレイヤーが3人で、上位2人が同じ調和平均の場合、2位が2名、3位が1名となる
求めた最終順位をプレイヤーの番号順に、改行区切りで標準出力に送ること

そして 以下が、入力と出力例になります。

No.入力例出力例
1 2 4 3 4
1 1 2 1
3 2 1 2
4 3 4 3
3
1
2
4
2 1 2
2 5
3 4
4 3
5 1
1
3
5
5
2

上記の1番目の例の場合、各プレイヤーの順位の調和平均は、小数点第5位以下切捨てで、次のようになります。
3.0000
1.1428
1.7142
3.4285

この数字が小さいほど上位となるので、プレイヤーの番号順に 3位 1位 2位 4位となるわけですね。
この最終順位は、直観的にも納得がいく結果ではないでしょうか。

実際のゲームでは順位を 1位→15pts、2位→12pts、3位→10pts という風にポイントに換算し、ポイントの合計で最終順位を決めるのが一般的です。
ただ順位という数字を通じて、平均にもいろんな種類があり、適材適所で活用できることに気づいていただけたら幸いです。

【問題】
標準入力から、レースゲームにおける各レースの順位が、プレイヤーの人数分複数行送られます。
全レースの順位の調和平均から、各プレイヤーの最終順位を求め、その結果を標準出力に返してください。

私のプログラム

Rubyで解答しています。

#!/usr/bin/ruby

def hMean(arr)
	n = arr.size
	x = 0

	for a in arr
		x += Rational(1,a)
	end

	return Rational(n*x.denominator, x.numerator)
end

def getResult(n, pts)
	ret = 0
	for pt in pts
		ret += 1 if pt <= n
	end
	return ret
end

# main
pts = []
while line = gets
	line.strip!
	arr = line.split.map{|a| a.to_i}

	pts << hMean(arr)
end

for pt in pts
	p getResult(pt, pts)
end

解説

問題の通りにやればできます。
Rubyには有理数型があるので浮動小数点誤差も気にしなくて良いのが便利です。

考え方

Wikipediaから調和平均は次の様になるとあります。
n/H = 1/x1 + 1/x2 + ... + 1/xn

X = 1/x1 + 1/x2 + ... + 1/xn
とすると、
n/H = X
1/H = X/n
H = n * 1/X

なので、入力値の逆数の和を求め、その逆数に入力値の数をかければ調和平均を求めることができます。
後は、これを順位に直せば良いわけです。

main

入力値を1行ずつ数値に分割し、1行の値の調和平均をptsに保持します。
ptsをgetResult()で順位に直して印字します。

hMean(arr)

引数arrの調査平均を求めます。
考え方の通り、arrの値の逆数の和を求めます(7〜9行目)。
その和の逆数にnarrの要素数nをかけて調和平均にします。

getResult(n, pts)

順位を求めます。
引数nは順位を求める調和平均の値、ptsは全ての選手の調和平均の値のリストです。
nの順位はptsにあるn以下の値の数なのでそれを求めて返します。

雑感

有理数型は誤差の問題がないので非常に便利です。