Ruby: Karma Anahtarları Filtrelemenin En Kolay Yolu?


225

Ben böyle bir şeye benzeyen bir karma var:

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

Ve seçim + int olmayan tüm anahtarları reddetmek için basit bir yol istiyorum. Seçim1 veya seçim1 ile seçim1 olabilir. Değişir.

Anahtarları sadece kelime seçimi ve arkasından bir rakam veya rakam ile nasıl ayırırım?

Bonus:

Karmayı, sınırlayıcı olarak sekmesi (\ t) olan bir dizeye dönüştürün. Bunu yaptım, ama birkaç satır kod aldı. Genellikle usta Rubiler bunu bir çizgi halinde yapabilir.


Bonus soru açısından, dizenin neye benzemesini istediğinizi açıklığa kavuşturabilirsiniz.
mikej

Tabii ki, yukarıdaki örnek karma: "Oh bak, başka bir \ tEven daha fazla dizgi \ tAma bekleyin" (dizenin sonunda \ t ile değil, sadece aralarında)
Derek

Örnek dizeniz yorumda kesildi. Sorunuzu düzenlemek ve örneği buraya eklemek için düzenle bağlantısını kullanabilirsiniz. Ayrıca, elde etmek istediğiniz yakutu, neye ulaşmak istediğinize bir örnek olarak gönderebilirsiniz.
mikej

Yanıtlar:


281

Orijinal cevaba düzenle : Bu cevap (bu yorumun zamanı itibariyle) seçilen cevap olmasına rağmen, bu cevabın orijinal sürümü eski.

Burada başkalarının da benim yaptığım gibi bu yanıttan kaçınmasına yardımcı olmak için bir güncelleme ekliyorum.

Diğer yanıtta belirtildiği gibi, Ruby> = 2.5 Hash#slicedaha önce sadece Rails'te mevcut olan yöntemi ekledi .

Misal:

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

Düzenleme sonu. Takip eden şey, Rails olmadan Ruby <2.5 üzerindeyseniz, tahmin edeceğim orijinal cevap, ancak bu noktada bu durumun oldukça nadir olduğunu hayal ediyorum.


Ruby kullanıyorsanız, selectyöntemi kullanabilirsiniz . Normal ifade eşleşmesini yapmak için anahtarı bir Sembol'den Dizeye dönüştürmeniz gerekir. Bu size sadece seçenekleri olan yeni bir Hash verecektir.

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

veya delete_ifmevcut Hash'i kullanabilir ve değiştirebilirsiniz.

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

veya yalnızca anahtarlarsa ve istediğiniz değerler değilse, şunları yapabilirsiniz:

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

ve bu sadece anahtarların bir dizisini verecektir. [:choice1, :choice2, :choice3]


1
Mükemmel! Bir kez gördüm, bu çok basit! Çok teşekkürler ve cevap vermeye zaman ayıran diğerlerine de teşekkür ederim.
Derek

2
Simgeler, günümüzde IIRC'de düzenli ifadelerle ayrıştırılabilmektedir.
Andrew Grimm

@Andrew, bence haklısın. Bu sorunun cevabını test ederken 1.8.7'deydim.
mikej

1
İçin muadili .selectolan .rejectbu senin kod daha deyimsel yaparsa,.
Joe Atzberger

#sliceYolu bile ActiveSupport olmadan 2.5.3 üzerinde benim için çalışıyor.
graywolf

305

Ruby'de Karma # seçimi doğru bir seçenektir. Rails ile çalışıyorsanız Hash # slice ve Hash # slice! Kullanabilirsiniz. örn. (raylar 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}

12
dizeli anahtarlara dikkat edin..h1 = {'a' => 1,: b => 2} yalnızca h1.slice ile {: b => 2} döndürecektir (: a
,:

Müthiş bir çözüm. Bunu bir hash içindeki bir karma değerini ayrıştırmak istediğim rspec'de sonuç döndürmede kullandım. `` `test = {: a => 1,: b => 2, c => {: A => 1,: B => 2}} çözüm ==> test.c.slice (: B) '' `
PriyankaK

Rails'i ActiveSupport ile değiştirin ve bu mükemmel;)
Geoffroy

5
bu "soru + int" olan anahtarların değerlerini ayıklamanın bir yolunu istediği için soruyu cevaplamaz. Çözümünüz yalnızca önceden bilinen anahtarları çıkarabilir.
metakungfu

46

En kolay yol gem 'activesupport'(veya gem 'active_support') eklemektir .

O zaman, sınıfınızda sadece

require 'active_support/core_ext/hash/slice'

ve aramak

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

Hatalar olabilen diğer işlevleri bildirmenin buna değmeyeceğine inanıyorum ve son birkaç yıldır tweaked edilmiş bir yöntem kullanmak daha iyi.


Bunun için en az 2.5'te Activerecord gerekmez.
graywolf

13

En kolay yol, taş 'activesupport' (veya gem 'active_support') değerini eklemektir.

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


4
Lütfen cevabınıza daha fazla ayrıntı ekleyin.
Mohit Jain

13

Raylarla çalışıyorsanız ve anahtarlar ayrı bir listede varsa, *gösterimi kullanabilirsiniz :

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

Belirtilen diğer yanıtlar gibi slice!, karma değerini yerinde değiştirmek (ve silinen anahtarı / değerleri döndürmek) için de kullanabilirsiniz .


9

Bu, orijinal sorunun tamamını çözmek için tek bir satırdır:

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

Ancak yukarıdaki çözümlerin çoğu, tuşları önceden sliceveya basit regexp kullanarak bilmeniz gereken bir durumu çözüyor.

İşte basit ve daha karmaşık kullanım durumları için çalışan ve çalışma zamanında değiştirilebilen başka bir yaklaşım

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

Şimdi sadece bu, anahtarların veya değerlerin eşleştirilmesinde daha karmaşık mantığa izin vermekle kalmaz, aynı zamanda test edilmesi de daha kolaydır ve eşleşen mantığı çalışma zamanında değiştirebilirsiniz.

Orijinal sorunu çözmek için eski:

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"

1
Eşleştiriciyi gerçekten çok seviyorum
Calin


6

Kalan karmayı istiyorsanız:

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

ya da sadece anahtarları istiyorsanız:

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

Kaç seçenekX ve alakasızX seçtiğinize bağlı olarak VEYA delete_if daha iyi olabilir. Daha fazla seçeneğiniz varsa delete_if daha iyidir.
ayckoster

6

Bunu bir başlatıcıya koy

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

Sonra yapabilirsin

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

veya

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

5
params.select{ |k,v| k =~ /choice\d/ }.map{ |k,v| v}.join("\t")

4

Bonus soruya gelince:

  1. Böyle bir #selectyöntemden çıkışınız varsa (2 elemanlı dizilerin listesi):

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

    o zaman bu sonucu alın ve yürütün:

    filtered_params.join("\t")
    # or if you want only values instead of pairs key-value
    filtered_params.map(&:last).join("\t")
  2. Böyle bir #delete_ifyöntemden (karma) çıktı varsa :

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

    sonra:

    filtered_params.to_a.join("\t")
    # or
    filtered_params.values.join("\t")

Harika çözümler. Bir soru: filter_params.map (&: son) .join ("\ t"), filter_params.map (| i | i.last) .join ("\ t") için kısadır? Eğer öyleyse, 'son' nedir? karma değerini almak için &: değerini kullanabilir miyim?
Derek

mapargüman olarak blok alır. Blok oluşturacak &:methodinşaat ile anında blok oluşturabilirsiniz {|v| v.method }. Benim durumumda &:lastdizi (2 elemanlı dizi) argümanı çağrıldı. Eğer onunla oynamak istiyorsanız, önce hangi argümanı bloğa aldığınızı kontrol edin (1 argüman alın, birden fazla argümana ayrıştırmayın!) Ve &:methodsize ne döndüreceğiniz 1 yöntem (yöntemleri zincirleyemezsiniz ) bulmaya çalışın. istemek. Mümkünse, o zaman kısayolu kullanabilirsiniz, değilse, tam bloğa ihtiyacınız vardır
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"}

0

Benzer bir sorun yaşadım, benim durumumda çözüm, anahtarlar sembol olmasa bile çalışan bir astardı, ancak bir dizideki ölçüt anahtarlarına sahip olmanız gerekiyor

criteria_array = [:choice1, :choice2]

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

Başka bir örnek

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"}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.