1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

同じ枚数の人を同じ順位にする(2通りのやり方)

Posted at

やりたいこと

カード枚数が同じ枚数だったら同じ順位にしたい。

array = [{:name=>"player3", :count=>20},
 {:name=>"player2", :count=>16},
 {:name=>"player1", :count=>16},
 {:name=>"player4", :count=>0}]
 player31位、player22位、player13位、player44位です。

以下のように出力したい。

[{:name=>"player3", :count=>20},
 {:name=>"player2", :count=>16},
 {:name=>"player1", :count=>16},
 {:name=>"player4", :count=>0}]
 player31位、player22位、player12位、player44位です。

考えたこと

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}]
player41位、hi
player23位、hi
player55位です。

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}]
player11位、player42位、player32位、player24位です。

失敗例

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
player12位、helloplayer33位です。
[{: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
player21位、{:name=>"player1", :count=>0, :ranking=>1}
evening
player31位、戦争を終了します。

途中経過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つ前の人よりカード枚数が多い場合の処理は不要であると気づき削除した。

player31位、hello
player42位、hello
player52位、player21位、player11位です。
[{: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
player31位、player42位、player12位、player22位、player22位です。
[{: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

同じ順位が何人かいた場合に、その次の人の順位が間違ってしまう。

player41位、player52位、player22位、player12位、player33位です。
[{: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
player21位、player52位、player43位、player13位、player35位です。
[{: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'などでどのように処理されるか確かめてみると打ち手が見えてくる可能性がある。

1
1
5

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?