I have this method in helper:
def get_hash_keys(hash)
hash.delete_if{|k, v| v.class != Hash}
return hash.keys
end
When I call get_hash_keys from another method in the same helper, it returns a blank array :
def datas_size
sum = 0
new_hash = {title: "This is a test", datas: {firstname: "foo", lastname: "bar"}}
new_hash.each do |k, v|
sum += v.size if get_hash_keys(new_hash).includes? k
end
return sum
end
I tested to change return hash.keys with fixed array, and I get it. Only keys function seems not to work. I also double checked my array in params.
Is there some specifications I ignore working inside helpers ?
Edit for #DRSE :
I start with hash that contains others hashes. I need to know size of each children hash. The point is when I call this function (get_hash_keys) from the views, it fails (return blank array), but from console it works (return keys).
More investigation this morning drive me to conclude may be this is a wrong usage of delete_if. My ununderstood solution is to replace :
def get_hash_keys(hash)
hash.delete_if{|k, v| v.class != Hash}
return hash.keys
end
with
def get_hash_keys(hash)
tmp_hash = hash.clone
tmp_hash.delete_if{|k, v| v.class != Hash}
return tmp_hash.keys
end
Nothing is wrong with hash.keys:
def get_new_keys(hash)
hash.delete_if{|k,v| k == :bar}
return hash.keys
end
get_new_keys({qux: :bar, bar: :baz}) # => [:qux]
This, on the other hand, is busted.
def display_keys(hash)
return "foo " + get_new_keys(hash)
end
Are you expecting an array to be returned? If so, that's not the way to go about it at all. You could do something like:
def display_keys(hash)
return [:foo].concat(get_new_keys(hash))
end
In which case, it'd do something like:
def display_keys(hash)
return [:foo].concat(get_new_keys(hash))
end
display_keys({qux: :bar, bar: :baz}) # => [:foo, :qux]
The .keys method returns and Array of keys in the Hash.
Your question is impossible to answer for two reasons:
You have not included the condition in your delete_if block. My
guess is that you are deleting all keys in the hash, so .keys is
returning [].
You have not described the output you are expecting.
On a side note, your method display_keys does not work in ruby 2.3. You cannot concatenate an Array and a String like that. It will throw a TypeError: no implicit conversion of Array into String
But taking my best guess at it the following code works just fine:
def get_new_keys(hash)
hash.delete_if{ |k,v| v % 2 == 0 }
return hash.keys
end
def display_keys(hash)
return ["foo"] + get_new_keys(hash)
end
hash = {
one: 1,
two: 2,
three: 3,
four: 4,
five: 5,
six: 6,
seven: 7,
eight: 8,
nine: 9,
ten: 10
}
Outputs:
pry(main)> display_keys( hash )
=> ["foo", :one, :three, :five, :seven, :nine]
Related
I'm working on this problem:
Write a method retrieve_values that takes in two hashes and a key. The method should return an array containing the values from the two hashes that correspond with the given key.
def retrieve_values(hash1, hash2, key)
end
dog1 = {"name"=>"Fido", "color"=>"brown"}
dog2 = {"name"=>"Spot", "color"=> "white"}
print retrieve_values(dog1, dog2, "name") #=> ["Fido", "Spot"]
puts
print retrieve_values(dog1, dog2, "color") #=> ["brown", "white"]
puts
I came up with a working solution:
def retrieve_values(hash1, hash2, key)
arr = []
hash1.each { |key| } && hash2.each { |key| }
if key == "name"
arr << hash1["name"] && arr << hash2["name"]
elsif key == "color"
arr << hash1["color"] && arr << hash2["color"]
end
return arr
end
I then looked at the 'official' solution:
def retrieve_values(hash1, hash2, key)
val1 = hash1[key]
val2 = hash2[key]
return [val1, val2]
end
What is wrong with my code? Or is it an acceptable "different" approach?
Line with hash1.each { |key| } && hash2.each { |key| } just does nothing it is not needed even in your solution.
This part a bit difficult to read arr << hash1["name"] && arr << hash2["name"]. It mutates the array two times in one line, this kind of style could lead to bugs.
Also, your code sticks only to two keys name and color:
dog1 = {"name"=>"Fido", "color"=>"brown", "age" => 1}
dog2 = {"name"=>"Spot", "color"=> "white", "age" => 2}
> retrieve_values(dog1, dog2, "age")
=> []
The official solution will return [1, 2].
You don't need here to explicitly use return keyword, any block of code returns the last evaluated expression. But it is a matter of style guide.
It is possible to simplify even the official solution:
def retrieve_values(hash1, hash2, key)
[hash1[key], hash2[key]]
end
def key_for_min_value(name_hash)
name_hash.max_by {|k, v| 0-v}[0]
end
This was my code to fulfill the test suite for finding the lowest value of a hash (this was for one of my lessons online).
I know there are much easier ways to do this but I had some restrictions, as you can see below:
**A Few Restrictions:
We want you to build this on your own. Some of the following methods are helpful but off limits for this exercise. (We'll cover a few below in more depth in subsequent lessons).
I could not use keys, values, min, sort, min_by to make it pass.
This code returned the key with the lowest value (a hash of key ==> integers) but here was the requirement I could not figure out.
If the method is called and passed an argument of an empty hash, it should return nil.
Only first month programming, so this may be obvious but is there a way to return nil for an empty hash, and keep my existing code intact?
Thanks for your help
To a beginner programmer I would recommend to print all intermediate results of expressions, or work in IRB.
def key_for_min_value(name_hash)
puts
puts "in key_for_min_value with parameter #{name_hash}"
# puts "about to return nil" if name_hash.empty?
# return nil if name_hash.empty?
name_hash.max_by { | item | puts "item=#{item}" }
max = name_hash.max_by do | k, v |
puts "k=#{k} v=#{v} 0 - v = #{0 - v}"
0 - v
end
puts "max=#{max.inspect}, class of value returned by max_by : #{max.class}"
result = name_hash.max_by {|k, v| 0-v}[0]
puts "result=#{result.inspect}"
result
end
key_for_min_value({a: 1, b: 2, c: 3})
key_for_min_value({})
Execution :
$ ruby -w t.rb
in key_for_min_value with parameter {:a=>1, :b=>2, :c=>3}
item=[:a, 1]
item=[:b, 2]
item=[:c, 3]
k=a v=1 0 - v = -1
k=b v=2 0 - v = -2
k=c v=3 0 - v = -3
max=[:a, 1], class of value returned by max_by : Array
result=:a
in key_for_min_value with parameter {}
max=nil, class of value returned by max_by : NilClass
t.rb:15:in `key_for_min_value': undefined method `[]' for nil:NilClass (NoMethodError)
from t.rb:21:in `<main>'
The documentation of enum.max_by says :
Returns the item corresponding to the largest value returned by the
block.
But if the enum is empty, it returns nil, from which you fetch element [0], which causes the error because there is no such method in the NilClass.
If you add return nil if name_hash.empty? at the beginning of the method, you prevent it to happen (with two uncommented lines) :
$ ruby -w t.rb
in key_for_min_value with parameter {:a=>1, :b=>2, :c=>3}
...
in key_for_min_value with parameter {}
about to return nil
There a lot of different possibilities to do what you want. The most obvious one is to literally translate the sentence: "return nil if the hash is empty" into Ruby:
def key_for_min_value(name_hash)
return nil if name_hash.empty?
name_hash.max_by {|k, v| 0-v}[0]
end
Another possibility would be to use the safe navigation operator:
def key_for_min_value(name_hash)
name_hash.max_by {|k, v| 0-v}&.[](0)
end
Yet another way would be to ensure that the value you are trying to index into is never nil:
def key_for_min_value(name_hash)
(name_hash.max_by {|k, v| 0-v} || [])[0]
end
# or
def key_for_min_value(name_hash)
Array(name_hash.max_by {|k, v| 0-v})[0]
end
I have some data saved in deeply nested Hashes and Arrays and I have run into trouble with the text encoding of the data. I know for fact that the texts are encoded in "UTF-8", so I decided to go over each element and force the encoding.
So, I created a method called deep_each for the Enumerable module:
module Enumerable
def deep_each(&block)
self.each do |element|
if element.is_a? Enumerable then
element.deep_each(&block)
else
block[element]
end
end
end
end
And expected to be able to fix the data using the following method call:
deephash.deep_each {|element| element.force_encoding("UTF-8") if element.class == String}
But the result was disappointing:
deephash.deep_each {|element| element.force_encoding("UTF-8") if element.class == String}
> RuntimeError: can't modify frozen String
> from (pry):16:in `force_encoding'
Then I moved the function down the hierarchy, to the "Array" and "Hash" classes:
class Hash
def deep_each(&block)
self.each do |element|
if [Array, Hash].include? element.class then
element.deep_each(&block)
else
block[element]
end
end
end
end
class Array
def deep_each(&block)
self.each do |element|
if [Array, Hash].include? element.class then
element.deep_each(&block)
else
block[element]
end
end
end
end
Surprisingly, the same call works now.
What constraint am I violating here, and how can I define a method for all Enumerables without defining it for every single one of them?
As far as I can tell, you should get the exact same error with both your Enumerable version and your Array/Hash monkey patch. I do. Are you sure you're using the same deephash in both cases?
Normally when you loop each on a hash, you'd pass in both key and value to the block. You're passing a single value element to the block. This then is an Array with the key and value:
irb> {a:1, b:2}.each {|el| puts el.inspect }
[:a, 1]
[:b, 2]
Your deep_each checks if this is an Enumerable, and it is, so it calls deep_each on the list. Then, finally, you reach the leafs and call the block on the key and the value. The block checks if it's working with a String, and if so, forces encoding.
If your hash key is a string, you will try to mutate it. But hash keys are frozen, and so RuntimeError: can't modify frozen String is raised.
irb> {a: {b: {c: "abc"}}}.deep_each { |el| el << "efg" if String === el}
=> {:a=>{:b=>{:c=>{:d=>"abcefg"}}}}
irb> {a: {b: {"c" => "abc"}}}.deep_each { |el| el << "efg" if String === el}
RuntimeError: can't modify frozen String
str = "\xE2\x82\xAC" #Euro sign in UTF-8
puts str.encoding #=> UTF-8
puts str #=> Euro sign in a UTF-8 enabled terminal window
File.open('data.txt', 'w:utf-8') do |f|
f.write("#{str}\n")
end
Encoding.default_external = 'ISO-8859-1'
str = File.read('data.txt')
puts str.encoding #=> ISO-8859-1
arr = [
{a: str},
{b: 'world'},
]
arr[0][:a].force_encoding('utf-8')
puts arr[0][:a].encoding #=> UTF-8
puts arr[0][:a] #=> Euro sign in a UTF-8 enabled terminal window
It would be more illustrative if you posted an example of: I have run into trouble with the text encoding of the data
Finally, it looks like writing the method for each class separately
makes more sense. For the Hash I need to use each_value rather than
each
You can do something like this:
iterator_for = Hash.new(:each) #When a non-existent key is looked up, return :each
iterator_for.update({
Hash => :each_value,
})
data = [
%w{ hello world goodbye },
{"a" => "red", "b" => "blue"},
]
data.each do |element|
element.send(iterator_for[element.class]) do |x|
puts x
end
puts '-' * 20
end
--output:--
hello
world
goodbye
--------------------
red
blue
--------------------
Write a function that accepts a multi-dimensional container of any size and converts it into a one dimensional associative array whose keys are strings representing their value's path in the original container.
So { 'one' => {'two' => 3, 'four' => [ 5,6,7]}, 'eight'=> {'nine'=> {'ten'=>11}}}
would become
:
"{'one/two' => 3,'one/four/0' => 5, 'one/four/1' => 6, 'one/four/2' => 7, 'eight/nine/ten' : 11}"
I've gotten this so far... But am having a lot of issues. Any pointers to things I am overlooking?
def oneDimHash(hash)
if hash.is_a?(Fixnum)
puts "AHHH"
else
hash.each_pair do |key,value|
if value.is_a?(Hash)
#temp_key << key << '/'
oneDimHash(value)
elsif value.is_a?(Array)
value.each_with_index do |val,index|
puts index
#temp_key << "#{index}"
oneDimHash(val)
end
else
#temp_key << key
#result["#{#temp_key}"] = "#{value}"
#temp_key = ''
end
end
end
end
It's immediately suspect to me that you are using instance variables instead of method arguments / local variables. Very likely that is producing messed-up keys, at least. Supposing that the method signature cannot be modified, you can work around the need for additional arguments by delegating to a helper function. Perhaps I'd try an approach along these lines:
def oneDimHash(o)
oneDimHashInternal("", o, {})
end
def oneDimHashInternal(keyStem, o, hash)
if o.is_a? Hash
o.each_pair do |key, value|
oneDimHashInternal("#{keystem}/#{key}", value, hash)
end
elsif o.is_a? Array
# Work this out for yourself
else
# Store the (non-container) object in hash
# Work this out for yourself
end
hash
end
Note also that there are Enumerables that are neither Arrays nor Hashes. I don't know whether you need to account for such.
How about this?
def oneDimHash(obj,parent="")
unless obj.is_a?(Hash)
puts "AHHH" # or may be better: raise "AHHH"
else
obj.flat_map do |key,value|
combined_key = [parent,key.to_s].join '/'
case value
when Hash then oneDimHash(value,combined_key).to_a
when Array then value.each_with_index.map { |v,i| [combined_key+"/#{i}",v] }
else [ [combined_key,value] ]
end
end.to_h
end
end
My code is:
hash = { two: 2, three: 3 }
def hash_add(hash, new_key, new_value)
temp_hash = {}
temp_hash[new_key.to_sym] = new_value
temp_hash.merge!(hash)
hash = temp_hash
puts hash
end
hash_add(hash, 'one', 1)
Within the method, puts hash returns { :one => 1, :two => 2, :three => 3 }, but when hash1 is put to the method, it remains unchanged afterward. It's like the assignment isn't carrying itself outside of the function.
I guess I could return the updated hash and set the hash I want to change to it outside the method:
hash = hash_add(hash, 'one', 1)
But I just don't see why the assignment I give to the hash does not stick outside of the method.
I have this, which works:
def hash_add(hash, new_key, new_value)
temp_hash = {}
temp_hash[new_key.to_sym] = new_value
temp_hash.merge!(hash)
hash.clear
temp_hash.each do |key, value|
hash[key] = value
end
end
Which gives me what I'm wanting when this method is called, but it just seems a little excessive to have to rebuild the hash like that.
How about this?
hash1 = { two: 2, three: 3 }
#add a new key,value
hash1 = Hash[:one,1].merge!(hash1) #=> {:one=>1, :two=>2, :three=>3}
Example #2:
h = { two: 2, three: 3 }
def hash_add(h,k,v)
Hash[k.to_sym,v].merge!(h)
end
h = hash_add(h, 'one', 1) #=> {:one=>1, :two=>2, :three=>3}
Ruby passes objects to methods by value, but the value is the reference to the object, so when you set hash=temp_hash within the add_hash method, that change only applies inside the method. The value of hash outside the method is unchanged.
def hash_add(hash, new_key, new_value)
temp_hash = {}
temp_hash[new_key.to_sym] = new_value
temp_hash.merge!(hash)
hash = temp_hash
hash
end
h2 = hash_add(hash, 'one', 1)
hash
=> {:two=>2, :three=>3}
h2
=>{:one=>1, :two=>2, :three=>3}
If you want hash to be updated, you need to replace the contents of hash rather than re-point hash at a new object as you did with the clear and re-adding the values. You can also do it with the replace method.
def hash_add(hash, new_key, new_value)
temp_hash = {}
temp_hash[new_key.to_sym] = new_value
temp_hash.merge!(hash)
hash.replace temp_hash
end
There are some good diagrams about pass by value in "Is Ruby pass by reference or by value?"
NOTE: this answer is old from times when Ruby 1.8 was still around.
In general, the class Hash in Ruby does not provide ordering. Behavior might differ between Ruby versions / implementations.
See also: Hash ordering preserved between iterations if not modified?
If you want ordering, you need to use the class OrderedHash which is provided through ActiveSupport
See: http://apidock.com/rails/ActiveSupport/OrderedHash
At the end of the function you are just putsing the hash, not returning it. Perhaps if you changed puts hash to return hash it would work (I haven't tried it myself).
temp_hash is a local variable, which gets deleted once the function returns.