Deleting a specific element from a nested hash
Asked Answered
F

7

27

I am trying to work with a nested hash. I have a deck of cards represented as follows:

deck_of_cards = {
:hearts => {:two => 2, :three => 3, :four => 4, :five => 5, :six => 6, :seven => 7, :eight => 8, :nine => 9, :ten => 10, :jack => 10, 
            :queen => 10, :king => 10, :ace => 11},
:spades => {:two => 2, :three => 3, :four => 4, :five => 5, :six => 6, :seven => 7, :eight => 8, :nine => 9, :ten => 10, :jack => 10, 
            :queen => 10, :king => 10, :ace => 11},
:clubs => {:two => 2, :three => 3, :four => 4, :five => 5, :six => 6, :seven => 7, :eight => 8, :nine => 9, :ten => 10, :jack => 10, 
            :queen => 10, :king => 10, :ace => 11},
:diamonds => {:two => 2, :three => 3, :four => 4, :five => 5, :six => 6, :seven => 7, :eight => 8, :nine => 9, :ten => 10, :jack => 10, 
            :queen => 10, :king => 10, :ace => 11}
}

My goal is to be able to remove one specific card from the deck and return the deck of cards without that specific card. Would anyone be able to help me on how to iterate through the hash and remove a card like the two of clubs?

deck_of_cards[:two][:clubs]

This code works to remove a suit of cards, but I cant figure out how to remove a specific card

deck_of_cards.delete_if {|k, v| k == :spades}
Fleabite answered 20/6, 2012 at 17:48 Comment(0)
J
47

Just do this:

deck_of_cards[:clubs].delete(:two)
Jeremiad answered 20/6, 2012 at 18:2 Comment(1)
Any way to retrieve the new element entirely? Because this code returns the deleted element.Mesne
S
7

You can remove an element and return the original hash also using tap function like this

deck_of_cards.tap{|d| 
  d[:hearts].tap{|h| 
    h.delete(:two)
  }
}

this will return the deck_if_cards hash without :two key

you can do it in one line also

    deck_of_cards.tap{|d| d[:hearts].tap{|h| h.delete("two")}}
Shabbygenteel answered 10/12, 2014 at 8:53 Comment(2)
I think your "two" supposed to be :two?Bloodsucker
tap can certainly be helpful in some situations, but you don't need to nest it: deck_of_cards.tap{|d| d[:hearts].delete(:two)} is fineTrabzon
B
4

Use .tap

deck_of_cards.tap{ |deck_of_cards| deck_of_cards[:hearts].delete(:two) }
#=> { 
#     :hearts=>{:three=>3, :four=>4, :five=>5, :six=>6, :seven=>7, :eight=>8, :nine=>9, :ten=>10, :jack=>10, :queen=>10, :king=>10, :ace=>11}, 
#     :spades=>{:two=>2, :three=>3, :four=>4, :five=>5, :six=>6, :seven=>7, :eight=>8, :nine=>9, :ten=>10, :jack=>10, :queen=>10, :king=>10, :ace=>11}, 
#     :clubs=>{:two=>2, :three=>3, :four=>4, :five=>5, :six=>6, :seven=>7, :eight=>8, :nine=>9, :ten=>10, :jack=>10, :queen=>10, :king=>10, :ace=>11}, 
#     :diamonds=>{:two=>2, :three=>3, :four=>4, :five=>5, :six=>6, :seven=>7, :eight=>8, :nine=>9, :ten=>10, :jack=>10, :queen=>10, :king=>10, :ace=>11} 
#   }

Has the benefit of returning the full hash instead of just the deleted value in an elegant way.

Bloodsucker answered 17/3, 2018 at 16:44 Comment(0)
C
1

I did write this concern as an initializer in my app to add a method called delete_nested_key to the Hash object. It deletes a nested key of the hash. You have to pass the key_path as an array (just a list of the keys to traverse to go to the one you want to delete).

It seems to work fine, but I just wrote it, so it might have issues.

class Hash
  module NestedKeyDeletion
    extend ActiveSupport::Concern

    included do
      def deleted_nested_key!(key_path)
        nested_hash = fetch_most_inner_hash(key_path)
        nested_hash.delete(key_path.last)

        self
      end

      private

      def fetch_most_inner_hash(key_path)
        nested_hash = self

        key_path.each_with_index do |key, index|
          return  nested_hash if index == key_path.size - 1
          nested_hash = nested_hash.fetch(key)
        end
      end
    end
  end
end

Hash.include(Hash::NestedKeyDeletion)

Then you can use it like this:

[1] pry(main)> x = { x: { y: 2} }
=> {:x=>{:y=>2}}
[2] pry(main)> x.deleted_nested_key!([:x, :y])
=> {:x=>{}}
[3] pry(main)>

Best regards, Daniel.

Carmelo answered 22/6, 2021 at 14:23 Comment(0)
O
0

You have a hash inside a hash, so you can do this:

deck_of_cards.each {|k,v| v.delete(:two) if k == :clubs}

You use each to iterate through keys and values, and make a condition inside the block to delete the specific value on the inner hash.

Orderly answered 20/6, 2012 at 17:59 Comment(2)
Underrated solution!Sprinkler
Why would you iterate the hash when you can access the subhash through the key?Ory
C
0

You would have to is something like this:

def remove_card deck, suit, number
  # do a deep clone
  new_deck = {}
  deck.each { |k, v| new_deck[k] = v.dup }

  # remove the card
  new_deck[suit] = new_deck[suit].reject { |k, v| k == number }

  new_deck
end

It might be better to represent your deck as an array of pairs, like this:

[ [:hearts, :two], [:hearts, :three], ... ]

Then you can just go:

def remove_card deck, suit, number
  deck.reject { |(s, n)| n == number and s == suit }
end
Clearing answered 20/6, 2012 at 18:1 Comment(0)
U
0

I think this method will do the job for you

def nested_delete(card:, deck:)
  each do |hash_key, hash_value|
    if hash_key.to_s.eql?(deck)
      self[hash_key].delete(card.to_sym)
    end
  end
  self
end

Say you want to delete card 'six' from deck 'hearts' All you need to do is

deck_of_cards.nested_delete(card: 'six', deck: 'hearts')
Undercut answered 19/11, 2018 at 12:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.