やりたいこと
カード枚数が同じ枚数だったら同じ順位にしたい。
array = [{:name=>"player3", :count=>20},
{:name=>"player2", :count=>16},
{:name=>"player1", :count=>16},
{:name=>"player4", :count=>0}]
player3が1位、player2が2位、player1が3位、player4が4位です。
以下のように出力したい。
[{:name=>"player3", :count=>20},
{:name=>"player2", :count=>16},
{:name=>"player1", :count=>16},
{:name=>"player4", :count=>0}]
player3が1位、player2が2位、player1が2位、player4が4位です。
考えたこと
「each_with_index
メソッドで順番に比較して、値が同じなら同じ順位になるように設定できるのではないか?」
「初期値を1としたranking変数を用意して、1つ前と比べて大きければ+1するようにすれば同じことを実現できるのではないか?」
試したこと
やり方①
[前提]
・ここまでに配列の要素は降順にしてある。
・カード枚数が多い人から高い順位となる。
・先頭の人は順位がインデックス+1
位となる。(1位)
・2番目以降の人は、1つ前の人のカード数より少ない場合は、順位がインデックス+1
位となる。
例:配列で3番目にあって、1つ前より少なければ、2+1
で3位になる。
・1つ前の人のカード枚数と同じであれば、インデックス番号がそのまま順位になる。
例:配列で3番目にあって、1つ前と同数ならば、インデックス2
だから2位になる。
途中経過
・前の人とカード枚数が違う場合と同じ場合で分けた。
・2行目のif i < n - 1
は、「player4が1位、」「player2が3位、」はprint
メソッドで、最後の要素だけはputs
で「player5が5位です。」と出力するため。
・カード枚数が同じ場合の処理はまだ書けていないが、条件ごとに分けるところまで。
array.each_with_index do |player, i|
if i < n - 1
# カード枚数が前の人と違う場合
if array[i][:count] != array[i - 1][:count]
print "#{player[:name]}が#{i + 1}位、"
else # カード枚数が同数の場合
next if i == 0
puts 'hi'
end
else
puts "#{player[:name]}が#{i + 1}位です。"
end
end
[{:name=>"player4", :count=>15},
{:name=>"player3", :count=>15},
{:name=>"player2", :count=>10},
{:name=>"player1", :count=>10},
{:name=>"player5", :count=>0}]
player4が1位、hi
player2が3位、hi
player5が5位です。
puts 'hi'
を置いて、コードがどういう順番で実行されているのか確認しながら進めた。
あとは'hi'
のところに2位と4位が表示されるようにすれば良さそう。
先述のとおり、1つ前の人のカード枚数と同じであれば、インデックス番号がそのまま順位になるようにした。
array.each_with_index do |player, i|
if i < n - 1
# カード枚数が前の人と違う場合
if array[i][:count] != array[i - 1][:count]
print "#{player[:name]}が#{i + 1}位、"
else # カード枚数が同数の場合
next if i == 0
print "#{player[:name]}が#{i}位、"
end
else
puts "#{player[:name]}が#{i + 1}位です。"
end
end
その結果、同じカード枚数の場合は同じ順位になった。
3番目のplayer3は、1つ前のplayer4とカード枚数が同じなので、インデックス2
から2位となる。
その後のplayer2は、1つ前のplayer3とカード枚数が違うので、インデックス3
+1で4位となる。
[{:name=>"player1", :count=>20},
{:name=>"player4", :count=>16},
{:name=>"player3", :count=>16},
{:name=>"player2", :count=>0}]
player1が1位、player4が2位、player3が2位、player2が4位です。
失敗例
4行目の!=
を<
にすると失敗する。
1位が出力されなくなる。
array.each_with_index do |player, i|
if i < n - 1
# != を < にするとうまくいかない
if array[i][:count] < array[i - 1][:count]
print "#{player[:name]}が#{i + 1}位、"
print 'hello'
else
next if i == 0
print "#{player[:name]}が#{i}位、"
end
else
puts "#{player[:name]}が#{i + 1}位です。"
end
end
player1が2位、helloplayer3が3位です。
[{:name=>"player2", :count=>36},
{:name=>"player1", :count=>15},
{:name=>"player3", :count=>0}]
理由は、i == 0
の場合に、4行目のarray[i - 1][:count]
がarray[-1][:count]
となり、配列の最後の人のカード枚数となってしまうから。
配列の要素は降順になっており、先頭の人の枚数が最後の人の枚数より少ない場合はないので、i == 0
の場合に順位をつける処理が実行されない。
やり方②
初期値を1としたranking変数を用意して、2つの要素を比較した結果によって更新されるようにする。
失敗例
array.each_with_index do |player, i|
# nilにならない間処理が続く
if i < n -1
player[:ranking] = 1
# 2つ比べて枚数が少ない方はランキング変数が1つ増える
if array[i][:count] > array[i + 1][:count]
array[i + 1][:ranking] += 1
elsif array[i][:count] < array[i + 1][:count]
array[i][:ranking] += 1
end
if i < n - 1
print "#{player[:name]}が#{player[:ranking]}位、"
else
puts "#{player[:name]}が#{player[:ranking]}位です。"
end
end
end
war_step2.rb:248:in `block in <main>': undefined method `+' for nil (NoMethodError)
array[i + 1][:ranking] += 1
nil
に対して+
は使えない。
エラーが出たコードの1行上にp array[i + 1]
を追加して、array[i + 1]
の中身を確認する。
{:name=>"player3", :count=>21}
:ranking
キーがないのに1を足そうとしているからエラーになったと考えられる。
:ranking
キーに初期値「1」を指定すると、エラーが解消した。
array[i + 1][:ranking] = 1
array[i + 1][:ranking] += 1
{:name=>"player3", :count=>21, :ranking=>1}
evening
player2が1位、{:name=>"player1", :count=>0, :ranking=>1}
evening
player3が1位、戦争を終了します。
途中経過1
やり方①と同じ条件分岐にして、どのように出力されるか確認してみる。
array.each_with_index do |player, i|
# :rankingに初期値を設定
player[:ranking] = 1
if i < n - 1
if i != 0
# 1つ前の人とカード枚数が同じの場合
if array[i][:count] == array[i - 1][:count]
array[i][:ranking] = array[i][:ranking]
#1つ前の人よりカード枚数が多い場合(不要)
elsif array[i][:count] > array[i - 1][:count]
array[i - 1][:ranking] += 1
#1つ前の人よりカード枚数が少ない場合
elsif array[i][:count] < array[i - 1][:count]
array[i][:ranking] += 1
puts "hello"
end
end
print "#{player[:name]}が#{player[:ranking]}位、"
else
puts "#{player[:name]}が#{player[:ranking]}位です。"
end
end
pp array
そもそもこの配列は降順にしてあるので、1つ前の人よりカード枚数が多い場合の処理は不要であると気づき削除した。
player3が1位、hello
player4が2位、hello
player5が2位、player2が1位、player1が1位です。
[{:name=>"player3", :count=>30, :ranking=>1},
{:name=>"player4", :count=>20, :ranking=>2},
{:name=>"player5", :count=>0, :ranking=>2},
{:name=>"player2", :count=>0, :ranking=>1},
{:name=>"player1", :count=>0, :ranking=>1}]
途中経過2
1位以外の人が全員2位になってしまった。
array.each_with_index do |player, i|
player[:ranking] = 1
if i != 0
if array[i][:count] == array[i - 1][:count]
array[i][:ranking] = array[i][:ranking]
print "#{player[:name]}が#{player[:ranking]}位、"
if i == n - 1
puts "#{player[:name]}が#{player[:ranking]}位です。"
end
elsif array[i][:count] < array[i - 1][:count]
array[i][:ranking] += 1
print "#{player[:name]}が#{player[:ranking]}位、"
if i == n - 1
puts "#{player[:name]}が#{player[:ranking]}位です。"
end
end
else
print "#{player[:name]}が#{player[:ranking]}位、"
end
end
player3が1位、player4が2位、player1が2位、player2が2位、player2が2位です。
[{:name=>"player3", :count=>28, :ranking=>1},
{:name=>"player4", :count=>16, :ranking=>2},
{:name=>"player1", :count=>8, :ranking=>2},
{:name=>"player2", :count=>0, :ranking=>2}]
1位以外が2位にしかならない原因は、2行目のplayer[:ranking] = 1
で毎回1にしてしまっているから。
1つの前の要素の順位が次の要素に引き継がれていないので、12行目のarray[i][:ranking] += 1
が毎回1 + 1
で2になってしまう。
途中経過3(失敗)
2位以下の順位が正しく出力されるように修正した。
1つ前の人よりカード枚数が少ない場合、1つ前の人の順位に+1する。
array[i][:ranking] = array[i - 1][:ranking] + 1
array.each_with_index do |player, i|
player[:ranking] = 1
if i < n - 1
if i != 0
if array[i][:count] == array[i - 1][:count]
array[i][:ranking] = array[i - 1][:ranking]
print "#{player[:name]}が#{player[:ranking]}位、"
elsif array[i][:count] < array[i - 1][:count]
array[i][:ranking] = array[i - 1][:ranking] + 1
print "#{player[:name]}が#{player[:ranking]}位、"
end
else
print "#{player[:name]}が#{player[:ranking]}位、"
end
else
if array[i][:count] == array[i - 1][:count]
array[i][:ranking] = array[i][:ranking]
puts "#{player[:name]}が#{player[:ranking]}位です。"
elsif array[i][:count] < array[i - 1][:count]
# 1つ前の要素の順位に+1する
array[i][:ranking] = array[i - 1][:ranking] + 1
puts "#{player[:name]}が#{player[:ranking]}位です。"
end
end
end
同じ順位が何人かいた場合に、その次の人の順位が間違ってしまう。
player4が1位、player5が2位、player2が2位、player1が2位、player3が3位です。
[{:name=>"player4", :count=>20, :ranking=>1},
{:name=>"player5", :count=>10, :ranking=>2},
{:name=>"player2", :count=>10, :ranking=>2},
{:name=>"player1", :count=>10, :ranking=>2},
{:name=>"player3", :count=>0, :ranking=>3}]
途中経過4(成功)
1つ前の人よりカード枚数が少ない場合、自分よりカード枚数が多い人の数をカウントする。
(単純に自分より前にある要素数を取得すればよかったかもしれない...)
if array[i][:count] == array[i - 1][:count]
array[i][:ranking] = array[i][:ranking]
puts "#{player[:name]}が#{player[:ranking]}位です。"
elsif array[i][:count] < array[i - 1][:count]
# 1つ前の要素の順位に+1する
# array[i][:ranking] = array[i - 1][:ranking] + 1
# その要素より大きい数の要素数をカウントする
num = array.count do |x|
x[:count] > array[i][:count]
end
array[i][:ranking] = num + 1
puts "#{player[:name]}が#{player[:ranking]}位です。"
end
player2が1位、player5が2位、player4が3位、player1が3位、player3が5位です。
[{:name=>"player2", :count=>25, :ranking=>1},
{:name=>"player5", :count=>15, :ranking=>2},
{:name=>"player4", :count=>5, :ranking=>3},
{:name=>"player1", :count=>5, :ranking=>3},
{:name=>"player3", :count=>0, :ranking=>5}]
完成したコード
重複が多いので、クラスやインスタンスを使って整理して書けるようになりたい。
array.each_with_index do |player, i|
player[:ranking] = 1
if i < n - 1
if i != 0
if array[i][:count] == array[i - 1][:count]
array[i][:ranking] = array[i - 1][:ranking]
print "#{player[:name]}が#{player[:ranking]}位、"
elsif array[i][:count] < array[i - 1][:count]
# その要素より大きい数の要素数をカウントする
num = array.count do |x|
x[:count] > array[i][:count]
end
array[i][:ranking] = num + 1
print "#{player[:name]}が#{player[:ranking]}位、"
end
else
print "#{player[:name]}が#{player[:ranking]}位、"
end
else
if array[i][:count] == array[i - 1][:count]
array[i][:ranking] = array[i][:ranking]
puts "#{player[:name]}が#{player[:ranking]}位です。"
elsif array[i][:count] < array[i - 1][:count]
# その要素より大きい数の要素数をカウントする
num = array.count do |x|
x[:count] > array[i][:count]
end
array[i][:ranking] = num + 1
puts "#{player[:name]}が#{player[:ranking]}位です。"
end
end
end
学んだこと
・next
を使うと条件を満たす場合に処理を中断して次の処理が実行される。
・やり方①はputs 'hi'
の出力結果を見てから思いついた。詰まったときはputs 'hi'
などでどのように処理されるか確かめてみると打ち手が見えてくる可能性がある。