解決済み

ハッシュのハッシュのソート

  • 困ってます
  • 質問No.7067894
  • 閲覧数565
  • ありがとう数7
  • 気になる数0
  • 回答数6
  • コメント数0

お礼率 100% (11/11)

rubyでハッシュのソート方法についてはいくつか情報のサイトを見つけられました。
ですが今やりたいのは、ハッシュのハッシュのソートなのですが、うまいやり方がわかりませんでした。
具体的には、
h1 = {"user1"=>{"a"=>10, "b"=>20, "c"=>30"},
"user2"=>{"d"=>5, "e"=>8},
"user3"=>{"f"=>10, "g"=>5, "h"=>10} }

というようなハッシュのハッシュを想定しています。ユーザごとに案件ごとの必要工数(時間)をハッシュとして持たせ、全工数が多いユーザ順にソートしたいのです。
上記の場合だと、
{"user1"=>{"a"=>10, "b"=>20, "c"=>30"},
"user3"=>{"f"=>10, "g"=>5, "h"=>10},
"user2"=>{"d"=>5, "e"=>8} }
というようにソートしたいのですが、何かやり方がありましたらご教授いただけますでしょうか。

質問者が選んだベストアンサー

  • 回答No.5

ベストアンサー率 81% (89/109)

追記された状況を見る限り、JobやUserといった構造体クラスやテーブルクラスを書きわけておくことで取り回しが楽になりそうな気もしますが・・・今回の本題からははずれちゃいますね。

本題の中で残っている疑問点は、多分 Enumerable#inject と Hash.[] ですよね

Enumerable#inject は有名なメソッドなので、少し検索すればいっぱい解説が出てくると思います。
自分はとりあえず公式リファレンスのURLだけ貼っておきます。
http://doc.ruby-lang.org/ja/1.8.7/class/Enumerable.html
http://doc.ruby-lang.org/ja/1.9.2/class/Enumerable.html
inject(sym) -> object
inject(init, sym) -> object
の部分ですね。
ぱっと見わかりづらいメソッドだとは思うので、いくつか書いてみて動作確認することをお勧めします。

RubyのHashは、1.9系から順番の保持を保証するようになりました。(OrderedHash)
Hash.[] を使うことでソートが崩れて、なおかつinjectのシンボル記法が使えるということであれば、多分Ruby1.8.7をお使いですよね?

もしRuby1.9系で#eachの順番だけ維持されればいい(indexアクセス等不要)なら、最終形がHashでも動きます。
Ruby1.8.7から乗り換えるわけにはいかないということであれば、多重配列で我慢するかテーブルクラスを書くことになるのかなと思います。
お礼コメント
lakings

お礼率 100% (11/11)

さらなる回答ありがとうございました。
いただいたURLでsymの意味もわかりました。「:-」は降順ということではなく、値を0から引いていって、マイナスの数字なので結果的に絶対値の大きいものからソートされるということですね。
また、ご指摘の通りRubyは1.8.7でした。やはり環境に違いがあったということでそれについても理解できました。
投稿日時 - 2011-10-15 21:24:56

その他の回答 (全5件)

  • 回答No.6

ベストアンサー率 18% (216/1139)

ANo5 氏が言われているように、User をあらわすクラスなどを定義するべきです。
自分のやりたいことをできる限りストレートにコード化することが重要です。

User クラスは name メソッドと task メソッド、total_cost メソッドくらいがあればよいでしょう。
外部システムから得られたユーザ情報を User インスタンスの配列として得れば、あとは total_cost に応じて、ソートするのは簡単です。
表示するときのために to_s メソッドを用意するのもよいでしょう。
お礼コメント
lakings

お礼率 100% (11/11)

質問の背景にあった問題について考えていただき大変勉強になりました。
今後の設計の際によく考えるようにしたいと思います。
回答ありがとうございました。
投稿日時 - 2011-10-15 21:29:23
  • 回答No.4

ベストアンサー率 58% (42/72)

RubyのHashは順番を保存するので勘違いする人もいますけど、
データ構造としての連想配列ではキー列挙の順序は追加の順序と一般には異なります。
まあ、それはそれとして、#2さんのを少し改変して、

 Hash[h1.sort_by{|k, v| -v.values.inject(:+)}]

変更点:
 o Hash.[]を使って最終的にHashを生成
 o 符号反転してソートすることで降順ソートを少し短く、意図を明確に
お礼コメント
lakings

お礼率 100% (11/11)

回答ありがとうございます。
こちらについても「(:+)」の部分の使い方がまだよくわかっておらず、もう少し探してみます。
あと、私の環境のせいかもしれませんが、Hash[]でくくる前はソートされているようなのですが、Hash[]でくくるとソート前の状態に戻ってしまうようでした。もう少し調べてみます。
投稿日時 - 2011-10-15 01:12:09
  • 回答No.3

ベストアンサー率 18% (216/1139)

そもそも Hash について、そのキーの登場順は考えるべきではないと思います。
ソートした結果は配列として得るようにインターフェースを考え直したほうがよいかと。

つまり、利用者が

{"user1"=>{"a"=>10, "b"=>20, "c"=>30"},
"user3"=>{"f"=>10, "g"=>5, "h"=>10},
"user2"=>{"d"=>5, "e"=>8} }



{"user1"=>{"a"=>10, "b"=>20, "c"=>30"},
"user2"=>{"d"=>5, "e"=>8},
"user3"=>{"f"=>10, "g"=>5, "h"=>10} }

を区別する必要が生ずるのは避けるべきだと思います。
お礼コメント
lakings

お礼率 100% (11/11)

ご指摘の通りです。自分でももっとよい設計があるのではないか?と思っています。
背景を補足します。
あるシステム内に、ユーザ名、案件、必要工数を含むテーブルが複数あり、それらは何かでソートされているわけではないのですが、一つずつすべてのテーブルを取り出すことができるようになっています。ここまでは既存のシステムなので変更できません。
このシステムからRubyで全テーブルを取り出しながら、最終的には、ユーザごとに抱えている案件の合計時間が多い順に表示させたい、というのがやりたいことです。
その前段として、テーブルを取り出し、ユーザ名ごとにハッシュを作っていこうと思ったのが例としてあげたハッシュになります。その後ソートしようとしたところで行き詰まり、質問させていただきました。
投稿日時 - 2011-10-15 01:08:25
  • 回答No.2

ベストアンサー率 81% (89/109)

ありゃ・・・すみません、injectの使い方間違ってました。
さっきのだと簡単にバグが出ますね。

修正版等
http://ideone.com/MhIAT
http://ideone.com/RrRJ6
お礼コメント
lakings

お礼率 100% (11/11)

回答ありがとうございます。
実際に試してみて、期待通りの結果になることを確認できました。
injectというのは使ったことがなかったので勉強になりました。
「:-」の部分がちょっとわかっておらず、降順を示すのだろうことは想像できるのですが、使い方の説明が見つけられていないのでもう少し探してみます。
投稿日時 - 2011-10-15 00:57:55
  • 回答No.1

ベストアンサー率 81% (89/109)

"c"=>30"
のところ、30の後のダブルクォーテーションは誤記ですよね?

取り敢えず、そのまま書くとこんな感じでしょうか
http://ideone.com/MjaPO
お礼コメント
lakings

お礼率 100% (11/11)

申し訳ありません。30の後のダブルクォーテーションは誤記でした。
早々の回答ありがとうございました。
投稿日時 - 2011-10-15 00:53:25
結果を報告する
このQ&Aにはまだコメントがありません。
あなたの思ったこと、知っていることをここにコメントしてみましょう。
関連するQ&A
AIエージェント「あい」

こんにちは。AIエージェントの「あい」です。
あなたの悩みに、OKWAVE 3,500万件のQ&Aを分析して最適な回答をご提案します。

その他の関連するQ&A、テーマをキーワードで探す

キーワードでQ&A、テーマを検索する

ピックアップ

ページ先頭へ