Зміна кожного значення в хеші в Ruby


170

Я хочу змінити кожне значення в хеші, щоб додати "%" до і після значення

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

повинні бути змінені на

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

Який найкращий спосіб зробити це?


1
Будь ласка, уточнюйте, чи хочете вимкнути оригінальні рядкові об'єкти, просто мутуйте оригінал, або нічого не змінюйте.
Фрогз

1
Тоді ви прийняли неправильну відповідь. (Без образи на @pst не було наміру, так як я особисто також виступаю за програмування в функціональному стилі замість
мутування

Але все-таки підхід був приємним су
TheReverseFlick

Відповіді:


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.[]не приймає масив пар масивів, для нього потрібна парна кількість прямих аргументів (звідси сплеск вперед).
Фрогз

2
Насправді, Hash. [Key_value_pairs] був введений в 1.8.7, тому лише Ruby 1.8.6 не потребує бризки та вирівнювання.
Марк-Андре Лафортун

2
@Aupajo Hash#eachдає блок і значення блоку. У цьому випадку я не переймався ключем, і тому я не назвав його нічого корисного. Імена змінних можуть починатися з підкреслення, а насправді може бути лише підкресленням. Немає користі від ефективності цього, це лише тонка примітка самодокументування, що я нічого не роблю з цим значенням першого блоку.
Фрогз

1
Я думаю, ви маєте на увазі my_hash.inject(my_hash){ |h,(k,str)| h[k]="%#{str}%"; h }, повинні повернути хеш з блоку
aceofspades

1
Крім того, ви можете скористатися методом every_value, який зрозуміти трохи простіше, ніж використовувати підкреслення для невикористаного значення ключа.
Strand McCutchen

266

У Ruby 2.1 і вище ви можете це зробити

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

5
Дякую, було насправді те, що я шукав. Не знаю, чому на вас так не заважають.
simperreault

7
Це, однак, дуже повільно і дуже голодна оперативна пам'ять. Вхідний хеш повторюється для отримання проміжного набору вкладених масивів, які потім перетворюються на новий хеш. Ігноруючи пікове використання оперативної пам’яті, час роботи значно гірший - порівняльний аналіз цього порівняно з модифікованими на місці рішеннями в іншій відповіді показує 2,5 з проти 1,5 с за однакову кількість ітерацій. Оскільки Рубі є порівняно повільною мовою, уникати повільних шматочків повільної мови має багато сенсу :-)
Ендрю Ходжкінсон

2
@AndrewHodgkinson, хоча я взагалі погоджуюся, і я не прихильник не звертати уваги на продуктивність виконання, не відслідковуючи всі ці підводні камені, починають ставати болем і йти проти "філософії продуктивності спочатку" розробника? Я думаю, це менше коментарів до вас, і більше загального коментаря щодо можливого парадоксу, до якого це нас приводить, використовуючи рубін.
ельсурудо

4
Загадка: ну, ми вже відмовляємося від нашого рішення навіть використовувати рубін, і яку різницю має "цей інший дріб"? Це слизький схил, чи не так? Для запису я вважаю за краще це рішення перед прийнятою відповіддю з точки зору читабельності.
ельсурудо

1
Якщо ви використовуєте Ruby , 2.4+, це ще простіше у використанні , #transform_values!як вказував sschmeck ( stackoverflow.com/a/41508214/6451879 ).
Фінн

127

Ruby 2.4 представив метод Hash#transform_values!, яким ви могли скористатися.

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

Саме те, що я шукав!
Долев

4
Звичайно, є також Hash#transform_values(без удару), який не змінює приймач. Інакше чудова відповідь, дякую!
iGEL

1
Це дійсно зменшить моє використання reduce:-p
iGEL

86

Найкращий спосіб змінити значення Hash на місці - це

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

Менше коду та чіткого наміру. Також швидше, оскільки жодні нові об'єкти не виділяються за значеннями, які потрібно змінити.


Не зовсім вірно: виділяються нові рядки. Все-таки цікаве рішення, яке є ефективним. +1
Фрогз

@Phrogz хороший момент; Я оновив відповідь. Розподілу значень взагалі не уникнути, оскільки не всі значення перетворення можуть бути виражені як мутатори, такі як gsub!.
Сім

3
Як і моя відповідь, але з іншим синонімом я погоджуюся, що updateпередає намір краще, ніж merge!. Я думаю, що це найкраща відповідь.

1
Якщо ви не використовуєте k, використовуйте _замість цього.
секретт

28

Трохи читабельніший - mapце масив одноелементних хешів і reduceте, що зmerge

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

2
Hash[the_hash.map { |key,value| [key, "%#{value}%"] }]
Ясніше

Це надзвичайно неефективний спосіб оновлення значень. Для кожної пари значень спочатку створюється пара Array(for map), а потім a Hash. Тоді кожен крок операції зменшення дублюватиме «пам’ятку» Hashта додавати до неї нову пару ключ-значення. Принаймні , використання :merge!в reduceмодифікувати остаточне Hashна місці. Зрештою, ви не змінюєте значення існуючого об'єкта, а створюєте новий об’єкт, що не є запитанням.
Сім

він повертається, nilякщо the_hashпорожній
DNNX

18

Для цього завдання існує новий метод "Шлях рейлів" :) http://api.rubyonrails.org/classes/Hash.html#method-i-transform_values


8
Також містить Ruby 2.4.0+ Hash#transform_values. Це має бути шлях відтепер.
амебе

1
Коротше кажучи, доступний для Rails 5+ або Ruby 2.4+
Cyril Duchon-Doris


16

Один метод, який не вносить побічні ефекти до оригіналу:

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

Карта Hash # також може бути цікавим для прочитання, оскільки вона пояснює, чому Hash.mapне повертає хеш (саме тому результатний масив[key,value] пар перетворюється на новий Hash) і надає альтернативні підходи до тієї ж загальної схеми.

Щасливе кодування.

[Відмова: Я не впевнений, чи Hash.mapзміниться семантика в Ruby 2.x]


7
Чи навіть Мац знає, чи Hash.mapзміниться семантика в Ruby 2.x?
Ендрю Грімм

1
Метод Hash # [] настільки корисний, але такий некрасивий. Чи є гарніший метод перетворення масивів у хеші таким же чином?
Martijn

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

Мені не подобаються побічні ефекти, але +1 для підходу :) Є each_with_objectв Ruby 1.9 (IIRC), який уникає необхідності прямого доступу до імені, а Map#mergeтакож може працювати. Не впевнений, чим відрізняються складні деталі.

1
Початковий хеш модифікований - це нормально, якщо поведінка передбачається, але може викликати тонкі проблеми, якщо "забути". Я вважаю за краще зменшити змінність об'єкта, але це не завжди може бути практично. (Рубі навряд чи є мовою без побічних ефектів ;-)

8

Hash.merge! є найчистішим рішенням

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

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

@MichieldeMare Вибачте, я не перевірив цього досить ретельно. Ви маєте рацію, блок повинен приймати два параметри. Виправлено.

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