Як видалити ключ з Hash і отримати решту хешів у Ruby / Rails?


560

Щоб додати нову пару до Hash, я роблю:

{:a => 1, :b => 2}.merge!({:c => 3})   #=> {:a => 1, :b => 2, :c => 3}

Чи є подібний спосіб видалити ключ з Hash?

Це працює:

{:a => 1, :b => 2}.reject! { |k| k == :a }   #=> {:b => 2}

але я сподівався б мати щось на кшталт:

{:a => 1, :b => 2}.delete!(:a)   #=> {:b => 2}

Важливо, що поверненим значенням буде хеш, що залишився, щоб я міг робити такі речі, як:

foo(my_hash.reject! { |k| k == my_key })

в один рядок.


1
Ви завжди можете продовжити (відкрити під час виконання) вбудований Hash, щоб додати цей спеціальний метод, якщо він вам справді потрібен.
дбрісон

Відповіді:


750

У Rails є окрім / крім! метод, який повертає хеш із видаленими ключами. Якщо ви вже використовуєте Rails, немає сенсу створювати власну версію цього.

class Hash
  # Returns a hash that includes everything but the given keys.
  #   hash = { a: true, b: false, c: nil}
  #   hash.except(:c) # => { a: true, b: false}
  #   hash # => { a: true, b: false, c: nil}
  #
  # This is useful for limiting a set of parameters to everything but a few known toggles:
  #   @person.update(params[:person].except(:admin))
  def except(*keys)
    dup.except!(*keys)
  end

  # Replaces the hash without the given keys.
  #   hash = { a: true, b: false, c: nil}
  #   hash.except!(:c) # => { a: true, b: false}
  #   hash # => { a: true, b: false }
  def except!(*keys)
    keys.each { |key| delete(key) }
    self
  end
end

51
Вам не доведеться використовувати повний стек Rails. Ви можете включити ActiveSupport у будь-яку програму Ruby.
Fryie

10
Щоб додати відповідь Фрі, вам навіть не потрібно завантажувати весь ActiveSupport; Ви можете просто включити їх потімrequire "active_support/core_ext/hash/except"
GMA

занадто пізно для редагування: я мав на увазі "включити дорогоцінний камінь", а не "включити їх"
GMA

@GMA: коли ваша п'ять хвилин редагування закінчується, ви завжди можете скопіювати, видалити, змінити та повторно поставити коментар.
іконоборство

211

Oneliner звичайний рубін, він працює лише з рубіном> 1.9.x:

1.9.3p0 :002 > h = {:a => 1, :b => 2}
 => {:a=>1, :b=>2} 
1.9.3p0 :003 > h.tap { |hs| hs.delete(:a) }
 => {:b=>2} 

Метод Tap завжди повертає об'єкт, на який викликається ...

В іншому випадку, якщо вам потрібно active_support/core_ext/hash(що автоматично вимагається в кожній програмі Rails), ви можете використовувати один із наведених нижче способів залежно від ваших потреб:

  ~  irb
1.9.3p125 :001 > require 'active_support/core_ext/hash' => true 
1.9.3p125 :002 > h = {:a => 1, :b => 2, :c => 3}
 => {:a=>1, :b=>2, :c=>3} 
1.9.3p125 :003 > h.except(:a)
 => {:b=>2, :c=>3} 
1.9.3p125 :004 > h.slice(:a)
 => {:a=>1} 

за винятком використовує підхід у чорному списку, тому він видаляє всі ключі, перелічені як аргументи, тоді як фрагмент використовує білий підхід, тому він видаляє всі ключі, які не вказані як аргументи. Існує також версія bang того методу ( except!і slice!), який модифікує даний хеш, але їх повернене значення різне, вони обидва повертають хеш. Він представляє видалені ключі для slice!та ключі, які зберігаються для except!:

1.9.3p125 :011 > {:a => 1, :b => 2, :c => 3}.except!(:a)
 => {:b=>2, :c=>3} 
1.9.3p125 :012 > {:a => 1, :b => 2, :c => 3}.slice!(:a)
 => {:b=>2, :c=>3} 

18
+1 Варто зазначити, що цей метод є руйнівним h. Hash#exceptне змінить оригінальний хеш.
Дякую

3
Використовуйте, h.dup.tap { |hs| hs.delete(:a) }щоб не змінювати оригінальний хеш.
Магікод

181

Чому б просто не використовувати:

hash.delete(key)

2
@dbryson: Я згоден, що іноді цього не варто. Я просто дивуюся , чому є merge, merge!, delete, але немає detele!...
Міша Moroshko

1
якщо вам справді це потрібно як один лайнер:foo(hash.delete(key) || hash)
Берт Ґетхалс

13
Було б більш послідовно з конвенціями Рубі , якщо deleteнічого НЕ змінити його параметри , і якщо delete!існує , і зробив зміни його параметрів.
Девід Дж.

60
Це не повертає решту хешів, як зазначено в запитанні, він поверне значення, пов’язане з видаленим ключем.
MhdSyrwan

1
delete повертає ключ, але він також змінює хеш. Щодо того, чому немає видалення !, я здогадуюсь, що семантично не має сенсу викликати видалення на чомусь, а фактично не видаляти його. виклик hash.delete () на відміну від hash.delete! () був би не-оп.
яєчники

85

Існує багато способів видалити ключ із хеша та отримати решту хешів у Ruby.

  1. .slice=> Він поверне вибрані ключі та не видалить їх із початкового хеша. Використовуйте, slice!якщо ви хочете назавжди видалити ключі, використовуйте прості slice.

    2.2.2 :074 > hash = {"one"=>1, "two"=>2, "three"=>3}
     => {"one"=>1, "two"=>2, "three"=>3} 
    2.2.2 :075 > hash.slice("one","two")
     => {"one"=>1, "two"=>2} 
    2.2.2 :076 > hash
     => {"one"=>1, "two"=>2, "three"=>3} 
    
  2. .delete => Він видалить вибрані ключі з вихідного хешу (він може приймати лише один ключ і не більше одного).

    2.2.2 :094 > hash = {"one"=>1, "two"=>2, "three"=>3}
     => {"one"=>1, "two"=>2, "three"=>3} 
    2.2.2 :095 > hash.delete("one")
     => 1 
    2.2.2 :096 > hash
     => {"two"=>2, "three"=>3} 
    
  3. .except=> Він поверне решта ключів, але нічого не видалить з оригінального хеша. Використовуйте, except!якщо ви хочете назавжди видалити ключі, використовуйте прості except.

    2.2.2 :097 > hash = {"one"=>1, "two"=>2, "three"=>3}
     => {"one"=>1, "two"=>2, "three"=>3} 
    2.2.2 :098 > hash.except("one","two")
     => {"three"=>3} 
    2.2.2 :099 > hash
     => {"one"=>1, "two"=>2, "three"=>3}         
    
  4. .delete_if=> У випадку, якщо вам потрібно видалити ключ на основі значення. Це, очевидно, видалить відповідні ключі з оригінального хеша.

    2.2.2 :115 > hash = {"one"=>1, "two"=>2, "three"=>3, "one_again"=>1}
     => {"one"=>1, "two"=>2, "three"=>3, "one_again"=>1} 
    2.2.2 :116 > value = 1
     => 1 
    2.2.2 :117 > hash.delete_if { |k,v| v == value }
     => {"two"=>2, "three"=>3} 
    2.2.2 :118 > hash
     => {"two"=>2, "three"=>3} 
    
  5. .compact=> Він використовується для видалення всіх nilзначень з хеша. Використовуйте, compact!якщо ви хочете nilназавжди видалити значення, використовуйте просто compact.

    2.2.2 :119 > hash = {"one"=>1, "two"=>2, "three"=>3, "nothing"=>nil, "no_value"=>nil}
     => {"one"=>1, "two"=>2, "three"=>3, "nothing"=>nil, "no_value"=>nil} 
    2.2.2 :120 > hash.compact
     => {"one"=>1, "two"=>2, "three"=>3}
    

Результати на основі Ruby 2.2.2.


16
sliceі exceptдодаються за допомогою ActiveSupport::CoreExtensions::Hash. Вони не входять до складу ядра Ruby. Ними можна скористатисяrequire 'active_support/core_ext/hash'
Madis Nõmme

3
Оскільки Ruby 2.5 Hash#sliceзнаходиться у стандартній бібліотеці. ruby-doc.org/core-2.5.0/Hash.html#method-i-slice Так !
Madis Nõmme

38

Якщо ви хочете використовувати чистий Ruby (без Rails), не хочете створювати методи розширення (можливо, вам це потрібно лише в одному або двох місцях, і не хочете забруднювати простір імен тоннами методів) і не хочете редагувати хеш на місці (тобто ви любитель функціонального програмування, як я), ви можете "вибрати":

>> x = {:a => 1, :b => 2, :c => 3}
=> {:a=>1, :b=>2, :c=>3}
>> x.select{|x| x != :a}
=> {:b=>2, :c=>3}
>> x.select{|x| ![:a, :b].include?(x)}
=> {:c=>3}
>> x
=> {:a=>1, :b=>2, :c=>3}

30
#in lib/core_extensions.rb
class Hash
  #pass single or array of keys, which will be removed, returning the remaining hash
  def remove!(*keys)
    keys.each{|key| self.delete(key) }
    self
  end

  #non-destructive version
  def remove(*keys)
    self.dup.remove!(*keys)
  end
end

#in config/initializers/app_environment.rb (or anywhere in config/initializers)
require 'core_extensions'

Я налаштував це так, щоб .remove повертав копію хешу з видаленими ключами, а видаляв! змінює сам хеш. Це відповідає конвенціям про рубіни. наприклад, з консолі

>> hash = {:a => 1, :b => 2}
=> {:b=>2, :a=>1}
>> hash.remove(:a)
=> {:b=>2}
>> hash
=> {:b=>2, :a=>1}
>> hash.remove!(:a)
=> {:b=>2}
>> hash
=> {:b=>2}
>> hash.remove!(:a, :b)
=> {}

26

Ви можете використовувати except!з facetsдорогоцінного каміння:

>> require 'facets' # or require 'facets/hash/except'
=> true
>> {:a => 1, :b => 2}.except(:a)
=> {:b=>2}

Оригінальний хеш не змінюється.

EDIT: як каже Рассел, у фасета є деякі приховані проблеми і не повністю сумісні з API з ActiveSupport. З іншого боку ActiveSupport не настільки повно, як грані. Врешті-решт, я б скористався AS та впустив крайові регістри у ваш код.


Просто require 'facets/hash/except'і їх не є "питаннями" (не впевнений, які б вони були проблеми, крім 100% AS API). Якщо ви робите проект Rails, використовуючи AS, має сенс, якщо не Facets має значно менший слід.
транс

На сьогодні @trans ActiveSupport має зовсім невеликий слід, і вам можуть знадобитися лише його частини. Так само, як і фасети, але з набагато більшими поглядами на нього (тому я гадаю, що він отримує кращі відгуки).
переписано

19

Замість того, щоб виправляти мавпи або не потрібно включати великі бібліотеки, ви можете використовувати уточнення, якщо ви використовуєте Ruby 2 :

module HashExtensions
  refine Hash do
    def except!(*candidates)
      candidates.each { |candidate| delete(candidate) }
      self
    end

    def except(*candidates)
      dup.remove!(candidates)
    end
  end
end

Ви можете використовувати цю функцію, не впливаючи на інші частини вашої програми або не включаючи великі зовнішні бібліотеки.

class FabulousCode
  using HashExtensions

  def incredible_stuff
    delightful_hash.except(:not_fabulous_key)
  end
end


13

Див. Розділ Ruby on Rails: Видалення кількох хеш-клавіш

hash.delete_if{ |k,| keys_to_delete.include? k }

keys_to_delete.each {| k | hash.delete (k)} набагато швидше для великих наборів даних. виправте мене, якщо не так.
Віньєш Джаявель

@VigneshJayavel, ви праві, але ОП хотів повернути хеш. eachповерне масив.
Накілон

3

Чудово було, якщо delete повернути пару видалення хешу. Я роблю це:

hash = {a: 1, b: 2, c: 3}
{b: hash.delete(:b)} # => {:b=>2}
hash  # => {:a=>1, :c=>3} 

1

Це єдиний спосіб зробити це, але він не дуже читабельний. Рекомендуйте замість цього використовувати два рядки.

use_remaining_hash_for_something(Proc.new { hash.delete(:key); hash }.call)

1
Hash#exceptі Hash#except!вже досить згадувалося. Proc.newВерсія не надто читається , як ви говорите , а також більш складні , ніж use_remaining_hash_for_something(begin hash.delete(:key); hash end). Можливо, просто видаліть цю відповідь.
Майкл Коль

1
Скоротив мою відповідь і усунув те, що вже було сказано. Я тримаю мою відповідь разом із вашим коментарем, оскільки вони відповідають на запитання та дають хороші рекомендації щодо використання.
розпочато

0

Кілька способів видалення ключа в Hash. ви можете використовувати будь-який метод знизу

hash = {a: 1, b: 2, c: 3}
hash.except!(:a) # Will remove *a* and return HASH
hash # Output :- {b: 2, c: 3}

hash = {a: 1, b: 2, c: 3}
hash.delete(:a) # will remove *a* and return 1 if *a* not present than return nil

Стільки способів є, ви можете подивитися на Ruby doc Hash тут .

Дякую


-12

Це також працює: hash[hey] = nil


3
h = {: a => 1,: b => 2,: c => 3}; h [: a] = нуль; h.each {| k, v | ставить k} Не те саме, що: h = {: a => 1,: b => 2,: c => 3}; h.delete (: a); h.each {| k, v | ставить k}
obaqueiro

1
Видалити ключ з хеша - це не те саме, що видалити значення ключа з хеша. Оскільки це може призвести до плутанини людей, краще відповідь видалити.
Себастьян Пальма
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.