Ruby:ハッシュキーをフィルタリングする最も簡単な方法?


225

私は次のようなハッシュを持っています:

params = { :irrelevant => "A String",
           :choice1 => "Oh look, another one",
           :choice2 => "Even more strings",
           :choice3 => "But wait",
           :irrelevant2 => "The last string" }

そして、choice + int以外のすべてのキーを拒否する簡単な方法が欲しいのです。choice1、またはchoice1からchoice10までです。状況により異なります。

単語の選択とその後ろの1つまたは複数の数字だけでキーを特定するにはどうすればよいですか?

ボーナス:

ハッシュをタブ(\ t)を区切り文字とする文字列に変換します。私はこれを行いましたが、それは数行のコードを必要としました。通常、マスターのルビシア人はそれを1行ほどで行うことができます。


おまけの質問に関して、文字列をどのように見せたいかを明確にできますか。
mikej '15

確かに、上記の例でハッシュをもたらすであろう:「ああ見て、別の1 \ tEven複数の文字列\ TBUT待ち」(唯一のそれらの間の文字列の末尾ではない\トン、と)
デレク・

あなたの例の文字列はコメントで途切れています。編集リンクを使用して質問を編集し、そこに例を追加できます。達成したいことの例として、これまでに思いついたルビを投稿することもできます。
mikej 2011

回答:


281

元の回答に編集:これは回答(このコメントの時点)が選択された回答ですが、この回答の元のバージョンは古くなっています。

私がここで更新を追加して、他の人が私と同じようにこの回答に見舞われるのを回避できるようにします。

他の回答が言及しているように、Ruby> = 2.5 Hash#sliceは、以前はRailsでしか利用できなかったメソッドを追加しました。

例:

> { one: 1, two: 2, three: 3 }.slice(:one, :two)
=> {:one=>1, :two=>2}

編集の終わり。以下は、Railsを使用せずにRuby <2.5を使用している場合に役立つと思われる元の回答ですが、この時点ではそのようなケースはかなり一般的ではないと思います。


Rubyを使用している場合は、このselectメソッドを使用できます。正規表現の一致を行うには、キーをシンボルから文字列に変換する必要があります。これにより、選択肢が含まれた新しいハッシュが得られます。

choices = params.select { |key, value| key.to_s.match(/^choice\d+/) }

またはdelete_if、既存のハッシュを使用して変更できます。

params.delete_if { |key, value| !key.to_s.match(/choice\d+/) }

または、それが単にキーであり、必要な値ではない場合は、次のようにすることができます。

params.keys.select { |key| key.to_s.match(/^choice\d+/) }

これにより、キーの配列のみが提供されます。 [:choice1, :choice2, :choice3]


1
優れた!一度見たら、これはとても簡単です!誠にありがとうございました。また、時間を割いて回答してくださった他の方々にも感謝いたします。
Derek

2
最近のIIRCでは、シンボルを正規表現で解析できます。
Andrew Grimm、

@アンドリュー、私はあなたが正しいと思います。この質問の答えをテストしていたとき、私は1.8.7にいました。
mikej '15

1
の対応.select.rejectです。これにより、コードがより慣用的になります。
Joe Atzberger、2016

この#slice方法は、activesupportがなくても2.5.3で動作します。
グレイウルフ2018年

305

Rubyでは、Hash#selectが正しいオプションです。Railsを使用している場合は、Hash#sliceおよびHash#slice!を使用できます。例(レール3.2.13)

h1 = {:a => 1, :b => 2, :c => 3, :d => 4}

h1.slice(:a, :b)         # return {:a=>1, :b=>2}, but h1 is not changed

h2 = h1.slice!(:a, :b)   # h1 = {:a=>1, :b=>2}, h2 = {:c => 3, :d => 4}

12
{B => 2 '' => 1}のみが返され、{B => 2} h1.sliceと(A:B:)文字列化keys..h1 =に注意
davidcollom

素晴らしいソリューション。ハッシュ内のハッシュの値を解析したいrspecで結果を返す際にこれを使用しました。`` `test = {:a => 1、:b => 2、c => {:A => 1、:B => 2}} solution ==> test.c.slice(:B)` ` `
PriyankaK 2014

RailsをActiveSupportに置き換えれば、これは完璧です;)
Geoffroy

5
ただし、「choice + int」であるキーの値を抽出する方法が必要であるため、これは質問の答えにはなりません。ソリューションでは、事前にわかっているキーのみを抽出できます。
metakungfu 16

46

最も簡単な方法は、gem 'activesupport'(またはgem 'active_support')を含めることです。

次に、あなたのクラスではあなただけする必要があります

require 'active_support/core_ext/hash/slice'

そして電話する

params.slice(:choice1, :choice2, :choice3) # => {:choice1=>"Oh look, another one", :choice2=>"Even more strings", :choice3=>"But wait"}

バグがあるかもしれない他の関数を宣言することは価値がないと私は思います、そしてここ数年の間に調整されたメソッドを使用するほうが良いと思います。


少なくとも2.5では、Activerecordは必要ありません。
グレイウルフ2018年

13

最も簡単な方法は、gem 'activesupport'(またはgem 'active_support')を含めることです。

params.slice(:choice1, :choice2, :choice3)


4
回答に詳細を追加してください。
Mohit Jain

13

レールを使用していて、別のリストにキーがある場合は、次の*表記を使用できます。

keys = [:foo, :bar]
hash1 = {foo: 1, bar:2, baz: 3}
hash2 = hash1.slice(*keys)
=> {foo: 1, bar:2}

他の回答が述べslice!たように、ハッシュを変更して(そして消去されたキー/値を返す)にも使用できます。


9

これは、完全な元の質問を解決するための1行です。

params.select { |k,_| k[/choice/]}.values.join('\t')

しかし、上記のほとんどの解決策は、事前にキーを知る必要がある場合、sliceまたは単純な正規表現を使用して解決することです。

これは、実行時にスワップ可能な、シンプルでより複雑なユースケースで機能する別のアプローチです

data = {}
matcher = ->(key,value) { COMPLEX LOGIC HERE }
data.select(&matcher)

これにより、キーまたは値の照合に関するより複雑なロジックが可能になるだけでなく、テストも簡単になり、実行時に照合ロジックを交換できます。

元の問題を解決する例:

def some_method(hash, matcher) 
  hash.select(&matcher).values.join('\t')
end

params = { :irrelevant => "A String",
           :choice1 => "Oh look, another one",
           :choice2 => "Even more strings",
           :choice3 => "But wait",
           :irrelevant2 => "The last string" }

some_method(params, ->(k,_) { k[/choice/]}) # => "Oh look, another one\\tEven more strings\\tBut wait"
some_method(params, ->(_,v) { v[/string/]}) # => "Even more strings\\tThe last string"

1
私はマッチャーが本当に好きです
Calin


6

残りのハッシュが必要な場合:

params.delete_if {|k, v| ! k.match(/choice[0-9]+/)}

または、キーだけが必要な場合:

params.keys.delete_if {|k| ! k.match(/choice[0-9]+/)}

selectXと無関係なXの数に応じて、select OR delete_ifの方が良い場合があります。choiceXが多い場合は、delete_ifの方が適しています。
ayckoster

6

これを初期化子に入れます

class Hash
  def filter(*args)
    return nil if args.try(:empty?)
    if args.size == 1
      args[0] = args[0].to_s if args[0].is_a?(Symbol)
      self.select {|key| key.to_s.match(args.first) }
    else
      self.select {|key| args.include?(key)}
    end
  end
end

その後、あなたは行うことができます

{a: "1", b: "b", c: "c", d: "d"}.filter(:a, :b) # =>  {a: "1", b: "b"}

または

{a: "1", b: "b", c: "c", d: "d"}.filter(/^a/)  # =>  {a: "1"}


4

ボーナスの質問については:

  1. #selectこのようなメソッドからの出力がある場合(2要素配列のリスト):

    [[:choice1, "Oh look, another one"], [:choice2, "Even more strings"], [:choice3, "But wait"]]

    次に、この結果を取得して実行します。

    filtered_params.join("\t")
    # or if you want only values instead of pairs key-value
    filtered_params.map(&:last).join("\t")
  2. 次の#delete_ifようなメソッドからの出力がある場合(ハッシュ):

    {:choice1=>"Oh look, another one", :choice2=>"Even more strings", :choice3=>"But wait"}

    次に:

    filtered_params.to_a.join("\t")
    # or
    filtered_params.values.join("\t")

素晴らしいソリューション。1つの質問:filtered_pa​​rams.map(&:last).join( "\ t")はfiltered_pa​​rams.map(| i | i.last).join( "\ t")の略ですか?もしそうなら、「最後」は何ですか?&:valueを使用してハッシュの値を取得できますか?
Derek

map引数としてブロックを取ります。あなたが、その場でブロックを作成することができます&:methodブロックを作成します建設、{|v| v.method }。私の場合&:last、配列(2要素配列)引数で呼び出されました。それを試してみたい場合は、最初にブロックに受け取っている引数を確認し(1つの引数を取得し、それを複数の引数に分解しないでください!)、1つのメソッドを見つけようとします(メソッドをでチェーンすることはできません&:method)。欲しいです。可能であれば、ショートカットを使用できます。そうでなければ、フルブロックが必要です
MBO

2
params = { :irrelevant => "A String",
           :choice1 => "Oh look, another one",
           :choice2 => "Even more strings",
           :choice3 => "But wait",
           :irrelevant2 => "The last string" }

choices = params.select { |key, value| key.to_s[/^choice\d+/] }
#=> {:choice1=>"Oh look, another one", :choice2=>"Even more strings", :choice3=>"But wait"}

0

私の場合も同様の問題がありました。私の場合、解決策は、キーがシンボルでなくても機能する1つのライナーでしたが、配列に条件キーが必要です。

criteria_array = [:choice1, :choice2]

params.select { |k,v| criteria_array.include?(k) } #=> { :choice1 => "Oh look another one",
                                                         :choice2 => "Even more strings" }

もう一つの例

criteria_array = [1, 2, 3]

params = { 1 => "A String",
           17 => "Oh look, another one",
           25 => "Even more strings",
           49 => "But wait",
           105 => "The last string" }

params.select { |k,v| criteria_array.include?(k) } #=> { 1 => "A String"}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.