Zmiana każdej wartości w skrócie w Rubim

170

Chcę zmienić każdą wartość w skrócie, aby dodać „%” przed i po wartości tak

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

należy zmienić na

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

Jak najlepiej to zrobić?

theReverseFlick
źródło
1
Proszę wyjaśnić, czy chcesz zmutować oryginalne obiekty typu string, po prostu zmutować oryginał ma, lub nic nie mutować.
Phrogz
1
Zatem zaakceptowałeś złą odpowiedź. (Bez obrazy @pst zamierzone, ponieważ osobiście opowiadam się również za programowaniem w stylu funkcjonalnym zamiast mutowania obiektów.)
Phrogz
Ale i tak podejście było fajne
theReverseFlick

Odpowiedzi:

178

Jeśli chcesz, aby same ciągi mutowały w miejscu (prawdopodobnie i korzystnie wpływając na inne odwołania do tych samych obiektów łańcuchowych):

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

Jeśli chcesz, aby hash zmienił się w miejscu, ale nie chcesz wpływać na ciągi (chcesz, aby otrzymywał nowe ciągi):

# 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 }

Jeśli chcesz nowy hash:

# 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}%"] } ]
Phrogz
źródło
1
@Andrew Marshall Racja, dzięki. W Rubim 1.8 Hash.[]nie akceptuje tablicy par tablic, wymaga parzystej liczby bezpośrednich argumentów (stąd ikona z przodu).
Phrogz
2
Właściwie Hash. [Key_value_pairs] został wprowadzony w 1.8.7, więc tylko Ruby 1.8.6 nie potrzebuje splat & flatten.
Marc-André Lafortune
2
@Aupajo Hash#eachzwraca zarówno klucz, jak i wartość do bloku. W tym przypadku nie przejmowałem się kluczem, więc nie nazwałem go niczym przydatnym. Nazwy zmiennych mogą zaczynać się od podkreślenia, a w rzeczywistości mogą być tylko podkreśleniem. Nie ma z tego żadnej korzyści dla wydajności, jest to po prostu subtelna samodokumentująca uwaga, że ​​nie robię nic z tą pierwszą wartością bloku.
Phrogz
1
Myślę, że masz na myśli my_hash.inject(my_hash){ |h,(k,str)| h[k]="%#{str}%"; h }, musisz zwrócić haszysz z bloku
aceofspades
1
Alternatywnie możesz użyć metody each_value, która jest nieco łatwiejsza do zrozumienia niż użycie podkreślenia dla nieużywanej wartości klucza.
Strand McCutchen
266

W Ruby 2.1 i nowszych możesz to zrobić

{ a: 'a', b: 'b' }.map { |k, str| [k, "%#{str}%"] }.to_h
shock_one
źródło
5
Dzięki, właśnie tego szukałem. Nie wiem, dlaczego nie cieszysz się tak dużym poparciem.
simperreault
7
Jest to jednak bardzo powolne i bardzo wymagające pamięci RAM. Hash wejściowy jest powtarzany w celu utworzenia pośredniego zestawu zagnieżdżonych tablic, które są następnie konwertowane na nowy skrót. Ignorując szczytowe użycie pamięci RAM, czas działania jest znacznie gorszy - porównanie tego z rozwiązaniami modyfikowanymi na miejscu w innej odpowiedzi pokazuje 2,5 s w porównaniu z 1,5 s przy tej samej liczbie iteracji. Ponieważ Ruby jest stosunkowo wolnym językiem, unikanie powolnych fragmentów języka wolnego ma wiele sensu :-)
Andrew Hodgkinson
2
@AndrewHodgkinson, podczas gdy generalnie się zgadzam i nie zalecam nie zwracania uwagi na wydajność środowiska wykonawczego, czy śledzenie wszystkich tych pułapek wydajnościowych nie staje się uciążliwe i nie jest sprzeczne z filozofią rubinową „najpierw produktywność programisty”? Wydaje mi się, że jest to mniejszy komentarz do ciebie, a bardziej ogólny komentarz dotyczący ewentualnego paradoksu, do którego nas to doprowadziło, używając rubinu.
elsurudo
4
Problem polega na tym, że: cóż, już rezygnujemy z wydajności, decydując się w ogóle na użycie rubinu, więc jaką różnicę robi „ten drugi kawałek”? To śliskie zbocze, prawda? Dla porządku, z punktu widzenia czytelności wolę to rozwiązanie od zaakceptowanej odpowiedzi.
elsurudo
1
Jeśli używasz Ruby 2.4+, jest on jeszcze łatwiejszy w użyciu, #transform_values!jak wskazał sschmeck ( stackoverflow.com/a/41508214/6451879 ).
Finn,
127

Ruby 2.4 wprowadził metodę Hash#transform_values!, której możesz użyć.

{ :a=>'a' , :b=>'b' }.transform_values! { |v| "%#{v}%" }
# => {:a=>"%a%", :b=>"%b%"} 
sschmeck
źródło
Dokładnie to, czego szukałem!
Dolev
4
Oczywiście jest też Hash#transform_values(bez huku), który nie modyfikuje słuchawki. W przeciwnym razie świetna odpowiedź, dzięki!
iGEL
1
To naprawdę zmniejszy moje użycie reduce:-p
iGEL
86

Najlepszym sposobem na zmodyfikowanie wartości skrótu jest

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

Mniej kodu i wyraźny zamiar. Również szybciej, ponieważ żadne nowe obiekty nie są przydzielane poza wartościami, które należy zmienić.

Sim
źródło
Nie do końca prawda: przydzielane są nowe ciągi. Mimo to ciekawe rozwiązanie, które jest skuteczne. +1
Phrogz,
@Phrogz dobra uwaga; Zaktualizowałem odpowiedź. Generalnie nie można uniknąć alokacji wartości, ponieważ nie wszystkie transformacje wartości można wyrazić jako mutatory, takie jak gsub!.
Sim
3
Tak samo jak moja odpowiedź, ale z innym synonimem, zgadzam się, że updatelepiej przekazuje intencję niż merge!. Myślę, że to najlepsza odpowiedź.
1
Jeśli nie używasz k, użyj _zamiast tego.
sekrett
28

Nieco bardziej czytelny, mapdo tablicy jednoelementowych skrótów i reduceto zmerge

the_hash.map{ |key,value| {key => "%#{value}%"} }.reduce(:merge)
edgerunner
źródło
2
Jaśniejsze w użyciuHash[the_hash.map { |key,value| [key, "%#{value}%"] }]
meagar
Jest to wyjątkowo nieefektywny sposób aktualizowania wartości. Dla każdej pary wartości najpierw tworzy parę Array(for map), a następnie a Hash. Następnie na każdym etapie operacji redukcji zostanie zduplikowana „notatka” Hashi dodana do niej nowa para klucz-wartość. Przynajmniej użyj :merge!w reducecelu zmodyfikowania ostatecznego Hashna miejscu. Ostatecznie nie modyfikujesz wartości istniejącego obiektu, ale tworzysz nowy obiekt, co nie jest tym, o co chodziło w pytaniu.
Sim
zwraca, niljeśli the_hashjest pusty
DNNX
16

Jedna metoda, która nie wprowadza skutków ubocznych do oryginału:

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

Hash # map może być również interesującą lekturą, ponieważ wyjaśnia, dlaczego Hash.mapnie zwraca Hash (dlatego wynikowa tablica [key,value]par jest konwertowana na nowy Hash) i zapewnia alternatywne podejście do tego samego ogólnego wzorca.

Miłego kodowania.

[Uwaga: nie jestem pewien, czy Hash.mapsemantyka zmieni się w Ruby 2.x]


źródło
7
Czy Matz w ogóle wie, czy Hash.mapsemantyka zmienia się w Ruby 2.x?
Andrew Grimm,
1
Metoda Hash # [] jest bardzo przydatna, ale tak brzydka. Czy istnieje ładniejsza metoda konwertowania tablic na skróty w ten sam sposób?
Martijn,
15
my_hash.each do |key, value|
  my_hash[key] = "%#{value}%"
end
Andrew Marshall
źródło
Nie lubię efektów ubocznych, ale +1 za podejście :) Jest each_with_objectw Rubim 1.9 (IIRC), który pozwala uniknąć konieczności bezpośredniego dostępu do nazwy i Map#mergemoże również działać. Nie jestem pewien, czym różnią się zawiłe szczegóły.
1
Początkowy skrót jest modyfikowany - jest to w porządku, jeśli zachowanie jest przewidywane, ale może powodować subtelne problemy, jeśli zostanie „zapomniane”. Wolę ograniczać zmienność obiektów, ale nie zawsze jest to praktyczne. (Ruby nie jest językiem pozbawionym efektów ubocznych ;-)
8

Hash.merge! to najczystsze rozwiązanie

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

puts o.inspect
> { :a => "%a%", :b => "%b%" }
dorycki
źródło
@MichieldeMare Przepraszam, nie przetestowałem tego wystarczająco dokładnie. Masz rację, blok musi przyjąć dwa parametry. Poprawione.
5

Po przetestowaniu go z RSpec w ten sposób:

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

Możesz zaimplementować Hash # map_values ​​w następujący sposób:

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

Funkcji można następnie użyć w następujący sposób:

{:a=>'a' , :b=>'b'}.map_values { |v| "%#{v}%" }
# {:a=>"%a%", :b=>"%b%"}
wedesoft
źródło
1

Jeśli jesteście ciekawi, który wariant Inplace jest najszybszy, oto:

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
lzap
źródło