質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.53%

  • Ruby

    9214questions

    Rubyはプログラミング言語のひとつで、オープンソース、オブジェクト指向のプログラミング開発に対応しています。

rubyで複雑な計算処理を簡潔に実装したい

解決済

回答 4

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 918

17number

score 6

前提・実現したいこと

以下のような仕組みで最終順位を計算したいのですが、rubyでどう書くのが簡潔なのかで悩んでいます。力技で実現できるとは思うのですが、このメソッドを使う方が良いなど、何か簡潔な方法やアドバイスなどあれば教えてください。

順位付け方法

  • 複数の選手を複数の審査員が順位付けして、全ての情報を総合して順位を計算する
  • 審査員は必ず奇数人数
  審査員A 審査員B 審査員C 審査員D 審査員E
Ken 4 7 1 1 1
Shinji 1 3 4 2 3
Taro 3 2 2 4 4
Kazuya 5 5 6 3 2
Shota 7 4 3 5 5
Fumiya 6 1 7 6 6
Ryu 2 6 5 7 7

※審査員Aは、 Shinji → Ryu → Taro → Ken → Kazuya → Fumiya → Shota の順で順位付けしたイメージ。

  • これをまずはソート
 -   -   -   中央値   -   - 
Ken 1 1 1 4 7
Shinji 1 2 3 3 4
Taro 2 2 3 4 4
Kazuya 2 3 5 5 6
Shota 3 4 5 5 7
Fumiya 1 6 6 6 7
Ryu 2 5 6 7 7
  • 最初の判定基準:中央値の小さい方が上位。この時点で Ken の1位が確定
  • 中央値が同じ場合は、中央値以降で同じ数値をいくつ保持しているかで比較。Shinji と Taro は、中央値3をそれぞれ 2つ vs 1つとなるので、Shinji の方が上位
  • 同様に Kazuya と Shota を比較すると、双方とも 中央値5を 2つずつ保持している。この場合は、中央値より手前の数値の総和が小さいものを上位とする。Kazuya は 5(=2+3)、Shota は 7(=3+4)となるので、Kazuya の方が上位
  • 同様に Fumiya と Ryu を比較すると、中央値の保持数が 2つ vs 1つ となるので、Fumiya の方が上位
  • なお、中央値、中央値の保持数、中央値より手間の総和比較も同値だった場合は、中央値より後の数値を順次比較していき、数値の小さい方が上位
  • 全ての基準に照らし合わせて、全て同じ値となった場合は同率順位

コードの前提条件

事前に取得できている情報は以下のようなハッシュ形式。

pry> rank_hash
=> {"Ken"=>[1, 1, 2, 3, 4],
    "Shinji"=>[2, 2, 2, 3, 4],
    "Taro"=>[1, 1, 4, 4, 5],
    ...}
  • キー : 選手名
  • バリュー : その選手に付けられた順位の配列(ソート済)

現状のコード(作りかけ)

コンソールで色々と動作確認しながら書いていっているため、関数分割したりなどは現時点では未考慮になります。

# 中央値を取得するためのインデックス
median_cnt = judge_num / 2  # 審査員の数(judge_num)は設定ファイルなどから取得済と思ってください

judging_infos = []
rank_hash.each do |name, ranks|
  # 中央値
  median = ranks[median_cnt]

  # 中央値以降の順位
  ranks_latter_half = ranks.drop(median_cnt)

  # 中央値が後半にいくつあるかをカウント
  majority_cnt = ranks_latter_half.group_by(&:itself)[median].count

  # 中央値より前の順位
  ranks_first_half = ranks.take(median_cnt)

  # 中央値より前の合計値
  upper_sum = ranks_first_half.sum

  # 算出した情報をまとめて配列に保持
  judging_infos << [name, median, majority_cnt, upper_sum]
end

# 算出した情報を中央値でソート
judging_infos.sort!{|a, b| a[1] <=> b[1]}

# これ以降、上記までに取得した情報を使って処理すれば良いものと思いロジックを検討中

# 先頭の要素を取得
place, name_places = 1, {}
judging_info = judging_infos.shift
while(judging_infos.present?) do
  median = judging_info[1]

  # 中央値が異なるので順位確定
  unless median == judging_infos[0][1]
     name_places.store(judging_info[0], place)
     place += 1
     next
  end

  # 中央値が同じものを処理する
  # このあたりの処理が while の多重ループになりそうで頭を悩ませ中
  # 一通り組み終わった後でリファクタしても良いが、そもそもコードとしてイケてない感しか無い…
  compare_infos = ...
  while(中央値が同じ間) do
    中央値が同じものを処理対象として抽出
  end

  中央値のカウント数でソート

  while(取り出した情報数) do
    ...
  end
end
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 4

checkベストアンサー

+3

質問があります。

  • 審査員が偶数人数の場合、中央値はどうしますか? 
  • そして、それ以降の順位判定方法はどうしますか?

審査員は奇数人数ということなので、それに従って書いてみました。

a.rb

def calc_factores(score)
  fail "bad data size: score.size" if score.size.even?
  center_index = score.size / 2
  center_value = score[center_index]
  center_value_count = score.count(center_value)
  [
    center_value,
    center_value_count * (-1),
    score[center_index..-1].sum,
    score[0..center_index].sum * (-1)
  ]
end

scores = {
  'Ken':    [1, 1, 1, 4, 7],
  'Shinji':    [1, 2, 3, 3, 4],
  'Taro':    [2, 2, 3, 4, 4],
  'Kazuya':    [2, 3, 5, 5, 6],
  'Shota':    [3, 4, 5, 5, 7],
  'Fumiya':    [1, 6, 6, 6, 7],
  'Ryu':    [2, 5, 6, 7, 7]
}
factores = scores.map { |name, score| [name.to_sym, calc_factores(score)] }
result = factores.sort_by {|entory| entory[1] }
result.each {|entory| p entory[0].to_s }

実行結果

$ ruby a.rb
"Ken"
"Shinji"
"Taro"
"Kazuya"
"Shota"
"Fumiya"
"Ryu"

`

順位判定に使う数値を配列で得るようにします。
その数値の配列でソートして、結果を得ています。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/03/05 01:21

    katoyさん、コメントありがとうございます。

    前提条件として記載できておらず申し訳ありません。審査員は必ず奇数人数になります。

    Zuishinさんへのコメントにも書きましたが、現状の書きなぐっているコードを追記しておきます。コンソールで色々と動かしながらコードを書いていっているため、関数分割などはまだ全然考えられていませんがご了承ください。

    キャンセル

  • 2017/03/05 14:08

    katoyさん、コメントありがとうございます。

    sort_by に配列をそのまま渡すと、配列全体でソートできるのですね…

    順位付けの条件文が分かりにくかったようで申し訳ありません。頂いたコードをもとに、条件と異なる部分を以下のように変更しました。

    ・「中央値の個数 比較」の対象範囲は、「中央地より後(5人の審査員がいたら3~5人目の部分)が対象」なので、処理範囲を変更しました。
    前:center_value_count = score.count(center_value)
    後:center_value_count = score[center_index...score.size].count(center_value)

    ・「中央値より前の部分の総和」の処理範囲を変更しました。
    前:score[center_index..-1].sum
    後:score[0...center_index].inject(:+)

    ・「中央値より後の部分を『順次比較』」なので、総和を取るのではなく配列をそのまま保持するようにしました。 ※別関数にて比較する
    前:score[0..center_index].sum * (-1)
    後:score[center_index...score.size]

    上記のように変更後、Zuishinさんのコメントを参考に、比較用のメソッドを別に作成して、sortメソッドに渡すようにしました。最終的なコードは解決方法欄に記載します。

    sort_byメソッドで配列全体でのソートができること、またそれを正しく行うために負数に変換していることなど、色々と知らないことがあり大変勉強になりました。ありがとうございます。

    キャンセル

+3

そのまま組めばいいでしょう。
そういう課題ですから。
この複雑な計算を間違いなくできるかどうかが試されているだけです。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/03/05 01:18 編集

    Zuishinさん、コメントありがとうございます。

    rubyのリファレンスを見たり、色々と検索したりしながらコードを書いていってるのですが、どうも黒魔術感の拭えないコードになってきておりまして…このメソッドを使うと求める解をより簡潔に求めることができる、などのアドバイスが頂けないかと思い質問した次第でした。

    とは言え、現状どこまでコードが書けているのかなどの情報が全く書けていなかったため、その点を追記しておきたいと思います。
    # 出かけないといけない予定が入っていたため、バタバタと質問を書いただけになっていました。

    キャンセル

  • 2017/03/05 02:10

    二人の順位を比較する関数を作れば一発でソートできますよ。
    http://ref.xaio.jp/ruby/classes/enumerable/sort

    キャンセル

  • 2017/03/05 13:22

    コメント、ありがとうございます。

    コメント頂いてから色々と調べたり試したりしたのですが、sortメソッドに比較関数を渡してやれば順次処理してくれるのですね。恥ずかしながら <=> 演算子の意味や、sortメソッドにブロックを渡すということの正しい理解を出来ておりませんでした。

    最終的に katoy さんから頂いたコメントと組み合わせたコードになりました。コメントバック欄だとシンタックスハイライトが効かないようなので、後ほど解決方法欄に作ったコードを記載します。

    ヒントを頂き、ありがとうございました。

    キャンセル

+2

順位を付ける部分はこんな風にも書けそうです。

ranks = results.map do |entory|
  entory_factor = entory.take(4)
  rank = results.select{|x| compare(x.take(4), entory_factor) == -1 }.size
  [rank + 1, entory[-1].to_s]
end
ranks.each {|rank| puts rank.join(': ')}

"自分より factor が小さい人の人数 + 1" を順位とするのです。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/03/05 23:31

    katoyさん、コメントありがとうございます。

    なるほど、比較用のメソッドを select で再利用すればより簡潔に書けるのですね…!コメント頂いた内容の方がスッキリするので、この方法を使わせていただこうと思います。重ね重ね、ありがとうございます。

    まだまだ ruby 脳になりきれてないので、今後も精進したいと思います!

    キャンセル

+1

Zuishinさん、katoyさんのコメントを組合せて以下のようにしました。これからもう少しキレイにしたいと思います。

def calc_factores(score, name)
  fail "bad data size: score.size" if score.size.even?
  center_index = score.size / 2
  center_value = score[center_index]
  center_value_count = score[center_index...score.size].count(center_value)
  [
    center_value,
    center_value_count,
    score[0...center_index].inject(:+),
    score[center_index...score.size],
    name,
  ]
end

def compare(a, b)
  if a[0] < b[0]
    return -1
  elsif b[0] < a[0]
    return 1
  end

  if a[1] > b[1]
    return -1
  elsif b[1] > a[1]
   return 1
  end

  if a[2] < b[2]
    return -1
  elsif b[2] < a[2]
    return 1
  end

  for i in 0...a[3].size
    if a[3][i] < b[3][i]
      return -1
    elsif b[3][i] < a[3][i]
      return 1
    end
  end
  return 0
end

scores = {
  'Shinji':    [1, 2, 3, 3, 4],
  'Fumiya':    [1, 2, 3, 3, 4],
  'Ryu':    [2, 5, 6, 7, 7],
  'Shota':    [1, 2, 3, 3, 4],
  'Kazuya':    [2, 3, 5, 5, 6],
  'Ken':    [1, 1, 1, 4, 7],
  'Taro':    [2, 2, 3, 4, 4],
}
factores = scores.map { |name, score| calc_factores(score, name) }
results = factores.sort {|a, b| compare(a, b) }
results.each{|result| p result}
place, place_num = 1, 1
results.each_with_index do |result, i|
  if result.take(4) == results[i+1]&.take(4)
    p "#{result[4]} : #{place}"
    place_num += 1
  else
    p "#{result[4]} : #{place}"
    place += place_num
    place_num = 1
  end
end

実行結果

$ ruby test.rb
[1, 1, 2, [1, 4, 7], :Ken]
[3, 2, 3, [3, 3, 4], :Shinji]
[3, 2, 3, [3, 3, 4], :Fumiya]
[3, 1, 4, [3, 4, 4], :Taro]
[5, 2, 5, [5, 5, 6], :Kazuya]
[5, 2, 5, [5, 5, 7], :Shota]
[6, 1, 7, [6, 7, 7], :Ryu]
"Ken : 1"
"Shinji : 2"
"Fumiya : 2"
"Taro : 4"
"Kazuya : 5"
"Shota : 6"
"Ryu : 7"

お二人ともアドバイスありがとうございました。甲乙つけがたいのですが、katoyさんの方をベストアンサーとさせて頂きます。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

同じタグがついた質問を見る

  • Ruby

    9214questions

    Rubyはプログラミング言語のひとつで、オープンソース、オブジェクト指向のプログラミング開発に対応しています。