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

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

新規登録して質問してみよう
ただいま回答率
85.48%
Ruby

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

Q&A

解決済

4回答

2283閲覧

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

17number

総合スコア14

Ruby

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

0グッド

0クリップ

投稿2017/03/04 09:27

編集2017/03/04 16:44

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

####順位付け方法

  • 複数の選手を複数の審査員が順位付けして、全ての情報を総合して順位を計算する
  • 審査員は必ず奇数人数
審査員A審査員B審査員C審査員D審査員E
Ken47111
Shinji13423
Taro32244
Kazuya55632
Shota74355
Fumiya61766
Ryu26577

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

  • これをまずはソート
---中央値--
Ken11147
Shinji12334
Taro22344
Kazuya23556
Shota34557
Fumiya16667
Ryu25677
  • 最初の判定基準:中央値の小さい方が上位。この時点で 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 の方が上位
  • なお、中央値、中央値の保持数、中央値より手間の総和比較も同値だった場合は、中央値より後の数値を順次比較していき、数値の小さい方が上位
  • 全ての基準に照らし合わせて、全て同じ値となった場合は同率順位

####コードの前提条件
事前に取得できている情報は以下のようなハッシュ形式。

ruby

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

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

ruby

1# 中央値を取得するためのインデックス 2median_cnt = judge_num / 2 # 審査員の数(judge_num)は設定ファイルなどから取得済と思ってください 3 4judging_infos = [] 5rank_hash.each do |name, ranks| 6 # 中央値 7 median = ranks[median_cnt] 8 9 # 中央値以降の順位 10 ranks_latter_half = ranks.drop(median_cnt) 11 12 # 中央値が後半にいくつあるかをカウント 13 majority_cnt = ranks_latter_half.group_by(&:itself)[median].count 14 15 # 中央値より前の順位 16 ranks_first_half = ranks.take(median_cnt) 17 18 # 中央値より前の合計値 19 upper_sum = ranks_first_half.sum 20 21 # 算出した情報をまとめて配列に保持 22 judging_infos << [name, median, majority_cnt, upper_sum] 23end 24 25# 算出した情報を中央値でソート 26judging_infos.sort!{|a, b| a[1] <=> b[1]} 27 28# これ以降、上記までに取得した情報を使って処理すれば良いものと思いロジックを検討中 29 30# 先頭の要素を取得 31place, name_places = 1, {} 32judging_info = judging_infos.shift 33while(judging_infos.present?) do 34 median = judging_info[1] 35 36 # 中央値が異なるので順位確定 37 unless median == judging_infos[0][1] 38 name_places.store(judging_info[0], place) 39 place += 1 40 next 41 end 42 43 # 中央値が同じものを処理する 44 # このあたりの処理が while の多重ループになりそうで頭を悩ませ中 45 # 一通り組み終わった後でリファクタしても良いが、そもそもコードとしてイケてない感しか無い… 46 compare_infos = ... 47 while(中央値が同じ間) do 48 中央値が同じものを処理対象として抽出 49 end 50 51 中央値のカウント数でソート 52 53 while(取り出した情報数) do 54 ... 55 end 56end

気になる質問をクリップする

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答4

0

ベストアンサー

質問があります。

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

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

a.rb

ruby

1def calc_factores(score) 2 fail "bad data size: score.size" if score.size.even? 3 center_index = score.size / 2 4 center_value = score[center_index] 5 center_value_count = score.count(center_value) 6 [ 7 center_value, 8 center_value_count * (-1), 9 score[center_index..-1].sum, 10 score[0..center_index].sum * (-1) 11 ] 12end 13 14scores = { 15 'Ken': [1, 1, 1, 4, 7], 16 'Shinji': [1, 2, 3, 3, 4], 17 'Taro': [2, 2, 3, 4, 4], 18 'Kazuya': [2, 3, 5, 5, 6], 19 'Shota': [3, 4, 5, 5, 7], 20 'Fumiya': [1, 6, 6, 6, 7], 21 'Ryu': [2, 5, 6, 7, 7] 22} 23factores = scores.map { |name, score| [name.to_sym, calc_factores(score)] } 24result = factores.sort_by {|entory| entory[1] } 25result.each {|entory| p entory[0].to_s }

実行結果

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

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

投稿2017/03/04 12:24

編集2017/03/05 01:34
katoy

総合スコア22324

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

17number

2017/03/04 16:21

katoyさん、コメントありがとうございます。 前提条件として記載できておらず申し訳ありません。審査員は必ず奇数人数になります。 Zuishinさんへのコメントにも書きましたが、現状の書きなぐっているコードを追記しておきます。コンソールで色々と動かしながらコードを書いていっているため、関数分割などはまだ全然考えられていませんがご了承ください。
17number

2017/03/05 05: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メソッドで配列全体でのソートができること、またそれを正しく行うために負数に変換していることなど、色々と知らないことがあり大変勉強になりました。ありがとうございます。
guest

0

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

投稿2017/03/04 09:36

Zuishin

総合スコア28660

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

17number

2017/03/05 04:13 編集

Zuishinさん、コメントありがとうございます。 rubyのリファレンスを見たり、色々と検索したりしながらコードを書いていってるのですが、どうも黒魔術感の拭えないコードになってきておりまして…このメソッドを使うと求める解をより簡潔に求めることができる、などのアドバイスが頂けないかと思い質問した次第でした。 とは言え、現状どこまでコードが書けているのかなどの情報が全く書けていなかったため、その点を追記しておきたいと思います。 # 出かけないといけない予定が入っていたため、バタバタと質問を書いただけになっていました。
17number

2017/03/05 04:22

コメント、ありがとうございます。 コメント頂いてから色々と調べたり試したりしたのですが、sortメソッドに比較関数を渡してやれば順次処理してくれるのですね。恥ずかしながら <=> 演算子の意味や、sortメソッドにブロックを渡すということの正しい理解を出来ておりませんでした。 最終的に katoy さんから頂いたコメントと組み合わせたコードになりました。コメントバック欄だとシンタックスハイライトが効かないようなので、後ほど解決方法欄に作ったコードを記載します。 ヒントを頂き、ありがとうございました。
guest

0

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

ruby

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

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

投稿2017/03/05 08:39

編集2017/03/05 09:09
katoy

総合スコア22324

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

17number

2017/03/05 14:31

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

0

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

ruby

1def calc_factores(score, name) 2 fail "bad data size: score.size" if score.size.even? 3 center_index = score.size / 2 4 center_value = score[center_index] 5 center_value_count = score[center_index...score.size].count(center_value) 6 [ 7 center_value, 8 center_value_count, 9 score[0...center_index].inject(:+), 10 score[center_index...score.size], 11 name, 12 ] 13end 14 15def compare(a, b) 16 if a[0] < b[0] 17 return -1 18 elsif b[0] < a[0] 19 return 1 20 end 21 22 if a[1] > b[1] 23 return -1 24 elsif b[1] > a[1] 25 return 1 26 end 27 28 if a[2] < b[2] 29 return -1 30 elsif b[2] < a[2] 31 return 1 32 end 33 34 for i in 0...a[3].size 35 if a[3][i] < b[3][i] 36 return -1 37 elsif b[3][i] < a[3][i] 38 return 1 39 end 40 end 41 return 0 42end 43 44scores = { 45 'Shinji': [1, 2, 3, 3, 4], 46 'Fumiya': [1, 2, 3, 3, 4], 47 'Ryu': [2, 5, 6, 7, 7], 48 'Shota': [1, 2, 3, 3, 4], 49 'Kazuya': [2, 3, 5, 5, 6], 50 'Ken': [1, 1, 1, 4, 7], 51 'Taro': [2, 2, 3, 4, 4], 52} 53factores = scores.map { |name, score| calc_factores(score, name) } 54results = factores.sort {|a, b| compare(a, b) } 55results.each{|result| p result} 56place, place_num = 1, 1 57results.each_with_index do |result, i| 58 if result.take(4) == results[i+1]&.take(4) 59 p "#{result[4]} : #{place}" 60 place_num += 1 61 else 62 p "#{result[4]} : #{place}" 63 place += place_num 64 place_num = 1 65 end 66end

実行結果

$ 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さんの方をベストアンサーとさせて頂きます。

投稿2017/03/05 05:40

17number

総合スコア14

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問