Ruby: najłatwiejszy sposób filtrowania kluczy skrótu?

225

Mam skrót, który wygląda mniej więcej tak:

params = { :irrelevant => "A String",
           :choice1 => "Oh look, another one",
           :choice2 => "Even more strings",
           :choice3 => "But wait",
           :irrelevant2 => "The last string" }

I chcę prosty sposób na odrzucenie wszystkich kluczy, które nie są wyborem + int. Może to być wybór1 lub wybór1 poprzez wybór10. To się zmienia.

Jak wyróżnić klawisze, wybierając tylko słowo i cyfrę lub cyfry po nich?

Premia:

Zamień skrót w ciąg znaków z tabulatorem (\ t) jako separatorem. Zrobiłem to, ale wymagało to kilku wierszy kodu. Zazwyczaj mistrzowie Rubicy mogą to zrobić w jednym lub kilku wierszach.

Derek
źródło
Jeśli chodzi o pytanie bonusowe, czy możesz wyjaśnić, jak ma wyglądać sznurek.
mikej
Jasne, powyższy przykładowy skrót uzyskałby: „O, popatrz, jeszcze jeden \ tEven more strings \ tBut wait” (bez \ t na końcu łańcucha, tylko między nimi)
Derek
Twój przykładowy ciąg został obcięty w komentarzu. Możesz użyć linku edycyjnego, aby edytować swoje pytanie i dodać tam przykład. Możesz również opublikować rubin, który wymyśliłeś do tej pory, jako przykład tego, co chcesz osiągnąć.
mikej
2
możliwy duplikat Ruby Hash Filter
jbochi

Odpowiedzi:

281

Edytuj do oryginalnej odpowiedzi : Mimo że jest to odpowiedź (w momencie komentarza) jest wybraną odpowiedzią, oryginalna wersja tej odpowiedzi jest nieaktualna.

Dodam tutaj aktualizację, aby inni mogli uniknąć omijania tej odpowiedzi, tak jak ja.

Jak wspomniano w innej odpowiedzi, Ruby> = 2.5 dodał Hash#slicemetodę, która wcześniej była dostępna tylko w Railsach.

Przykład:

> { one: 1, two: 2, three: 3 }.slice(:one, :two)
=> {:one=>1, :two=>2}

Koniec edycji. Poniżej znajduje się oryginalna odpowiedź, która, jak sądzę, będzie przydatna, jeśli korzystasz z Ruby <2,5 bez Railsów, chociaż wydaje mi się, że ta sprawa jest dość rzadka w tym momencie.


Jeśli używasz Ruby, możesz użyć tej selectmetody. Będziesz musiał przekonwertować klucz z symbolu na ciąg, aby wykonać dopasowanie wyrażenia regularnego. To da ci nowy Hash z wybranymi opcjami.

choices = params.select { |key, value| key.to_s.match(/^choice\d+/) }

lub możesz użyć delete_ifi zmodyfikować istniejący hash np

params.delete_if { |key, value| !key.to_s.match(/choice\d+/) }

lub jeśli chodzi tylko o klucze, a nie o wartości, które chcesz, możesz:

params.keys.select { |key| key.to_s.match(/^choice\d+/) }

a to da tylko tablicę kluczy np [:choice1, :choice2, :choice3]

mikej
źródło
1
Doskonały! Kiedy to zobaczę, jest to takie proste! Dziękuję bardzo i dziękuję innym, którzy poświęcili czas również na odpowiedź.
Derek
2
Symbole można obecnie analizować za pomocą wyrażeń regularnych IIRC.
Andrew Grimm,
@Andrew, myślę, że masz rację. Miałem 1.8.7, kiedy testowałem odpowiedź na to pytanie.
mikej
1
Odpowiednikiem .selectjest .reject, jeśli to czyni kod bardziej idiomatycznym.
Joe Atzberger,
Ten #slicesposób działa dla mnie w wersji 2.5.3, nawet bez aktywnego wsparcia.
graywolf
305

W Ruby wybór Hash # jest właściwą opcją. Jeśli pracujesz z Railsami, możesz użyć Hash # slice i Hash # slice !. np. (szyny 3.2.13)

h1 = {:a => 1, :b => 2, :c => 3, :d => 4}

h1.slice(:a, :b)         # return {:a=>1, :b=>2}, but h1 is not changed

h2 = h1.slice!(:a, :b)   # h1 = {:a=>1, :b=>2}, h2 = {:c => 3, :d => 4}
lfx_cool
źródło
12
uważaj na klucze strunowe .. h1 = {'a' => 1,: b => 2} zwróci tylko {: b => 2} z h1.slice (: a
,:
Niesamowite rozwiązanie. Użyłem tego przy zwracaniu wyniku w rspec, gdzie chciałem parsować wartość skrótu w haszu. `` `test = {: a => 1,: b => 2, c => {: A => 1,: B => 2}} solution ==> test.c.slice (: B)` ` `
PriyankaK
Zamień szyny na ActiveSupport, a to jest idealne;)
Geoffroy
5
to jednak nie odpowiada na pytanie, ponieważ chce on wyodrębnić wartości dla kluczy, które są „choice + int”. Twoje rozwiązanie może tylko wyodrębnić znane wcześniej klucze.
metakungfu
46

Najprostszym sposobem jest dołączenie gem 'activesupport'(lub gem 'active_support').

Następnie w swojej klasie wystarczy

require 'active_support/core_ext/hash/slice'

i zadzwonić

params.slice(:choice1, :choice2, :choice3) # => {:choice1=>"Oh look, another one", :choice2=>"Even more strings", :choice3=>"But wait"}

Uważam, że nie warto deklarować innych funkcji, które mogą zawierać błędy, i lepiej jest użyć metody, która została poprawiona w ciągu ostatnich kilku lat.

Nuno Costa
źródło
Activerecord nie jest do tego potrzebny przynajmniej w wersji 2.5.
graywolf
13

Najprostszym sposobem jest dołączenie klejnotu „activesupport” (lub klejnot „active_support”).

params.slice(:choice1, :choice2, :choice3)

翟英昌
źródło
4
Dodaj więcej szczegółów do swojej odpowiedzi.
Mohit Jain
13

Jeśli pracujesz z szynami i masz klucze na osobnej liście, możesz użyć *zapisu:

keys = [:foo, :bar]
hash1 = {foo: 1, bar:2, baz: 3}
hash2 = hash1.slice(*keys)
=> {foo: 1, bar:2}

Jak stwierdzono w innych odpowiedziach, możesz także użyć slice!do modyfikacji skrótu na miejscu (i zwrócenia skasowanego klucza / wartości).

Robert
źródło
9

To jest jeden wiersz, aby rozwiązać pełne oryginalne pytanie:

params.select { |k,_| k[/choice/]}.values.join('\t')

Ale większość powyższych rozwiązań rozwiązuje przypadek, w którym musisz znać klucze z wyprzedzeniem, używając slicelub zwykłego wyrażenia regularnego.

Oto inne podejście, które działa w przypadku prostych i bardziej złożonych przypadków użycia, które można wymieniać w czasie wykonywania

data = {}
matcher = ->(key,value) { COMPLEX LOGIC HERE }
data.select(&matcher)

Teraz nie tylko pozwala to na bardziej złożoną logikę dopasowywania kluczy lub wartości, ale jest również łatwiejsze do testowania i można zamienić pasującą logikę w czasie wykonywania.

Ex, aby rozwiązać pierwotny problem:

def some_method(hash, matcher) 
  hash.select(&matcher).values.join('\t')
end

params = { :irrelevant => "A String",
           :choice1 => "Oh look, another one",
           :choice2 => "Even more strings",
           :choice3 => "But wait",
           :irrelevant2 => "The last string" }

some_method(params, ->(k,_) { k[/choice/]}) # => "Oh look, another one\\tEven more strings\\tBut wait"
some_method(params, ->(_,v) { v[/string/]}) # => "Even more strings\\tThe last string"
metakungfu
źródło
1
Bardzo podoba mi się mecz
Calin
7

Z Hash::select:

params = params.select { |key, value| /^choice\d+$/.match(key.to_s) }
Arnaud Le Blanc
źródło
6

Jeśli chcesz pozostały skrót:

params.delete_if {|k, v| ! k.match(/choice[0-9]+/)}

lub jeśli chcesz tylko klucze:

params.keys.delete_if {|k| ! k.match(/choice[0-9]+/)}
ayckoster
źródło
W zależności od tego, ile masz wybórX i nieistotnyX, LUB LUB usuń_jeśli może być lepszy. Jeśli masz większy wybór, X usuń_jeśli jest lepsze.
ayckoster
6

Umieść to w inicjalizatorze

class Hash
  def filter(*args)
    return nil if args.try(:empty?)
    if args.size == 1
      args[0] = args[0].to_s if args[0].is_a?(Symbol)
      self.select {|key| key.to_s.match(args.first) }
    else
      self.select {|key| args.include?(key)}
    end
  end
end

To możesz zrobić

{a: "1", b: "b", c: "c", d: "d"}.filter(:a, :b) # =>  {a: "1", b: "b"}

lub

{a: "1", b: "b", c: "c", d: "d"}.filter(/^a/)  # =>  {a: "1"}
montrealmike
źródło
5
params.select{ |k,v| k =~ /choice\d/ }.map{ |k,v| v}.join("\t")
Puhlze
źródło
4

Co do pytania bonusowego:

  1. Jeśli masz dane wyjściowe z #selectmetody takiej jak ta (lista tablic 2-elementowych):

    [[:choice1, "Oh look, another one"], [:choice2, "Even more strings"], [:choice3, "But wait"]]

    następnie po prostu weź ten wynik i wykonaj:

    filtered_params.join("\t")
    # or if you want only values instead of pairs key-value
    filtered_params.map(&:last).join("\t")
  2. Jeśli masz dane wyjściowe z #delete_ifmetody takiej jak ta (skrót):

    {:choice1=>"Oh look, another one", :choice2=>"Even more strings", :choice3=>"But wait"}

    następnie:

    filtered_params.to_a.join("\t")
    # or
    filtered_params.values.join("\t")
MBO
źródło
Świetne rozwiązania. Jedno pytanie: Czy filter_params.map (&: last) .join ("\ t") jest skrótem od filter_params.map (| i | i.last) .join ("\ t")? Jeśli tak, to co jest „ostatnie”? czy mogę użyć &: value, aby uzyskać wartość skrótu?
Derek
mapprzyjmuje blok jako argument. Możesz tworzyć bloki w locie, z &:methodkonstrukcją, która tworzy blok {|v| v.method }. W moim przypadku &:lastwywołano argument tablica (tablica 2-elementowa). Jeśli chcesz się nim bawić, najpierw sprawdź, jaki argument otrzymujesz w bloku (zdobądź 1 argument, nie dekonstruuj go na wiele argumentów!) I spróbuj znaleźć 1 metodę (nie możesz połączyć metod &:method), która zwróci ci to, co masz chcieć. Jeśli to możliwe, możesz użyć skrótu, jeśli nie, potrzebujesz pełnego bloku
MBO
2
params = { :irrelevant => "A String",
           :choice1 => "Oh look, another one",
           :choice2 => "Even more strings",
           :choice3 => "But wait",
           :irrelevant2 => "The last string" }

choices = params.select { |key, value| key.to_s[/^choice\d+/] }
#=> {:choice1=>"Oh look, another one", :choice2=>"Even more strings", :choice3=>"But wait"}
Arup Rakshit
źródło
0

Miałem podobny problem, w moim przypadku rozwiązaniem było jedno-liniowe działanie, nawet jeśli klucze nie są symbolami, ale musisz mieć klucze kryteriów w tablicy

criteria_array = [:choice1, :choice2]

params.select { |k,v| criteria_array.include?(k) } #=> { :choice1 => "Oh look another one",
                                                         :choice2 => "Even more strings" }

Inny przykład

criteria_array = [1, 2, 3]

params = { 1 => "A String",
           17 => "Oh look, another one",
           25 => "Even more strings",
           49 => "But wait",
           105 => "The last string" }

params.select { |k,v| criteria_array.include?(k) } #=> { 1 => "A String"}
Jose Paez
źródło