Как изменить значения хэша?

126

Я хотел бы заменить каждое valueв хеше на value.some_method.

Например, для заданного простого хеша:

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

каждое значение должно быть .upcased, поэтому оно выглядит так:

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

Я пробовал #collectи #mapвсегда просто возвращал массивы. Есть ли элегантный способ сделать это?

ОБНОВИТЬ

Блин, я забыл: хеш находится в переменной экземпляра, которую нельзя менять. Мне нужен новый хеш с измененными значениями, но я бы предпочел не определять эту переменную явно, а затем перебирать хеш, заполняя ее. Что-то вроде:

new_hash = hash.magic{ ... }
potashin
источник
второй пример в моем ответе возвращает новый хеш. прокомментируйте это, если хотите, чтобы я подробно рассказал о магии, которую творит #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 } 

Эта последняя версия имеет дополнительное преимущество, заключающееся в том, что вы также можете преобразовывать ключи.

KCH
источник
26
Первое предложение не подходит; изменение перечислимого элемента во время итерации приводит к неопределенному поведению - это также верно и для других языков. Вот ответ Матца (он отвечает на пример с использованием массива, но ответ является общим): j.mp/tPOh2U
Marcus
4
@Marcus Я согласен с тем, что в целом следует избегать таких изменений. Но в этом случае это, вероятно, безопасно, поскольку модификация не меняет будущих итераций. Теперь, если был назначен другой ключ (не тот, который был передан блоку), тогда были бы проблемы.
Kelvin
36
@Adam @kch each_with_object- более удобный вариант, injectкогда вы не хотите заканчивать блок хешем:my_hash.each_with_object({}) {|(k, v), h| h[k] = v.upcase }
Kelvin
6
Существует также очень изящный синтаксис для преобразования списка пар в хеш, так что вы можете писатьa_new_hash = Hash[ my_hash.map { |k, v| [k, v.upcase] } ]
переписано
2
с Ruby 2 вы можете писать.to_h
roman-roman
35

Вы можете собрать значения и снова преобразовать их из массива в хэш.

Как это:

config = Hash[ config.collect {|k,v| [k, v.upcase] } ]
Endel
источник
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
Хорошая мысль, и с его обновлением ваше второе решение (неразрушающее) - лучшее.
Крис Доггетт,
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!.

schmijos
источник
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  
Tokumine
источник
1

Я делаю примерно так:

new_hash = Хеш [* original_hash.collect {| ключ, значение | [ключ, значение + 1]}. сглаживание]

Это дает вам возможность преобразовать ключ или значение с помощью любого выражения (и это, конечно, неразрушающее).


источник
1

В Ruby есть tapметод ( 1.8.7 , 1.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"}
Nate
источник
1

Рельсы конкретных

Если кому-то нужно только вызвать to_sметод для каждого из значений и не использовать Rails 4.2 (который включает ссылку наtransform_values метод ), вы можете сделать следующее:

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: это также превратит ключи в струны

lllllll
источник
1

Если вы знаете, что значения являются строками, вы можете вызвать для них метод replace во время цикла: таким образом вы измените значение.

Altohugh это ограничено случаем, когда значения являются строками и, следовательно, не отвечает на вопрос полностью, я подумал, что это может быть полезно для кого-то.

действительно мило
источник
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"}
CNST
источник
Это эффективно?
ioquatix