Rubyでハッシュのすべての値を変更する


170

ハッシュのすべての値を変更して、値の前後に '%'を追加したいので、

{ :a=>'a' , :b=>'b' }

に変更する必要があります

{ :a=>'%a%' , :b=>'%b%' }

これを行う最良の方法は何ですか?


1
元の文字列オブジェクトを変更するか、元の文字列オブジェクトを変更するか、何も変更しないかを明確にしてください。
Phrogz

1
その後、あなたは間違った答えを受け入れました。(私が個人的にオブジェクトを変更する代わりに関数スタイルのプログラミングを提唱しているので、@ pstには意図的な違反はありません。)
Phrogz

しかし、それでもアプローチは素晴らしいもの
でした

回答:


178

実際の文字列自体を適切に変更したい場合(おそらく、同じ文字列オブジェクトへの他の参照に影響を与える可能性があります):

# Two ways to achieve the same result (any Ruby version)
my_hash.each{ |_,str| str.gsub! /^|$/, '%' }
my_hash.each{ |_,str| str.replace "%#{str}%" }

ハッシュをその場で変更したいが、文字列に影響を与えたくない場合(新しい文字列を取得する場合):

# Two ways to achieve the same result (any Ruby version)
my_hash.each{ |key,str| my_hash[key] = "%#{str}%" }
my_hash.inject(my_hash){ |h,(k,str)| h[k]="%#{str}%"; h }

新しいハッシュが必要な場合:

# Ruby 1.8.6+
new_hash = Hash[*my_hash.map{|k,str| [k,"%#{str}%"] }.flatten]

# Ruby 1.8.7+
new_hash = Hash[my_hash.map{|k,str| [k,"%#{str}%"] } ]

1
@Andrew Marshallありがとうございます。Ruby 1.8では、Hash.[]配列ペアの配列を受け入れません。これには、偶数の直接引数が必要です(したがって、最初にスプラット)。
Phrogz

2
実際、Hash。[key_value_pairs]は1.8.7で導入されたので、Ruby 1.8.6だけがスプラットとフラット化を必要としません。
マルク=アンドレ・Lafortune

2
@Aupajo Hash#eachはキーと値の両方をブロックに渡します。この場合、私はキーを気にしなかったので、便利な名前を付けませんでした。変数名はアンダースコアで始まる場合があり、実際にはアンダースコアだけの場合もあります。これを行うことによるパフォーマンス上の利点はありません。これは、最初のブロック値では何もしないという、自己文書化された微妙なメモです。
Phrogz

1
私はあなたが意味を考えるmy_hash.inject(my_hash){ |h,(k,str)| h[k]="%#{str}%"; h }と、ブロックからハッシュを返す必要が
aceofspades

1
あるいは、使用されていないキー値にアンダースコアを使用するよりも少し理解しやすいeach_valueメソッドを使用することもできます。
Strand McCutchen 2013年

266

Ruby 2.1以降でできること

{ a: 'a', b: 'b' }.map { |k, str| [k, "%#{str}%"] }.to_h

5
ありがとう、私が実際に探していたものでした。なぜそんなに賛成されないのかわからない。
simperreault 2015年

7
ただし、これは非常に遅く、非常にRAMを大量に消費します。入力ハッシュは繰り返し処理されてネストされた配列の中間セットが生成され、それが新しいハッシュに変換されます。RAMのピーク使用量を無視すると、実行時間ははるかに悪くなります。別の回答でこれとインプレース変更ソリューションをベンチマークすると、同じ反復回数で2.5秒と1.5秒を示しています。Rubyは比較的遅い言語であるため、遅い言語の遅い部分を避けることは非常に理にかなっています:-)
Andrew Hodgkinson

2
@AndrewHodgkinsonは、一般的に同意し、実行時のパフォーマンスに注意を払わないことを主張していませんが、これらすべてのパフォーマンスの落とし穴を追跡し続けるのは面倒になり、ルビーの「開発者の生産性を第一に」という哲学に反しませんか?これはあなたへのコメントではなく、ルビを使用してこれがもたらす最終的なパラドックスに関する一般的なコメントのほうが多いと思います。
elsurudo

4
問題は次のとおりです。まあ、ルビーを使用するという決断ではすでにパフォーマンスをあきらめているので、「これ以外の少し」はどのような違いがあるのでしょうか。滑りやすい坂道ですね。記録として、可読性の観点から、私はこの解決策を受け入れられた回答よりも優先します。
elsurudo

1
Ruby 2.4以降を使用する場合#transform_values!、sschmeck(stackoverflow.com/a/41508214/6451879)で指摘されているように、さらに使いやすくなっています。
フィンランド

128

Ruby 2.4でHash#transform_values!使用できるメソッドが導入されました。

{ :a=>'a' , :b=>'b' }.transform_values! { |v| "%#{v}%" }
# => {:a=>"%a%", :b=>"%b%"} 

まさに私が探していたもの!
Dolev、

4
もちろんHash#transform_values、レシーバーを変更しない(強打なしで)もあります。それ以外の場合は素晴らしい回答、ありがとう!
iGEL

1
これにより、私の使用が本当に減りますreduce:-p
iGEL

86

ハッシュの値を変更する最良の方法は、

hash.update(hash){ |_,v| "%#{v}%" }

少ないコードと明確な意図。また、変更する必要がある値を超えて新しいオブジェクトが割り当てられないため、より高速です。


厳密には当てはまりません。新しい文字列が割り当てられます。それでも、効果的な興味深い解決策はあります。+1
Phrogz

@Phrogz良い点。答えを更新しました。すべての値の変換がなどのミューテーターとして表現できるわけではないため、値の割り当ては一般に避けられませんgsub!
Sim

3
私の回答と同じですが、別の同義語を使用するとupdate、意図がを上回っていることに同意しmerge!ます。これが最良の答えだと思います。

1
を使用しない場合はk_代わりに使用してください。
sekrett

28

もう少し可読一mapその単一要素のハッシュの配列へとreduceとそのmerge

the_hash.map{ |key,value| {key => "%#{value}%"} }.reduce(:merge)

2
より明確に使用Hash[the_hash.map { |key,value| [key, "%#{value}%"] }]
貧弱

これは、値を更新するための非常に非効率的な方法です。すべての値のペアに対して、最初にArray(のmap)ペアを作成し、次にを作成しますHash。次に、reduce操作の各ステップで「メモ」を複製し、Hashそれに新しいキーと値のペアを追加します。少なくとも:merge!in reduceを使用してファイナルを適切に修正しHashます。そして最終的には、既存のオブジェクトの値を変更するのではなく、新しいオブジェクトを作成します。これは質問の答えではありません。
Sim

空のnil場合に返されるthe_hash
DNNX 2015


16

オリジナルに副作用をもたらさない1つの方法:

h = {:a => 'a', :b => 'b'}
h2 = Hash[h.map {|k,v| [k, '%' + v + '%']}]

がハッシュをHash.map返さない理由(結果として[key,value]ペアの配列が新しいハッシュに変換される理由)を説明し、同じ一般的なパターンへの代替アプローチを提供するため、Hash#mapも興味深い読み物になる場合があります。

ハッピーコーディング。

[免責事項:Hash.mapRuby 2.xでセマンティクスが変更されたかどうかはわかりません]


7
Hash.mapRuby 2.xでセマンティクスが変更されたかどうかをマッツは知っていますか?
Andrew Grimm

1
Hash#[]メソッドはとても便利ですが、醜いです。同じ方法で配列をハッシュに変換するよりきれいな方法はありますか?
Martijn 2013

15
my_hash.each do |key, value|
  my_hash[key] = "%#{value}%"
end

私は副作用は好きではありませんが、アプローチの+1です:) each_with_objectRuby 1.9(IIRC)には、名前に直接アクセスする必要がなく、機能するMap#merge場合もあります。複雑な詳細がどのように異なるのかわかりません。

1
最初のハッシュが変更されます。これは、動作が予期される場合は問題ありませんが、「忘れられる」と微妙な問題を引き起こす可能性があります。オブジェクトの可変性を減らすことを好みますが、常に実用的であるとは限りません。(Rubyはほとんど「副作用のない」言語です;-)

8

Hash.merge!最もクリーンなソリューションです

o = { a: 'a', b: 'b' }
o.merge!(o) { |key, value| "%#{ value }%" }

puts o.inspect
> { :a => "%a%", :b => "%b%" }

@MichieldeMareすみません、これを十分にテストしていませんでした。あなたは正しいです、ブロックは2つのパラメータを取る必要があります。修正。

5

このようにRSpecでテストした後:

describe Hash do
  describe :map_values do
    it 'should map the values' do
      expect({:a => 2, :b => 3}.map_values { |x| x ** 2 }).to eq({:a => 4, :b => 9})
    end
  end
end

次のようにHash#map_valuesを実装できます。

class Hash
  def map_values
    Hash[map { |k, v| [k, yield(v)] }]
  end
end

この関数は次のように使用できます。

{:a=>'a' , :b=>'b'}.map_values { |v| "%#{v}%" }
# {:a=>"%a%", :b=>"%b%"}

1

ここでどのインプレースバリアントが最速か知りたければ、次のようになります。

Calculating -------------------------------------
inplace transform_values! 1.265k  0.7%) i/s -      6.426k in   5.080305s
      inplace update      1.300k  2.7%) i/s -      6.579k in   5.065925s
  inplace map reduce    281.367   1.1%) i/s -      1.431k in   5.086477s
      inplace merge!      1.305k  0.4%) i/s -      6.630k in   5.080751s
        inplace each      1.073k  0.7%) i/s -      5.457k in   5.084044s
      inplace inject    697.178   0.9%) i/s -      3.519k in   5.047857s
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.