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?

More than 1 year has passed since last update.

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

Posted at

やりたいこと

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

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'などでどのように処理されるか確かめてみると打ち手が見えてくる可能性がある。

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?