Ruby: Самый простой способ фильтровать хеш-ключи?

У меня есть хеш, который выглядит примерно так:

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

И я хочу простой способ отклонить все ключи, которые не являются выбором + int. Это может быть выбор1 или выбор1 по выбору10. Он меняется.

Как выделить ключи только с выбором слова и цифрой или цифрами после них?

Bonus:

Поверните хеш в строку с вкладкой (\ t) в качестве разделителя. Я сделал это, но потребовалось несколько строк кода. Обычно мастера-рубики могут делать это в одной или нескольких строках.

Ответ 1

ПРИМЕЧАНИЕ.. Если вы используете Rails, вы можете использовать Hash.slice, как указано lfx_cool в ответе ниже.

Если вы используете Ruby, вы можете использовать метод select. Вам нужно будет преобразовать ключ из символа в строку, чтобы выполнить регулярное выражение. Это даст вам новый хэш только с выбором.

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]

Ответ 2

В Ruby выбор Hash # является правильным вариантом. Если вы работаете с Rails, вы можете использовать кусочек Hash # и 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}

Ответ 3

Самый простой способ - включить 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"}

Я считаю, что не стоит объявлять другие функции, которые могут иметь ошибки, и лучше использовать метод, который был изменен в течение последних нескольких лет.

Ответ 4

Самый простой способ - включить gem 'activesupport' (или gem 'active_support').

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

Ответ 5

Это одна строка для решения полного исходного вопроса:

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

Но большинство решений выше решают случай, когда вам нужно заранее знать ключи, используя slice или простое регулярное выражение.

Вот еще один подход , который работает для простых и более сложных прецедентов, которые можно заменять во время выполнения

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

Теперь не только это позволяет более сложную логику согласовывать ключи или значения, но также легче протестировать, и вы можете поменять логику соответствия во время выполнения.

Ex для решения исходной проблемы:

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"

Ответ 6

Поместите это в инициализатор

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"}

Ответ 7

Если вам нужен оставшийся хэш:

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

или если вы просто хотите использовать клавиши:

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

Ответ 8

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

Ответ 9

С Hash::select:

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

Ответ 10

Что касается бонусного вопроса:

  • Если у вас есть выход из метода #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")
    
  • Если вы используете метод #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")
    

Ответ 11

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"}