Ruby: วิธีที่ง่ายที่สุดในการกรอง Hash Keys?


225

ฉันมีแฮชที่มีลักษณะดังนี้:

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

และฉันต้องการวิธีง่ายๆในการปฏิเสธกุญแจทั้งหมดที่ไม่ใช่ตัวเลือก + int มันอาจเป็นทางเลือก 1 หรือทางเลือก 1 ผ่านทางเลือก 10 มันแตกต่างกันไป

ฉันจะทำกุญแจด้วยตัวเลือกคำและตัวเลขหรือหลักหลังพวกเขาได้อย่างไร

โบนัส:

แปลงแฮชเป็นสตริงด้วยแท็บ (\ t) เป็นตัวคั่น ฉันทำสิ่งนี้ แต่ใช้โค้ดหลายบรรทัด โดยปกติแล้วผู้เชี่ยวชาญ Rubician สามารถทำได้ในหนึ่งหรือมากกว่านั้น


ในแง่ของคำถามโบนัสคุณช่วยอธิบายสิ่งที่คุณต้องการให้สตริงดูได้ไหม
mikej

แน่นอนแฮชตัวอย่างด้านบนจะให้: "โอ้ดูอีกหนึ่ง \ t อีกหนึ่งสาย \ tBut รอ" (โดยไม่ \ t ที่ส่วนท้ายของสตริงเพียงระหว่างพวกเขาเท่านั้น)
ดีเร็ก

สตริงตัวอย่างของคุณถูกตัดออกไปในความคิดเห็น คุณสามารถใช้ลิงก์แก้ไขเพื่อแก้ไขคำถามของคุณและเพิ่มตัวอย่างได้ นอกจากนี้คุณยังสามารถโพสต์ทับทิมที่คุณสร้างขึ้นเพื่อเป็นตัวอย่างของสิ่งที่คุณต้องการบรรลุ
mikej

2
อาจเป็นไปได้ซ้ำของตัวกรอง Ruby Hash
jbochi

คำตอบ:


281

แก้ไขเป็นคำตอบเดิม : ถึงแม้ว่าคำตอบนี้จะเป็นคำตอบที่เลือก แต่รุ่นเดิมของคำตอบนี้ล้าสมัยแล้ว

ฉันกำลังเพิ่มการอัปเดตที่นี่เพื่อช่วยให้ผู้อื่นหลีกเลี่ยงการถูกเบี่ยงเบนจากคำตอบนี้เหมือนที่ฉันทำ

ตามที่ระบุไว้ในคำตอบอื่น ๆ Ruby> = 2.5 ได้เพิ่มHash#sliceวิธีการที่เคยมีเฉพาะใน Rails เท่านั้น

ตัวอย่าง:

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

สิ้นสุดการแก้ไข ต่อไปนี้เป็นคำตอบดั้งเดิมที่ฉันคิดว่าจะมีประโยชน์หากคุณใช้ Ruby <2.5 ที่ไม่มี Rails แม้ว่าฉันจะจินตนาการว่ากรณีนี้ค่อนข้างแปลกในตอนนี้


หากคุณใช้ Ruby คุณสามารถใช้selectวิธีนี้ได้ คุณจะต้องแปลงคีย์จาก Symbol เป็น String เพื่อทำการจับคู่ regexp นี่จะให้แฮชใหม่ที่มีตัวเลือกในตัวคุณ

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

หรือคุณสามารถใช้delete_ifและแก้ไขแฮชที่มีอยู่เช่น

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

หรือถ้ามันเป็นเพียงกุญแจและไม่ใช่ค่าที่คุณต้องการคุณสามารถทำได้:

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

และสิ่งนี้จะให้เพียงแค่อาร์เรย์ของคีย์เช่น [:choice1, :choice2, :choice3]


1
ยอดเยี่ยม เมื่อฉันเห็นมันง่ายมาก! ขอบคุณมากและขอบคุณคนอื่น ๆ ที่ใช้เวลาในการตอบด้วย
ดีเร็ก

2
สามารถแยกวิเคราะห์สัญลักษณ์ด้วยนิพจน์ทั่วไปในปัจจุบัน IIRC
Andrew Grimm

@ แอนดรูว์ฉันคิดว่าคุณพูดถูก ฉันอยู่ที่ 1.8.7 เมื่อฉันทดสอบคำตอบของคำถามนี้
mikej

1
สิ่งที่ต้องทำ.selectคือ.rejectถ้านั่นทำให้โค้ดของคุณดูงี่เง่ายิ่งขึ้น
Joe Atzberger

#sliceวิธีการทำงานสำหรับฉันใน 2.5.3 ได้โดยไม่ต้อง ActiveSupport
graywolf

305

ใน Ruby Hash # select เป็นตัวเลือกที่ถูกต้อง หากคุณทำงานกับ Rails คุณสามารถใช้ Hash # slice และ Hash # slice! เช่น (ราง 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
ระวังรหัสที่ผ่านการคัดสรรแล้ว .. h1 = {'a' => 1,: b => 2} จะส่งคืนเฉพาะ {: b => 2} ด้วย h1.slice (: a
,:

ทางออกที่ดีเลิศ ฉันใช้สิ่งนี้ในการส่งคืนผลลัพธ์เป็น rspec ซึ่งฉันต้องการแยกค่าแฮชภายในแฮช `` `test = {: a => 1,: b => 2, c => {: A => 1,: B => 2}} ทางออก ==> test.c.slice (: B)` ` `
PriyankaK

แทนที่ Rails โดย ActiveSupport และนี่ก็สมบูรณ์แบบ;)
Geoffroy

5
สิ่งนี้ไม่ตอบคำถามในขณะที่เขาต้องการวิธีการแยกค่าสำหรับคีย์ที่เป็น "choice + int" โซลูชันของคุณสามารถแยกคีย์ที่ทราบล่วงหน้าเท่านั้น
metakungfu

46

วิธีที่ง่ายที่สุดคือการรวมgem 'activesupport'(หรือgem 'active_support')

จากนั้นในชั้นเรียนของคุณคุณจะต้อง

require 'active_support/core_ext/hash/slice'

และเพื่อโทร

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

ฉันเชื่อว่ามันไม่คุ้มค่าที่จะประกาศฟังก์ชั่นอื่น ๆ ที่อาจมีข้อบกพร่องและควรใช้วิธีการที่ได้รับการปรับแต่งในช่วงสองสามปีที่ผ่านมา


Activerecord ไม่จำเป็นสำหรับสิ่งนี้อย่างน้อยใน 2.5
graywolf

13

วิธีที่ง่ายที่สุดคือการรวม gem 'activesupport' (หรือ gem 'active_support')

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


4
โปรดเพิ่มรายละเอียดเพิ่มเติมในคำตอบของคุณ
Mohit Jain

13

หากคุณทำงานกับรางและคุณมีกุญแจในรายการแยกต่างหากคุณสามารถใช้*สัญลักษณ์:

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

ตามคำตอบอื่น ๆ ที่ระบุไว้คุณสามารถใช้slice!เพื่อปรับเปลี่ยนแฮช (และส่งคืนคีย์ / ค่าที่ถูกลบ)


9

นี่คือหนึ่งบรรทัดเพื่อแก้ปัญหาต้นฉบับทั้งหมด:

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

แต่วิธีแก้ปัญหาข้างต้นส่วนใหญ่กำลังแก้ไขกรณีที่คุณจำเป็นต้องรู้กุญแจล่วงหน้าใช้งานsliceหรือใช้regexp แบบง่าย ๆ

นี่เป็นอีกวิธีการหนึ่งที่ใช้งานได้ง่ายและซับซ้อนยิ่งขึ้น

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

ตอนนี้ไม่เพียง แต่จะช่วยให้ตรรกะที่ซับซ้อนมากขึ้นในการจับคู่คีย์หรือค่า แต่ยังง่ายต่อการทดสอบและคุณสามารถสลับตรรกะการจับคู่ที่รันไทม์

อดีตเพื่อแก้ไขปัญหาเดิม:

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
ฉันชอบ matcher จริงๆ
Calin


6

หากคุณต้องการแฮชที่เหลือ:

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

หรือถ้าคุณต้องการปุ่ม:

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

ขึ้นอยู่กับว่ามีตัวเลือก X จำนวนเท่าใดและไม่เกี่ยวข้อง X ที่คุณเลือกหรือ delete_if อาจดีกว่า หากคุณมีตัวเลือกเพิ่มเติม X delete_if จะดีกว่า
ayckoster

6

ใส่สิ่งนี้ใน initializer

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

จากนั้นคุณสามารถทำได้

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

หรือ

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


4

สำหรับคำถามโบนัส:

  1. หากคุณมีเอาต์พุตจาก#selectวิธีการเช่นนี้ (รายการอาร์เรย์ 2 องค์ประกอบ):

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

    จากนั้นก็นำผลลัพธ์นี้ไปใช้และดำเนินการ:

    filtered_params.join("\t")
    # or if you want only values instead of pairs key-value
    filtered_params.map(&:last).join("\t")
  2. หากคุณมีผลลัพธ์จาก#delete_ifวิธีการเช่นนี้ (แฮ):

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

    แล้ว:

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

โซลูชั่นที่ยอดเยี่ยม คำถามหนึ่ง: filtered_params.map (&: ล่าสุด) .join ("\ t") สั้นสำหรับ filtered_params.map (| i | i.last) .join ("\ t") หรือไม่ ถ้าเป็นเช่นนั้น 'สุดท้าย' คืออะไร? ฉันสามารถใช้ &: value เพื่อรับค่าแฮชได้หรือไม่
ดีเร็ก

mapใช้บล็อกเนื่องจากเป็นอาร์กิวเมนต์ คุณสามารถสร้างบล็อกเกี่ยวกับการบินที่มีการก่อสร้างซึ่งจะสร้างบล็อก&:method {|v| v.method }ในกรณีของฉัน&:lastถูกเรียกว่าอาเรย์ (อาเรย์ 2 องค์ประกอบ) อาร์กิวเมนต์ หากคุณต้องการเล่นกับมันก่อนอื่นให้ตรวจสอบว่าคุณได้รับการโต้แย้งอะไรในบล็อก (รับ 1 อาร์กิวเมนต์อย่าแยกมันออกเป็นหลายอาร์กิวเมนต์!) และลองหาวิธี 1 วิธี (คุณไม่สามารถเชื่อมโยงวิธีการด้วย&:method) ซึ่งจะคืนสิ่งที่คุณต้องการ ต้องการ. หากเป็นไปได้คุณสามารถใช้ทางลัดได้หากไม่เป็นเช่นนั้นคุณต้องใช้การบล็อกแบบเต็ม
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

ฉันมีปัญหาที่คล้ายกันในกรณีของฉันการแก้ปัญหาคือหนึ่งซับซึ่งทำงานได้แม้ว่าคีย์ไม่ได้เป็นสัญลักษณ์ แต่คุณต้องมีคีย์เกณฑ์ในอาร์เรย์

criteria_array = [:choice1, :choice2]

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

ตัวอย่างอื่น

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"}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.