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{ ... }

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.


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

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.


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

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!.


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  

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).


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

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


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ś.


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

Czy to jest wydajne?
ioquatix
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.