@JanDvorak To pytanie dotyczy nie tylko zwrotu subhasha, ale także modyfikacji istniejącego. Bardzo podobne rzeczy, ale ActiveSupport ma inne sposoby radzenia sobie z nimi.
skalee
Odpowiedzi:
59
Jeśli chcesz, aby metoda zwracała wyodrębnione elementy, ale h1 pozostała taka sama:
To jest O (n2) - będziesz mieć jedną pętlę na selekcji, drugą pętlę na dołączeniu, która będzie nazywana h1.size times.
metakungfu
2
Chociaż ta odpowiedź jest przyzwoita dla czystego rubinu, jeśli używasz szyn, poniższa odpowiedź (używając wbudowanej slicelub except, w zależności od potrzeb) jest znacznie czystsza
Krease
140
ActiveSupportCo najmniej od 2.3.8 dostarcza cztery dogodne metody: #slice, #excepti ich odpowiedniki niszczące: #slice!i #except!. Zostały wymienione w innych odpowiedziach, ale podsumowując je w jednym miejscu:
Zwróć uwagę na wartości zwracane przez metody bang. Nie tylko dostosują istniejący hash, ale również zwrócą usunięte (nieprzechowywane) wpisy. Do Hash#except!najlepiej pasuje przykładzie podanym w pytaniu:
ActiveSupportnie wymaga całych Railsów, jest dość lekki. W rzeczywistości wiele klejnotów nie-railsowych zależy od tego, więc prawdopodobnie masz już to w Gemfile.lock. Nie ma potrzeby samodzielnego rozszerzania klasy Hash.
Łatanie Mokey jest zdecydowanie sposobem na IMO. Znacznie czystsze i sprawia, że intencja jest jaśniejsza.
Romário
1
Dodaj, aby zmodyfikować kod w celu zaadresowania podstawowego modułu, zdefiniuj moduł i zaimportuj rozszerzenie Hash core ... moduł Moduł CoreExtensions Hash def slice (* klucze) :: Hash [[klucze, self.values_at (* klucze)]. Transpose] end end koniec Hash.include CoreExtensions :: Hash
Myślę, że opisujesz ekstrakt !. wyciąg! usuwa klucze z początkowego skrótu, zwracając nowy skrót zawierający usunięte klucze. plasterek! robi odwrotnie: usuwa wszystkie klucze oprócz określonych z początkowego skrótu (ponownie, zwraca nowy hash zawierający usunięte klucze). Więc pokrój! jest trochę bardziej jak operacja „zatrzymania”.
Oto funkcjonalne rozwiązanie, które może być przydatne, jeśli nie używasz Ruby 2.5 i jeśli nie chcesz zanieczyszczać swojej klasy Hash przez dodanie nowej metody:
Odpowiedzi:
Jeśli chcesz, aby metoda zwracała wyodrębnione elementy, ale h1 pozostała taka sama:
h1 = {:a => :A, :b => :B, :c => :C, :d => :D} h2 = h1.select {|key, value| [:b, :d, :e, :f].include?(key) } # => {:b=>:B, :d=>:D} h1 = Hash[h1.to_a - h2.to_a] # => {:a=>:A, :c=>:C}
A jeśli chcesz to załatać w klasie Hash:
class Hash def extract_subhash(*extract) h2 = self.select{|key, value| extract.include?(key) } self.delete_if {|key, value| extract.include?(key) } h2 end end
Jeśli chcesz tylko usunąć określone elementy z skrótu, jest to znacznie łatwiejsze za pomocą delete_if .
h1 = {:a => :A, :b => :B, :c => :C, :d => :D} h1.delete_if {|key, value| [:b, :d, :e, :f].include?(key) } # => {:a=>:A, :c=>:C} h1 # => {:a=>:A, :c=>:C}
źródło
slice
lubexcept
, w zależności od potrzeb) jest znacznie czystszaActiveSupport
Co najmniej od 2.3.8 dostarcza cztery dogodne metody:#slice
,#except
i ich odpowiedniki niszczące:#slice!
i#except!
. Zostały wymienione w innych odpowiedziach, ale podsumowując je w jednym miejscu:x = {a: 1, b: 2, c: 3, d: 4} # => {:a=>1, :b=>2, :c=>3, :d=>4} x.slice(:a, :b) # => {:a=>1, :b=>2} x # => {:a=>1, :b=>2, :c=>3, :d=>4} x.except(:a, :b) # => {:c=>3, :d=>4} x # => {:a=>1, :b=>2, :c=>3, :d=>4}
Zwróć uwagę na wartości zwracane przez metody bang. Nie tylko dostosują istniejący hash, ale również zwrócą usunięte (nieprzechowywane) wpisy. Do
Hash#except!
najlepiej pasuje przykładzie podanym w pytaniu:x = {a: 1, b: 2, c: 3, d: 4} # => {:a=>1, :b=>2, :c=>3, :d=>4} x.except!(:c, :d) # => {:a=>1, :b=>2} x # => {:a=>1, :b=>2}
ActiveSupport
nie wymaga całych Railsów, jest dość lekki. W rzeczywistości wiele klejnotów nie-railsowych zależy od tego, więc prawdopodobnie masz już to w Gemfile.lock. Nie ma potrzeby samodzielnego rozszerzania klasy Hash.źródło
x.except!(:c, :d)
(z hukiem) powinien być# => {:a=>1, :b=>2}
. Dobrze, jeśli możesz edytować swoją odpowiedź.Jeśli używasz szyn , najlepszym rozwiązaniem jest Hash # slice .
{:a => :A, :b => :B, :c => :C, :d => :D}.slice(:a, :c) # => {:a => :A, :c => :C}
Jeśli nie używasz szyn , Hash # values_at zwróci wartości w tej samej kolejności, w jakiej je prosiłeś, więc możesz to zrobić:
def slice(hash, *keys) Hash[ [keys, hash.values_at(*keys)].transpose] end def except(hash, *keys) desired_keys = hash.keys - keys Hash[ [desired_keys, hash.values_at(*desired_keys)].transpose] end
dawny:
slice({foo: 'bar', 'bar' => 'foo', 2 => 'two'}, 'bar', 2) # => {'bar' => 'foo', 2 => 'two'} except({foo: 'bar', 'bar' => 'foo', 2 => 'two'}, 'bar', 2) # => {:foo => 'bar'}
Wyjaśnienie:
Z
{:a => 1, :b => 2, :c => 3}
czego chcemy{:a => 1, :b => 2}
hash = {:a => 1, :b => 2, :c => 3} keys = [:a, :b] values = hash.values_at(*keys) #=> [1, 2] transposed_matrix =[keys, values].transpose #=> [[:a, 1], [:b, 2]] Hash[transposed_matrix] #=> {:a => 1, :b => 2}
Jeśli czujesz, że małpka łatanie jest właściwą drogą, wykonaj następujące czynności:
module MyExtension module Hash def slice(*keys) ::Hash[[keys, self.values_at(*keys)].transpose] end def except(*keys) desired_keys = self.keys - keys ::Hash[[desired_keys, self.values_at(*desired_keys)].transpose] end end end Hash.include MyExtension::Hash
źródło
Ruby 2.5 dodał plasterek Hash # :
h = { a: 100, b: 200, c: 300 } h.slice(:a) #=> {:a=>100} h.slice(:b, :c, :d) #=> {:b=>200, :c=>300}
źródło
Możesz użyć slice! (* Klucze), który jest dostępny w podstawowych rozszerzeniach ActiveSupport
initial_hash = {:a => 1, :b => 2, :c => 3, :d => 4} extracted_slice = initial_hash.slice!(:a, :c)
Initial_hash będzie teraz
{:b => 2, :d =>4}
extract_slide będzie teraz
{:a => 1, :c =>3}
Możesz spojrzeć
slice.rb in ActiveSupport 3.1.3
źródło
module HashExtensions def subhash(*keys) keys = keys.select { |k| key?(k) } Hash[keys.zip(values_at(*keys))] end end Hash.send(:include, HashExtensions) {:a => :A, :b => :B, :c => :C, :d => :D}.subhash(:a) # => {:a => :A}
źródło
def subhash(*keys) select {|k,v| keys.include?(k)} end
h1 = {:a => :A, :b => :B, :c => :C, :d => :D} keys = [:b, :d, :e, :f] h2 = (h1.keys & keys).each_with_object({}) { |k,h| h.update(k=>h1.delete(k)) } #=> {:b => :B, :d => :D} h1 #=> {:a => :A, :c => :C}
źródło
jeśli używasz szyn, może być wygodne użycie Hash. z wyjątkiem
h = {a:1, b:2} h1 = h.except(:a) # {b:2}
źródło
class Hash def extract(*keys) key_index = Hash[keys.map{ |k| [k, true] }] # depends on the size of keys partition{ |k, v| key_index.has_key?(k) }.map{ |group| Hash[group] } end end h1 = {:a => :A, :b => :B, :c => :C, :d => :D} h2, h1 = h1.extract(:b, :d, :e, :f)
źródło
Oto szybkie porównanie wydajności sugerowanych metod,
#select
wydaje się być najszybszek = 1_000_000 Benchmark.bmbm do |x| x.report('select') { k.times { {a: 1, b: 2, c: 3}.select { |k, _v| [:a, :b].include?(k) } } } x.report('hash transpose') { k.times { Hash[ [[:a, :b], {a: 1, b: 2, c: 3}.fetch_values(:a, :b)].transpose ] } } x.report('slice') { k.times { {a: 1, b: 2, c: 3}.slice(:a, :b) } } end Rehearsal -------------------------------------------------- select 1.640000 0.010000 1.650000 ( 1.651426) hash transpose 1.720000 0.010000 1.730000 ( 1.729950) slice 1.740000 0.010000 1.750000 ( 1.748204) ----------------------------------------- total: 5.130000sec user system total real select 1.670000 0.010000 1.680000 ( 1.683415) hash transpose 1.680000 0.010000 1.690000 ( 1.688110) slice 1.800000 0.010000 1.810000 ( 1.816215)
Uszlachetnienie będzie wyglądać następująco:
module CoreExtensions module Extractable refine Hash do def extract(*keys) select { |k, _v| keys.include?(k) } end end end end
I aby z niego skorzystać:
using ::CoreExtensions::Extractable { a: 1, b: 2, c: 3 }.extract(:a, :b)
źródło
Obie
delete_if
ikeep_if
są częścią rdzenia Ruby. Tutaj możesz osiągnąć to, co chcesz, bez łataniaHash
typu.h1 = {:a => :A, :b => :B, :c => :C, :d => :D} h2 = h1.clone p h1.keep_if { |key| [:b, :d, :e, :f].include?(key) } # => {:b => :B, :d => :D} p h2.delete_if { |key, value| [:b, :d, :e, :f].include?(key) } #=> {:a => :A, :c => :C}
Aby uzyskać więcej informacji, sprawdź poniższe linki w dokumentacji:
źródło
Jak wspominali inni, Ruby 2.5 dodał metodę Hash # slice.
Rails 5.2.0beta1 dodał również własną wersję Hash # slice, aby dostosować funkcjonalność dla użytkowników frameworka, którzy używają wcześniejszej wersji Rubiego. https://github.com/rails/rails/commit/01ae39660243bc5f0a986e20f9c9bff312b1b5f8
Jeśli z jakiegoś powodu chcesz wdrożyć własne, jest to również fajny jeden liner:
def slice(*keys) keys.each_with_object(Hash.new) { |k, hash| hash[k] = self[k] if has_key?(k) } end unless method_defined?(:slice)
źródło
Ten kod wprowadza funkcjonalność, o którą prosisz, do klasy Hash:
class Hash def extract_subhash! *keys to_keep = self.keys.to_a - keys to_delete = Hash[self.select{|k,v| !to_keep.include? k}] self.delete_if {|k,v| !to_keep.include? k} to_delete end end
i podaje podane przez Ciebie wyniki:
h1 = {:a => :A, :b => :B, :c => :C, :d => :D} p h1.extract_subhash!(:b, :d, :e, :f) # => {b => :B, :d => :D} p h1 #=> {:a => :A, :c => :C}
Uwaga: ta metoda w rzeczywistości zwraca wyodrębnione klucze / wartości.
źródło
Oto funkcjonalne rozwiązanie, które może być przydatne, jeśli nie używasz Ruby 2.5 i jeśli nie chcesz zanieczyszczać swojej klasy Hash przez dodanie nowej metody:
slice_hash = -> keys, hash { hash.select { |k, _v| keys.include?(k) } }.curry
Następnie możesz go zastosować nawet do zagnieżdżonych haszów:
my_hash = [{name: "Joe", age: 34}, {name: "Amy", age: 55}] my_hash.map(&slice_hash.([:name])) # => [{:name=>"Joe"}, {:name=>"Amy"}]
źródło
To tylko dodatek do metody slice, jeśli klawisze subhash, które chcesz oddzielić od oryginalnego skrótu, będą dynamiczne, możesz to zrobić,
slice(*dynamic_keys) # dynamic_keys should be an array type
źródło
Możemy to zrobić, zapętlając klucze, które chcemy wyodrębnić, po prostu sprawdzając, czy klucz istnieje, a następnie wyodrębniając go.
class Hash def extract(*keys) extracted_hash = {} keys.each{|key| extracted_hash[key] = self.delete(key) if self.has_key?(key)} extracted_hash end end h1 = {:a => :A, :b => :B, :c => :C, :d => :D} h2 = h1.extract(:b, :d, :e, :f)
źródło