Jak zmienić wartości skrótu?

126

Chciałbym zamienić każdy valuew skrócie na value.some_method.

Na przykład dla danego prostego skrótu:

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

każda wartość powinna być .upcased, więc wygląda to tak:

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

Próbowałem #collecti #mapzawsze odzyskiwałem tablice. Czy jest na to elegancki sposób?

AKTUALIZACJA

Cholera, zapomniałem: hash znajduje się w zmiennej instancji, której nie należy zmieniać. Potrzebuję nowego skrótu ze zmienionymi wartościami, ale wolałbym nie definiować tej zmiennej jawnie, a następnie zapętlać ją, wypełniając ją. Coś jak:

new_hash = hash.magic{ ... }
potashin
źródło
drugi przykład w mojej odpowiedzi zwraca nowy hash. skomentuj to, jeśli chcesz, żebym rozwinął magię, którą robi #inject.
kch

Odpowiedzi:

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

lub, jeśli wolisz zrobić to w sposób nieniszczący i zwrócić nowy hash zamiast modyfikowania my_hash:

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

Ta ostatnia wersja ma tę dodatkową zaletę, że możesz również przekształcić klucze.

kch
źródło
26
Pierwsza sugestia nie jest odpowiednia; modyfikowanie wyliczalnego elementu podczas iteracji prowadzi do niezdefiniowanego zachowania - dotyczy to również innych języków. Oto odpowiedź od Matza (odpowiada na przykład przy użyciu tablicy, ale odpowiedź jest ogólna): j.mp/tPOh2U
Marcus,
4
@Marcus Zgadzam się, że generalnie należy unikać takich modyfikacji. Ale w tym przypadku jest to prawdopodobnie bezpieczne, ponieważ modyfikacja nie zmienia przyszłych iteracji. Gdyby teraz przydzielono inny klucz (nie ten przekazany do bloku), wystąpiłyby kłopoty.
Kelvin
36
@Adam @kch each_with_objectto wygodniejsza wersja, injectkiedy nie chcesz kończyć bloku hashem:my_hash.each_with_object({}) {|(k, v), h| h[k] = v.upcase }
Kelvin
6
Istnieje również bardzo zgrabna składnia do konwersji listy par na skrót, więc możesz pisaća_new_hash = Hash[ my_hash.map { |k, v| [k, v.upcase] } ]
przepisano
2
z Ruby 2 możesz pisać.to_h
roman-roman
35

Możesz zebrać wartości i ponownie przekonwertować je z Array na Hash.

Lubię to:

config = Hash[ config.collect {|k,v| [k, v.upcase] } ]
Endel
źródło
23

To zrobi to:

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

W przeciwieństwie do injectzalet jest to, że nie musisz ponownie zwracać hasha wewnątrz bloku.

Konrad Reiche
źródło
Podoba mi się, że ta metoda nie modyfikuje oryginalnego skrótu.
Christian Di Lorenzo
10

Wypróbuj tę funkcję:

h = {"a" => "b", "c" => "d"}
h.each{|i,j| j.upcase!} # now contains {"a" => "B", "c" => "D"}.
Chris Doggett
źródło
5
to dobrze, ale należy zauważyć, że działa tylko wtedy, gdy j ma destrukcyjną metodę, która działa na siebie. Więc nie może obsłużyć ogólnego przypadku value.some_method.
kch
Słuszna uwaga, a dzięki jego aktualizacji Twoje drugie rozwiązanie (nieniszczące) jest najlepsze.
Chris Doggett
10

Jest na to metoda w ActiveSupport v4.2.0. Nazywa się transform_valuesi po prostu wykonuje blok dla każdej pary klucz-wartość.

Ponieważ robią to za pomocą, eachmyślę, że nie ma lepszego sposobu niż pętla.

hash = {sample: 'gach'}

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

Aktualizacja:

Od wersji 2.4.0 Rubiego możesz natywnie używać #transform_valuesi #transform_values!.

schmijos
źródło
2

Możesz pójść o krok dalej i zrobić to na zagnieżdżonym skrócie. Z pewnością dzieje się to dość często w przypadku projektów Railsowych.

Oto kod zapewniający, że hash parametrów jest w 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  
Tokumina
źródło
1

Robię coś takiego:

new_hash = Hash [* original_hash.collect {| klucz, wartość | [klucz, wartość + 1]}. spłaszcz]

Zapewnia to możliwość przekształcania klucza lub wartości również za pomocą dowolnego wyrażenia (i oczywiście jest to nieniszczące).


źródło
1

Ruby ma tapmetodę ( 1.8.7 , 1.9.3 i 2.1.0 ), która jest bardzo przydatna do takich rzeczy.

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

Specyficzne dla szyn

W przypadku, gdy ktoś potrzebuje tylko wywołać to_smetodę do każdej z wartości i nie używa Railsów 4.2 (co zawiera link dotransform_values metody ), możesz wykonać następujące czynności:

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

Uwaga: wymagane jest użycie biblioteki „json”.

Uwaga 2: Spowoduje to również zmianę kluczy w struny

lllllll
źródło
1

Jeśli wiesz, że wartości są łańcuchami, możesz wywołać na nich metodę replace w trakcie cyklu: w ten sposób zmienisz wartość.

Altohugh jest to ograniczone do przypadku, w którym wartości są łańcuchami i dlatego nie odpowiada w pełni na pytanie, pomyślałem, że może to być przydatne dla kogoś.

bardzo miły
źródło
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
źródło
Czy to jest wydajne?
ioquatix