Як змінити значення Hash?


126

Я хотів би замінити кожного valueна хеш value.some_method.

Наприклад, для простого хешу:

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

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

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

Я спробував #collectі , #mapале завжди просто отримати масиви назад. Чи є елегантний спосіб це зробити?

ОНОВЛЕННЯ

Чорт, я забув: Хеш - це змінна інстанція, яку не слід міняти. Мені потрібен новий хеш із зміненими значеннями, але я вважаю за краще не визначати цю змінну явно, а потім перетворювати цикл на хеш, заповнюючи її. Щось на зразок:

new_hash = hash.magic{ ... }

другий приклад у моїй відповіді повертає новий хеш. коментуйте це, якщо ви хочете, щоб я розширив магію, яку робить #inject.
кч

Відповіді:


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
Перша пропозиція не підходить; зміна чисельного елемента під час ітерації призводить до невизначеної поведінки - це справедливо і в інших мовах. Ось відповідь від Мац (він відповідає на приклад , використовуючи масив, але відповідь родової): j.mp/tPOh2U
Marcus

4
@Marcus Я погоджуюся, що взагалі слід уникати таких змін. Але в цьому випадку це, мабуть, безпечно, оскільки модифікація не змінює майбутніх ітерацій. Тепер, якщо був призначений інший ключ (який не був переданий блоку), то виникли б проблеми.
Келвін

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


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.
кч

Хороший момент, і з його оновленням ваше друге рішення (неруйнівне) - найкраще.
Кріс Доггетт

10

Для цього існує метод ActiveSupport v4.2.0. Він називається transform_valuesі в основному просто виконує блок для кожної пари ключ-значення.

Оскільки вони роблять це з eachI, я думаю, немає кращого способу, ніж пройти цикл.

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.

Ось код, щоб переконатися, що хеш-парам є в 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 = Хеш [* 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"}

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: Це також перетворить клавіші в рядки


1

Якщо ви знаєте, що значення - це рядки, ви можете викликати метод заміни на них під час циклу: таким чином ви зміните значення.

Але все це обмежено випадком, коли значення є рядками, і, отже, не відповідають на питання повністю, я вважав, що це може бути корисним для когось.


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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.