0

I have the below array of hashes. I want to add a new key,value pair to "hashes" which are in "all" array. Is there any better way of looping through, than what I am doing currently?

stack = {
  "all": [
    "mango",
    "apple",
    "banana",
    "grapes"
  ],
  "mango": {
    "TYPE": "test",
    "MAX_SIZE": 50,
    "REGION": "us-east-1"
  },
  "apple": {
    "TYPE": "dev",
    "MAX_SIZE": 55,
    "REGION": "us-east-1"
  },
  "banana": {
    "TYPE": "test",
    "MAX_SIZE": 60,
    "REGION": "us-east-1"
  },
  "grapes": {
    "TYPE": "dev",
    "MAX_SIZE": 80,
    "REGION": "us-east-1"
  },
  "types": [
    "dev",
    "test"
  ]
}

My code:

  stack['all'].each do |fruit|
   stack[fruit].each do |fruit_name|
     stack[fruit_name]['COUNT'] = stack[fruit_name]['MAX_SIZE'] * 2
   end
  end

Expected output:

stack = {
  "all": [
    "mango",
    "apple",
    "banana",
    "grapes"
  ],
  "mango": {
    "TYPE": "test",
    "MAX_SIZE": 50,
    "REGION": "us-east-1",
    "COUNT" : 100
  },
  "apple": {
    "TYPE": "dev",
    "MAX_SIZE": 55,
    "REGION": "us-east-1",
    "COUNT" : 110
  },
  "banana": {
    "TYPE": "test",
    "MAX_SIZE": 60,
    "REGION": "us-east-1",
    "COUNT" : 120
  },
  "grapes": {
    "TYPE": "dev",
    "MAX_SIZE": 80,
    "REGION": "us-east-1",
    "COUNT" : 160
  },
  "types": [
    "dev",
    "test"
  ]
}
4
  • Did you test your code? It contains several errors now. Some of them are more like typos (like trying to access symbol keys using string values) but the internal iteration is logically broken. stack[fruit] (or rather stack[fruit.to_sym)) is a hash, so iterating over it using each will yield NOT fruit_name but something very different :) Commented Sep 25, 2020 at 16:05
  • I'm guessing you are new to Ruby. Ruby does not use : to specify key/value-pairs. : is used in combination with a valid identifier to create a symbol. If you want to specify key/value-pairs use =>. eg. "TYPE" => "test" using "TYPE": "test" is a shorthand for :"TYPE" => "test" or even shorter :TYPE => "test" where :TYPE is a symbol, not a string. See: Ruby literals - Hashes Commented Sep 25, 2020 at 16:23
  • I am newbie to rubie and trying to learn. How can I iterate , if not through each Commented Sep 25, 2020 at 16:24
  • If stack[:all] includes "mango", as it does in the example, does stack necessarily have a key :mango? Commented Sep 25, 2020 at 18:25

2 Answers 2

2

There is no need for the second loop. The following does what you want:

keys = stack[:all].map(&:to_sym)
keys.each do |key|
  stack[key][:COUNT] = stack[key][:MAX_SIZE] * 2
end

In the above code-block stack[:all] will return an array of keys as strings, .map(&:to_sym) will convert each string in the resulting array into a symbol.


Another way to achieve the same result would be to use either fetch_values or values_at to retrieve an array of values belonging to the provided keys. The difference being that fetch_values raises an exception if a key is missing while values_at returns nil for that key.

fruits = stack.fetch_values(*stack[:all].map(&:to_sym))
fruits.each do |fruit|
  fruit[:COUNT] = fruit[:MAX_SIZE] * 2
end

If you are wondering why there is a * before stack[:all].map(&:to_sym), this is to convert the array into individual arguments. In this context * is called the spat operator.

Sign up to request clarification or add additional context in comments.

4 Comments

Thankyou, but I cannot change stacks[all] to symbols or any other data. I need to work with the existing data as mentioned in the question
Jill, that would be a concern had 3... written stack[:all].map!(&:to_sym), but stack[:all].map(&:to_sym) creates a new array without modifying stack[:all]. 3..., that's gotta be Ruby's nastiest operator.
why are converting the keys to_sym ? In my data, there are not symbols, the value inside keys/fruits are only symbols. Also all is not a symbol
Jill, suppose you define a hash { "a":1, "b c":2, "d"=>3 } #=> {:a=>1, :"b c"=>2, "d"=>3}. For key-value pairs that are defined with the colon form, the key is always treated as a symbol. b c must be put in quotes because of the presence of the space. The quotes around a are redundant. One would normally write { a:1, "b c":2, "d"=>3 }.
1

You might write the code as follows.

stack[:all].each do |k|
  h = stack[k.to_sym]
  h[:COUNT] = 2*h[:MAX_SIZE] unless h.nil?
end

When, for example, `k = "mango",

h #=> h={:TYPE=>"test", :MAX_SIZE=>50, :REGION=>"us-east-1", :COUNT=>100}

I've defined the local variable h for three reasons:

  • it simplifies the code by avoiding multiple references to stack[k.to_sym]
  • when debugging it may may be helpful to be able to examine h
  • it makes the code more readable

Note that h merely holds an existing hash; it does not create a copy of that hash, so it has a neglibile effect on memory requirements.

The technique of defining local variables to hold objects that are parts of other objects is especially useful for more complex objects. Suppose, for example, we had the hash

 hash = {
   cat: { sound: "purr", lives: 9 },
   dog: { sound: "woof", toys: ["ball", "rope"] }
 }

Now suppose we wish to add a dog toy

new_toy = "frisbee"

if it is not already present in the array

hash[:dog][:toys]

We could write

hash[:dog][:toys] << new_toy unless hash[:dog][:toys].include?(new_toy)
  #=> ["ball", "rope", "frisbee"] 
hash
  #=> {:cat=>{:sound=>"purr", :lives=>9},
  #    :dog=>{:sound=>"woof", :toys=>["ball", "rope", "frisbee"]}} 

Alternatively, we could write

dog_hash = hash[:dog]
  #=> {:sound=>"woof", :toys=>["ball", "rope"]} 
dog_toys_arr = dog_hash[:toys]
  #=> ["ball", "rope"] 
dog_toys_arr << new_toy unless dog_toys_arr.include?(new_toy)  
  #=> ["ball", "rope", "frisbee"] 
hash
  #=> {:cat=>{:sound=>"purr", :lives=>9},
  #    :dog=>{:sound=>"woof", :toys=>["ball", "rope", "frisbee"]}} 
 

Not only does the latter snippet display intermediate results, it probably is a wash with the first snippet in terms of execution speed and storage requirements and arguably is more readable. It also cuts down on careless mistakes such as

hash[:dog][:toys] << new_toy unless hash[:dog][:toy].include?(new_toy)

If one element of stack[:all] were, for example, "pineapple", stack[:pineapple] #=> nil since stack has no key :pineapple. If, however, stack contained the key-value pair

nil=>{ sound: "woof", toys: ["ball", "rope"] }

that would become problem. Far-fetched? Maybe, but it is perhaps good practice--in part for readability--to avoid the assumption that h[k] #=> nil means h has no key k; instead, use if h.key?(k). For example:

stack[:all].each do |k|
  key = k.to_sym
  if stack.key?(key)
    h = stack[key]
    h[:COUNT] = 2*h[:MAX_SIZE]
  end
end

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.