Ruby: Cách dễ nhất để lọc các phím Hash?


225

Tôi có một hàm băm trông giống như thế này:

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

Và tôi muốn một cách đơn giản để từ chối tất cả các khóa không phải là sự lựa chọn + int. Nó có thể là sự lựa chọn1, hoặc sự lựa chọn1 thông qua sự lựa chọn10. Nó thay đổi.

Làm thế nào để tôi chọn ra các phím chỉ với lựa chọn từ và một chữ số hoặc chữ số sau chúng?

Tặng kem:

Biến hàm băm thành một chuỗi có tab (\ t) làm dấu phân cách. Tôi đã làm điều này, nhưng phải mất một vài dòng mã. Thông thường bậc thầy Rubician có thể làm điều đó trong một hoặc nhiều dòng.


Về câu hỏi phần thưởng, bạn có thể làm rõ những gì bạn muốn chuỗi trông như thế nào.
mikej

Chắc chắn, hàm băm ví dụ trên sẽ mang lại: "Ôi, một chuỗi khác \ tEven thêm chuỗi \ tBut chờ" (không phải ở cuối chuỗi, chỉ giữa chúng)
Derek

Chuỗi ví dụ của bạn đã bị cắt trong bình luận. Bạn có thể sử dụng liên kết chỉnh sửa để chỉnh sửa câu hỏi của bạn và thêm ví dụ vào đó. Bạn cũng có thể đăng viên ruby ​​mà bạn nghĩ ra cho đến nay là một ví dụ về những gì bạn muốn đạt được.
mikej

2
bản sao có thể có của Bộ lọc Ruby Hash
jbochi

Câu trả lời:


281

Chỉnh sửa thành câu trả lời gốc : Mặc dù đây là câu trả lời (tại thời điểm nhận xét này) là câu trả lời được chọn, phiên bản gốc của câu trả lời này đã lỗi thời.

Tôi đang thêm một bản cập nhật ở đây để giúp những người khác tránh bị lạc hướng bởi câu trả lời này giống như tôi đã làm.

Như câu trả lời khác đề cập, Ruby> = 2.5 đã thêm Hash#slicephương thức mà trước đây chỉ có trong Rails.

Thí dụ:

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

Kết thúc chỉnh sửa. Câu trả lời ban đầu mà tôi đoán sẽ hữu ích nếu bạn sử dụng Ruby <2.5 mà không có Rails, mặc dù tôi tưởng tượng trường hợp đó khá hiếm ở thời điểm này.


Nếu bạn đang sử dụng Ruby, bạn có thể sử dụng selectphương pháp này. Bạn sẽ cần phải chuyển đổi khóa từ Biểu tượng thành Chuỗi để thực hiện khớp regex. Điều này sẽ cung cấp cho bạn một Hash mới chỉ với các lựa chọn trong đó.

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

hoặc bạn có thể sử dụng delete_ifvà sửa đổi Hash hiện tại, vd

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

hoặc nếu đó chỉ là các khóa và không phải là các giá trị bạn muốn thì bạn có thể làm:

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

và điều này sẽ cung cấp cho chỉ một mảng các phím, vd [:choice1, :choice2, :choice3]


1
Thông minh! Một khi tôi thấy nó, điều này thật đơn giản! Cảm ơn rất nhiều, và cảm ơn những người khác đã dành thời gian để trả lời.
Derek

2
Các biểu tượng có thể được phân tích cú pháp bằng các biểu thức thông thường hiện nay IIRC.
Andrew Grimm

@Andrew, tôi nghĩ bạn đúng. Tôi đã vào ngày 1.8.7 khi tôi đang kiểm tra câu trả lời cho câu hỏi này.
mikej

1
Đối tác .select.reject, nếu điều đó làm cho mã của bạn thành ngữ hơn.
Joe Atzberger

Các #slicecách làm việc cho tôi trên 2.5.3 ngay cả khi không activesupport.
Graywolf

305

Trong Ruby, chọn Hash # là một tùy chọn đúng. Nếu bạn làm việc với Rails, bạn có thể sử dụng Hash # lát và Hash # lát!. ví dụ: (đường ray 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
coi chừng các khóa được xâu chuỗi..h1 = {'a' => 1 ,: b => 2} sẽ chỉ trả về {: b => 2} với h1.slice (: a ,: b)
davidcollom

Giải pháp tuyệt vời. Tôi đã sử dụng điều này trong việc trả về kết quả trong rspec, nơi tôi muốn phân tích giá trị của hàm băm trong hàm băm. `` `test = {: a => 1 ,: b => 2, c => {: A => 1 ,: B => 2}} giải pháp ==> test.c.slice (: B)` ` `
PriyankaK

Thay thế Rails bằng ActiveSupport và điều này là hoàn hảo;)
Geoffroy

5
Tuy nhiên, điều này không trả lời câu hỏi, vì anh ta muốn một cách để trích xuất các giá trị cho các khóa là "sự lựa chọn + int". Giải pháp của bạn chỉ có thể trích xuất các khóa được biết trước thời hạn.
metakungfu

46

Cách dễ nhất là bao gồm gem 'activesupport'(hoặc gem 'active_support').

Sau đó, trong lớp học của bạn, bạn chỉ cần

require 'active_support/core_ext/hash/slice'

và để gọi

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

Tôi tin rằng không đáng để tuyên bố các chức năng khác có thể có lỗi và tốt hơn là sử dụng một phương pháp đã được điều chỉnh trong vài năm qua.


Activerecord không cần thiết cho điều này ít nhất là trên 2.5.
Graywolf

13

Cách dễ nhất là bao gồm đá quý 'activesupport' (hoặc đá quý 'active_support').

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


4
Vui lòng thêm chi tiết để trả lời của bạn.
Mohit Jain

13

Nếu bạn làm việc với đường ray và bạn có các phím trong một danh sách riêng, bạn có thể sử dụng *ký hiệu:

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

Như các câu trả lời khác đã nêu, bạn cũng có thể sử dụng slice!để sửa đổi hàm băm tại chỗ (và trả về khóa / giá trị bị xóa).


9

Đây là một dòng để giải quyết câu hỏi gốc hoàn chỉnh:

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

Nhưng hầu hết các giải pháp ở trên đang giải quyết một trường hợp mà bạn cần biết các khóa trước, sử dụng slicehoặc regrec đơn giản.

Đây là một cách tiếp cận khác hoạt động cho các trường hợp sử dụng đơn giản và phức tạp hơn, có thể hoán đổi khi chạy

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

Bây giờ không chỉ điều này cho phép logic phức tạp hơn trong việc khớp các khóa hoặc các giá trị, mà còn dễ kiểm tra hơn và bạn có thể trao đổi logic khớp khi chạy.

Ex để giải quyết vấn đề ban đầu:

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
Tôi thực sự thích người mai mối
Calin


6

Nếu bạn muốn băm còn lại:

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

hoặc nếu bạn chỉ muốn các phím:

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

Tùy thuộc vào số lượng sự lựa chọnX và không liên quanX mà bạn đã chọn HOẶC xóa_if có thể tốt hơn. Nếu bạn có nhiều sự lựa chọn hơn thì xóa_if là tốt hơn.
ayckoster

6

Đặt cái này trong bộ khởi tạo

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

Sau đó bạn có thể làm

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

hoặc là

{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

Đối với câu hỏi tiền thưởng:

  1. Nếu bạn có đầu ra từ #selectphương thức như thế này (danh sách các mảng 2 phần tử):

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

    sau đó chỉ cần lấy kết quả này và thực hiện:

    filtered_params.join("\t")
    # or if you want only values instead of pairs key-value
    filtered_params.map(&:last).join("\t")
  2. Nếu bạn có đầu ra từ #delete_ifphương thức như thế này (hàm băm):

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

    sau đó:

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

Giải pháp tuyệt vời. Một câu hỏi: Được lọc_params.map (&: last) .join ("\ t") là viết tắt của filtered_params.map (| i | i.last) .join ("\ t")? Nếu vậy, 'cuối cùng' là gì? Tôi có thể sử dụng &: value để lấy giá trị của hàm băm không?
Derek

maplấy khối làm đối số của nó. Bạn có thể tạo khối khi đang bay, với &:methodcấu trúc, sẽ tạo khối {|v| v.method }. Trong trường hợp của tôi &:lastđã được gọi trên đối số mảng (mảng 2 phần tử). Nếu bạn muốn chơi với nó, trước tiên hãy kiểm tra xem bạn đang nhận được đối số nào trong khối (nhận 1 đối số, không giải mã nó thành nhiều đối số!) Và cố gắng tìm 1 phương thức (bạn không thể xâu chuỗi các phương thức với &:method) sẽ trả về cho bạn những gì bạn muốn Nếu có thể, thì bạn có thể sử dụng phím tắt, nếu không, thì bạn cần khối đầy đủ
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

Tôi đã gặp một vấn đề tương tự, trong trường hợp của tôi, giải pháp là một lớp lót hoạt động ngay cả khi các khóa không có ký hiệu, nhưng bạn cần phải có các khóa tiêu chí trong một mảng

criteria_array = [:choice1, :choice2]

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

Một vi dụ khac

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"}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.