ハッシュ値を変更するには?


126

valueハッシュのそれぞれをに置き換えたいのvalue.some_methodですが。

たとえば、単純なハッシュが与えられた場合:

{"a" => "b", "c" => "d"}` 

すべての値は.upcasedである必要があるため、次のようになります。

{"a" => "B", "c" => "D"}

私が試した#collectし、#mapいつも戻ったばかりの配列を取得します。これを行うエレガントな方法はありますか?

更新

くそー、私は忘れました:ハッシュは変更されるべきではないインスタンス変数にあります。値が変更された新しいハッシュが必要ですが、その変数を明示的に定義せずに、ハッシュをループしてそれを満たします。何かのようなもの:

new_hash = hash.magic{ ... }

私の回答の2番目の例は、新しいハッシュを返します。#injectが行っている魔法をさらに詳しく説明してほしい場合は、コメントしてください。
kch

回答:


218
my_hash.each { |k, v| my_hash[k] = v.upcase } 

または、それを非破壊的に行い、変更する代わりに新しいハッシュを返すことを希望する場合my_hash

a_new_hash = my_hash.inject({}) { |h, (k, v)| h[k] = v.upcase; h } 

この最後のバージョンには、キーを変換できるという追加の利点があります。


26
最初の提案は適切ではありません。列挙中に列挙可能な要素を変更すると、未定義の動作が発生します。これは他の言語でも有効です。これはMatzからの返信です(彼は配列を使用した例に返信しますが、答えは一般的です):j.mp/tPOh2U
Marcus

4
@マーカス私は一般にそのような変更は避けられるべきであることに同意する。ただし、この場合、変更によって将来の反復が変更されることはないため、おそらく安全です。別のキー(ブロックに渡されたキーではない)が割り当てられた場合、問題が発生します。
Kelvin

36
@Adam @kchはeach_with_object、より便利なバージョンであるinjectあなたは、ハッシュとブロックを終了したくない場合:my_hash.each_with_object({}) {|(k, v), h| h[k] = v.upcase }
ケルビン

6
ペアのリストをハッシュに変換するための非常にきちんとした構文もあるので、次のように書くことができますa_new_hash = Hash[ my_hash.map { |k, v| [k, v.upcase] } ]
書き換え

2
Ruby 2では次のように書くことができます.to_h
roman-roman '12


35

値を収集し、それを配列からハッシュに再度変換できます。

このような:

config = Hash[ config.collect {|k,v| [k, v.upcase] } ]

23

これはそれを行います:

my_hash.each_with_object({}) { |(key, value), hash| hash[key] = value.upcase }

inject利点とは対照的に、ブロック内で再度ハッシュを返す必要はありません。


このメソッドは元のハッシュを変更しないのが好きです。
クリスチャンディロレンソ

10

この機能を試してください:

h = {"a" => "b", "c" => "d"}
h.each{|i,j| j.upcase!} # now contains {"a" => "B", "c" => "D"}.

5
それは良いことですが、jがそれ自体に作用する破壊的なメソッドを持っている場合にのみ機能することに注意してください。そのため、一般的なvalue.some_methodケースを処理できません。
kch、2009年

良い点、そして彼の更新により、2番目のソリューション(非破壊的)が最適です。
Chris Doggett、

10

ActiveSupport v4.2.0にはそのための方法があります。それは呼び出されtransform_values、基本的には各キーと値のペアに対してブロックを実行します。

彼らはそれを使ってやっているので、eachループするより良い方法はないと思います。

hash = {sample: 'gach'}

result = {}
hash.each do |key, value|
  result[key] = do_stuff(value)
end

更新:

Ruby 2.4.0以降では#transform_values、およびをネイティブに使用できます#transform_values!


2

さらに進んで、ネストされたハッシュでこれを行うことができます。確かに、これはRailsプロジェクトではかなり発生します。

以下は、paramsハッシュがUTF-8であることを確認するためのコードです。

  def convert_hash hash
    hash.inject({}) do |h,(k,v)|
      if v.kind_of? String
        h[k] = to_utf8(v) 
      else
        h[k] = convert_hash(v)
      end
      h
    end      
  end    

  # Iconv UTF-8 helper
  # Converts strings into valid UTF-8
  #
  # @param [String] untrusted_string the string to convert to UTF-8
  # @return [String] your string in UTF-8
  def to_utf8 untrusted_string=""
    ic = Iconv.new('UTF-8//IGNORE', 'UTF-8')
    ic.iconv(untrusted_string + ' ')[0..-2]
  end  

1

私はこのようなことをします:

new_hash = Hash [* original_hash.collect {| key、value | [キー、値+ 1]}。フラット化]

これは、任意の式を介してキーまたは値を変換する機能も提供します(もちろん、非破壊的です)。


1

Rubyは持っているtap方法(1.8.71.9.3および2.1.0このようなもののために非常に便利です)。

original_hash = { :a => 'a', :b => 'b' }
original_hash.clone.tap{ |h| h.each{ |k,v| h[k] = v.upcase } }
# => {:a=>"A", :b=>"B"}
original_hash # => {:a=>"a", :b=>"b"}

1

Rails固有

誰かto_sが各値に対してメソッドを呼び出す必要があるだけで、Rails 4.2(transform_valuesメソッドlinkを含む)を使用していない場合は、以下を実行できます。

original_hash = { :a => 'a', :b => BigDecimal('23.4') }
#=> {:a=>"a", :b=>#<BigDecimal:5c03a00,'0.234E2',18(18)>}
JSON(original_hash.to_json)
#=> {"a"=>"a", "b"=>"23.4"}

注:「json」ライブラリを使用する必要があります。

注2:これにより、キーも文字列に変換されます


1

値が文字列であることがわかっている場合は、サイクル中にそれらのreplaceメソッドを呼び出すことができます。これにより、値を変更できます。

これは、値が文字列であり、質問に完全には答えられない場合に限られますが、誰かに役立つと思いました。


1
new_hash = old_hash.merge(old_hash) do |_key, value, _value|
             value.upcase
           end

# old_hash = {"a" => "b", "c" => "d"}
# new_hash = {"a" => "B", "c" => "D"}

これは効率的ですか?
ioquatix
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.