Ruby: Найпростіший спосіб фільтрації ключів?


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) як роздільник. Я зробив це, але знадобилося кілька рядків коду. Зазвичай майстри рубіканти можуть це робити в одній чи близько рядках.


Що стосується питання про бонус, чи можете ви уточнити, як ви хотіли б виглядати рядок.
mikej

Звичайно, наведений вище хеш-приклад дасть: "О, дивіться, ще один \ tБільше рядків \ tБудьте чекати" (з не \ 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метод. Вам потрібно буде перетворити ключ із символу в рядок, щоб виконати збіг регулярних виразів. Це дасть вам новий хеш із лише вибором.

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, я думаю, ти маєш рацію. Мені було 1,8,7, коли я тестував відповідь на це питання.
mikej

1
Аналогом .selectє .reject, якщо це робить ваш код більш ідіоматичних.
Джо Атцбергер

#sliceСпосіб працює для мене на 2.5.3 навіть без ActiveSupport.
сірий вовк

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, де я хотів проаналізувати значення хеша в хеші. `` тест = {: a => 1,: b => 2, c => {: A => 1,: B => 2}} рішення ==> test.c.slice (: B) `` `
ПриянкаК

Замініть рейки на ActiveSupport, і це ідеально;)
Джеффрой

5
це не дає відповіді на запитання, оскільки він хоче спосіб витягнути значення для ключів, які є "вибір + 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.
сірий вовк

13

Найпростіший спосіб - включити дорогоцінний камінь '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або простий повторний вираз.

Ось ще один підхід, який працює для простих і складніших випадків використання, який можна змінювати під час виконання

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
Мені дуже подобається матч
Калін


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

Помістіть це в ініціалізатор

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методу (список двоелементних масивів):

    [[: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 (&: last) .join ("\ t") є коротким для filtered_params.map (| i | i.last) .join ("\ t")? Якщо так, то що "останнє"? Чи можу я використовувати &: значення, щоб отримати значення хеша?
Дерек

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"}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.