ruby print hash values in iteration - ruby

This is happening and it seems weird to me.
The following code prints nothing but blank lines:
matz = { "First name" => "Yukihiro",
"Last name" => "Matsumoto",
"Age" => 47,
"Nationality" => "Japanese",
"Nickname" => "Matz"
}
matz.each do |k|
puts matz[k]
end
if I change that to
matz.each do |k|
puts k
puts matz[k]
end
works
also
matz.each do |k,v|
puts matz[k]
end
works
Anybody has got any explain please

In:
matz.each do |k|
puts matz[k]
end
each k will be an array that represents a key-value pair such as ["First name", "Yukihiro"]. Since none of these pairs is a key of the hash matz, puts matz[k] is the same as puts nil.

Related

How to flatten a hash, making each key a unique value?

I want to take a hash with nested hashes and arrays and flatten it out into a single hash with unique values. I keep trying to approach this from different angles, but then I make it way more complex than it needs to be and get myself lost in what's happening.
Example Source Hash:
{
"Name" => "Kim Kones",
"License Number" => "54321",
"Details" => {
"Name" => "Kones, Kim",
"Licenses" => [
{
"License Type" => "PT",
"License Number" => "54321"
},
{
"License Type" => "Temp",
"License Number" => "T123"
},
{
"License Type" => "AP",
"License Number" => "A666",
"Expiration Date" => "12/31/2020"
}
]
}
}
Example Desired Hash:
{
"Name" => "Kim Kones",
"License Number" => "54321",
"Details_Name" => "Kones, Kim",
"Details_Licenses_1_License Type" => "PT",
"Details_Licenses_1_License Number" => "54321",
"Details_Licenses_2_License Type" => "Temp",
"Details_Licenses_2_License Number" => "T123",
"Details_Licenses_3_License Type" => "AP",
"Details_Licenses_3_License Number" => "A666",
"Details_Licenses_3_Expiration Date" => "12/31/2020"
}
For what it's worth, here's my most recent attempt before giving up.
def flattify(hashy)
temp = {}
hashy.each do |key, val|
if val.is_a? String
temp["#{key}"] = val
elsif val.is_a? Hash
temp.merge(rename val, key, "")
elsif val.is_a? Array
temp["#{key}"] = enumerate val, key
else
end
print "=> #{temp}\n"
end
return temp
end
def rename (hashy, str, n)
temp = {}
hashy.each do |key, val|
if val.is_a? String
temp["#{key}#{n}"] = val
elsif val.is_a? Hash
val.each do |k, v|
temp["#{key}_#{k}#{n}"] = v
end
elsif val.is_a? Array
temp["#{key}"] = enumerate val, key
else
end
end
return flattify temp
end
def enumerate (ary, str)
temp = {}
i = 1
ary.each do |x|
temp["#{str}#{i}"] = x
i += 1
end
return flattify temp
end
Interesting question!
Theory
Here's a recursive method to parse your data.
It keeps track of which keys and indices it has found.
It appends them in a tmp array.
Once a leaf object has been found, it gets written in a hash as value, with a joined tmp as key.
This small hash then gets recursively merged back to the main hash.
Code
def recursive_parsing(object, tmp = [])
case object
when Array
object.each.with_index(1).with_object({}) do |(element, i), result|
result.merge! recursive_parsing(element, tmp + [i])
end
when Hash
object.each_with_object({}) do |(key, value), result|
result.merge! recursive_parsing(value, tmp + [key])
end
else
{ tmp.join('_') => object }
end
end
As an example:
require 'pp'
pp recursive_parsing(data)
# {"Name"=>"Kim Kones",
# "License Number"=>"54321",
# "Details_Name"=>"Kones, Kim",
# "Details_Licenses_1_License Type"=>"PT",
# "Details_Licenses_1_License Number"=>"54321",
# "Details_Licenses_2_License Type"=>"Temp",
# "Details_Licenses_2_License Number"=>"T123",
# "Details_Licenses_3_License Type"=>"AP",
# "Details_Licenses_3_License Number"=>"A666",
# "Details_Licenses_3_Expiration Date"=>"12/31/2020"}
Debugging
Here's a modified version with old-school debugging. It might help you understand what's going on:
def recursive_parsing(object, tmp = [], indent="")
puts "#{indent}Parsing #{object.inspect}, with tmp=#{tmp.inspect}"
result = case object
when Array
puts "#{indent} It's an array! Let's parse every element:"
object.each_with_object({}).with_index(1) do |(element, result), i|
result.merge! recursive_parsing(element, tmp + [i], indent + " ")
end
when Hash
puts "#{indent} It's a hash! Let's parse every key,value pair:"
object.each_with_object({}) do |(key, value), result|
result.merge! recursive_parsing(value, tmp + [key], indent + " ")
end
else
puts "#{indent} It's a leaf! Let's return a hash"
{ tmp.join('_') => object }
end
puts "#{indent} Returning #{result.inspect}\n"
result
end
When called with recursive_parsing([{a: 'foo', b: 'bar'}, {c: 'baz'}]), it displays:
Parsing [{:a=>"foo", :b=>"bar"}, {:c=>"baz"}], with tmp=[]
It's an array! Let's parse every element:
Parsing {:a=>"foo", :b=>"bar"}, with tmp=[1]
It's a hash! Let's parse every key,value pair:
Parsing "foo", with tmp=[1, :a]
It's a leaf! Let's return a hash
Returning {"1_a"=>"foo"}
Parsing "bar", with tmp=[1, :b]
It's a leaf! Let's return a hash
Returning {"1_b"=>"bar"}
Returning {"1_a"=>"foo", "1_b"=>"bar"}
Parsing {:c=>"baz"}, with tmp=[2]
It's a hash! Let's parse every key,value pair:
Parsing "baz", with tmp=[2, :c]
It's a leaf! Let's return a hash
Returning {"2_c"=>"baz"}
Returning {"2_c"=>"baz"}
Returning {"1_a"=>"foo", "1_b"=>"bar", "2_c"=>"baz"}
Unlike the others, I have no love for each_with_object :-). But I do like passing a single result hash around so I don't have to merge and remerge hashes over and over again.
def flattify(value, result = {}, path = [])
case value
when Array
value.each.with_index(1) do |v, i|
flattify(v, result, path + [i])
end
when Hash
value.each do |k, v|
flattify(v, result, path + [k])
end
else
result[path.join("_")] = value
end
result
end
(Some details adopted from Eric, see comments)
Non-recursive approach, using BFS with an array as a queue. I keep the key-value pairs where the value isn't an array/hash, and push array/hash contents to the queue (with combined keys). Turning arrays into hashes (["a", "b"] ↦ {1=>"a", 2=>"b"}) as that felt neat.
def flattify(hash)
(q = hash.to_a).select { |key, value|
value = (1..value.size).zip(value).to_h if value.is_a? Array
!value.is_a?(Hash) || !value.each { |k, v| q << ["#{key}_#{k}", v] }
}.to_h
end
One thing I like about it is the nice combination of keys as "#{key}_#{k}". In my other solution, I could've also used a string path = '' and extended that with path + "_" + k, but that would've caused a leading underscore that I'd have to avoid or trim with extra code.

why 2 same strings have the same object_id in Ruby?

As you may know that in Ruby two same strings do not have a same object_id, while two same symbols do. For instance:
irb(main):001:0> :george.object_id == :george.object_id
=> true
irb(main):002:0> "george".object_id == "george".object_id
=> false
However, in my code below, it shows that two strings which have a same value "one" having a same object_id.
class MyArray < Array
def ==(x)
comparison = Array.new()
x.each_with_index{|item, i| comparison.push(item.object_id.equal?(self[i].object_id))}
if comparison.include?(false) then
false
else
true
end
end
end
class MyHash < Hash
def ==(x)
y = Hash[self.sort]
puts y.class
puts y
x = Hash[x.sort]
puts x.class
puts x
puts "______"
xkeys = MyArray.new(x.keys)
puts xkeys.class
puts xkeys.to_s
puts xkeys.object_id
puts xkeys[0].class
puts xkeys[0]
puts xkeys[0].object_id
puts "______"
xvals = MyArray.new(x.values)
puts "______"
selfkeys = MyArray.new(y.keys)
puts selfkeys.class
puts selfkeys.to_s
puts selfkeys.object_id
puts selfkeys[0].class
puts selfkeys[0]
puts selfkeys[0].object_id
puts "______"
selfvals = MyArray.new(y.values)
puts xkeys.==(selfkeys)
puts xvals.==(selfvals)
end
end
a1 = MyHash[{"one" => 1, "two" => 2}]
b1 = MyHash[{"one" => 1, "two" => 2}]
puts a1.==(b1)
And Get
Hash
{"one"=>1, "two"=>2}
Hash
{"one"=>1, "two"=>2}
______
MyArray
["one", "two"]
21638020
String
one
21641920
______
______
MyArray
["one", "two"]
21637580
String
one
21641920
______
true
true
As you can see from the result that 2 String objects with have a same value "one" having a same object_id 21641920, while it's supposed to have different ID. So can anyone give me some hints or tell me how can I get different ID in this case?
Best Regards.
When a String object is used as a key in a Hash, the hash will duplicate and freeze the string internally and will use that copy as its key.
Reference: Hash#store.
As of ruby 2.2 strings used as keys in hash literals are frozen and de-duplicated: the same string will be reused.
This is a performance optimisation: not allocating many copies of the same string means there are fewer objects to allocate and fewer to garbage collect.
Another way to see frozen string literals in action :
"foo".freeze.object_id == "foo".freeze.object_id
Will return true in versions of ruby >= 2.1

Using one Array to search a second array for frequency in ruby

I have an Array-1 say
arr1 =['s','a','sd','few','asdw','a','sdfeg']
And a second Array
arr2 = ['s','a','d','f','w']
I want to take arr1 and sort the frequency of letters by inputting arr2 with result
[s=> 4, a=> 2, d => 3] So on and so forth.
As far as I can muddle around.. Nothing below works, Just my thoughts on it?
hashy = Hash.new
print "give me a sentance "
sentance = gets.chomp.downcase.delete!(' ')
bing = sentance.split(//)
#how = sentance.gsub!(/[^a-z)]/, "") #Remove nil result
#chop = how.to_s.split(//).uniq
#hashy << bing.each{|e| how[e] }
#puts how.any? {|e| bing.count(e)}
#puts how, chop
bing.each {|v| hashy.store(v, hashy[v]+1 )}
puts bing
Thank you for your time.
I assumed that you want to count all letters in the sentence you put in, and not array 1. Assuming that, here's my take on it:
hashy = Hash.new()
['s','a','d','f','w'].each {|item| hashy[item.to_sym] = 0}
puts "give me a sentence"
sentence = gets.chomp.downcase.delete!(' ')
sentence_array = []
sentence.each_char do |l|
sentence_array.push(l)
end
hashy.each do |key, value|
puts "this is key: #{key} and value #{hashy[key]}"
sentence_array.each do |letter|
puts "letter: #{letter}"
if letter.to_sym == key
puts "letter #{letter} equals key #{key}"
value = value + 1
hashy[key] = value
puts "value is now #{value}"
end
end
end
puts hashy

Ruby(Hard Way, Ex48) matching array to hash then assigning values to struct?

I've looked at everything from the Ruby docs on struct, map, array & hash; but for whatever reason haven't grasped HOW to do this. I have two questions.
What's a good way to see if an array and a hash have a value in common?
From a hash, how could I find a value and "automatically" take it's key (or viceversa) and include them in a new instance of something else (e.g. Pair.new)?
I saw this SO response, but it wasn't much help...Learn Ruby Hard Way ex. 48
Also looked at this too, but it didn't work & Zed says to use map func...
Using Ruby, what is the most efficient way to check if any key in a hash matches any values within an Array
The exercise instructions can be found here.
Ruby The Hard Way EX.48 Instructions
MY CODE (try#2080)
class Lexicon
Pair = Struct.new(:token, :key)
def scan(stuff)
#words = stuff.split(" ")
return analyze
end
def analyze
hash = { :direction => "north", :direction => "south",
:direction => "east", :direction => "west", :verb => "go",
:verb => "stop"}
#words.map do |word|
if word == hash[:direction]
#i need something here that says if word matches a value in hash...
#assign matching key/value to a new instance of Pair
Pair.new(word)
else
*#puts to see if anything is sticking*
puts "Oh god its not working #{word}"
puts Pair[]
puts hash[:direction]
end
end
end
end
a = Lexicon.new()
a.scan("north mama jeffrey homie")
TERMINAL
$ ruby lexicon.rb
Oh god its not working north
#<struct Lexicon::Pair token=nil, key=nil>
west
Oh god its not working mama
#<struct Lexicon::Pair token=nil, key=nil>
west
Oh god its not working Jeffrey
#<struct Lexicon::Pair token=nil, key=nil>
west
Oh god its not working homie
#<struct Lexicon::Pair token=nil, key=nil>
west
MY CODE #2081 Same as above but,
hash.each do |k, v|
if v == #words
#i need something here that says if word matches a value in hash...
#assign matching key/value to a new instance of Pair
Pair.new(k,v)
else
puts "Oh god its not working"
puts Pair[]
puts hash[:direction]
puts #words
end
end
TERMINAL
Oh god its not working
#<struct Lexicon::Pair token=nil, key=nil>
...
...
You've got several issues here. Most importantly, your hash is not functional; hashes are built on key-value pairs with unique keys, so your hash is actually the same as the following:
hash = { :direction => "west", :verb => "stop"}
You would probably be better off swapping the key-value pairs in your hash as follows:
hash = { "north" => :direction, "south" => :direction,
"east" => :direction, "west" => :direction, "go" => :verb,
"stop" => :verb }
#words.map do |word|
hash.keys.include?(word) ? Pair.new(hash[word], word) : Pair.new(:error, word)
end
def have_common_value?(array, hash)
hash.values.any? { |v| array.include? v }
end
def get_pair_from_value(hash, value)
hash.find { |k,v| v == value }
end
A hash can only have one unique key per value, therefore, part of your problem is that the hash you generate above will only return one :direction and one :location.
This should help get you closer to what you're looking for:
class Lexicon
Pair = Struct.new(:token, :key)
def scan(stuff)
#words = stuff.split(" ")
return analyze
end
def analyze
hash = { :directions => { :north => "north", :south => "south", :east => "east", :west => "west" },
:actions => { :verb => "go", :verb => "stop" } }
#words.map do |word|
if hash[:directions].values.include?(word)
Pair.new(word)
else
puts "Oh god its not working #{word}"
puts Pair[]
puts hash[:directions]
end
end
end
end
a = Lexicon.new()
a.scan("north mama jeffrey homie")

hash methods argument values

Working on trying to understand the syntax for calling on different values of a hash.
For example lets say I am trying to delete 'pants' How do go about setting the argument for something like this:
products = {124 => ['shoes', 59.99], 352 => ['shirt', 19.99], 777 => ['pants', 19.87],
667 => ['jacket', 39.99], 898 => ['shoulder_holster', 22.78]}
While writing a menu driven program for this hash I'm including error checking before deleteing or adding a key this is what I have so far:
if a == 3 # Loop delete a Product
puts "Delete a Product"
d = gets.to_s # Get value for argument
while products.has_value?( d + syntax for right here???? )!= true do
puts "This turned out false because product does not exsist!"
d = gets.to_s
end
puts "Congrats your out of the loop"
products.delete(d + again syntax problems ???? )
puts products
end
How do I enter the syntax for the argument if I where to delete pants. Would it be ([d,:number]) I'm not having luck with any resources online with how to delete or add in this scenario. Any help or code example would be appreciated,
Matt
products.to_a.select {|a| a.last.first == 'pants' }
That will get you the record that matches 'pants'.
[[777, ["pants", 19.87]]]
So I think you'll want
while !products.to_a.select {|a| a.last.first == d }.empty?
on your loop then use Dafydd's line to delete the record.
It depends on whether the user is inputing the ID number or the name "pants". If the former:
if a == 3 # Loop delete a Product
puts "Delete a Product"
d = gets # Get value for argument
until products.has_key?(d.to_i)
puts "This turned out false because product does not exsist!"
d = gets
end
puts "Congrats your out of the loop"
products.delete(d.to_i)
puts products
end
If it's "pants", then this is how you want to do it:
if a == 3 # Loop delete a Product
puts "Delete a Product"
d = gets.strip # Need to strip because otherwise the newline will wreck it
until products.find {|key, val| val.first == d}
puts "This turned out false because product does not exsist!"
d = gets.strip
end
puts "Congrats your out of the loop"
products.delete_if {|key, val| val.first == d}
puts products
end
Writing a "delete named product from hash" method
There are shorter ways of doing it, but shooting for clarity I came up with this:
products = {124 => ['shoes', 59.99], 352 => ['shirt', 19.99], 777 => ['pants', 19.87],
667 => ['jacket', 39.99], 898 => ['shoulder_holster', 22.78]}
def wipeProduct(hash, nameToDelete)
hash.each do |i|
key = i[0]
productName = i[1].first
hash.delete(key) if productName==nameToDelete
end
end
puts products.inspect
wipeProduct(products,'pants')
puts products.inspect
wipeProduct(products,'shoulder_holster')
puts products.inspect
bash-3.2$ ruby prod.rb
{352=>["shirt", 19.99], 898=>["shoulder_holster", 22.78], 667=>["jacket", 39.99], 777=>["pants", 19.87], 124=>["shoes", 59.99]}
{352=>["shirt", 19.99], 898=>["shoulder_holster", 22.78], 667=>["jacket", 39.99], 124=>["shoes", 59.99]}
{352=>["shirt", 19.99], 667=>["jacket", 39.99], 124=>["shoes", 59.99]}
I don't know if it's possible for "pants" to occur in the hash in multiple places, but since I used "hash.each(...)", the method wipeProduct(hash, nameToDelete) will test every hash entry.
The input type bug and how to fix it
When you take input, you're assigning the string you captured to d. Here's the proof:
irb(main):010:0> d = gets.to_s
12
=> "12\n"
irb(main):011:0> d.class
=> String
You can convert that string to a Fixnum like this:
irb(main):012:0> d.to_i
=> 12
irb(main):013:0> d.to_i.class
=> Fixnum
All keys in the products hash are Fixnums. Here's the proof:
irb(main):014:0> products.keys.each {|i| puts i.class}
Fixnum
Fixnum
Fixnum
Fixnum
Fixnum
=> [352, 898, 667, 777, 124]
So you need to capture the value for the argument with this line:
d = gets.to_i # Get value for argument
The deletion part of the answer:
From products, you can delete the pants entry programmatically with this:
products.delete(777)
Running it gets you this:
irb(main):003:0> products.delete(777)
=> ["pants", 19.87]
Notice that you supply the key value (in this case 777) to .delete() and that it returns an array consisting of the key and value in that order respectively.
An alternative implementation
I'm not sure if it's safe to modify a hash in a block that's iterating over the key-value pairs in the hash. If it isn't, you can just save up all the keys to be deleted and delete them after iterating over the hash:
def wipeProduct(hash, nameToDelete)
keysToDelete = []
hash.each do |i|
key = i[0]
productName = i[1].first
keysToDelete << key if productName==nameToDelete
end
keysToDelete.each {|key| hash.delete(key) }
end
Here's the neater way to delete the "pants" entry:
def wipeProduct(hash, nameToDelete)
hash.reject!{|key,value| nameToDelete==value.first}
end
The reject! block gets to see each key-value pair, and when it returns true, the key-value supplied will be removed from the hash.
if a == 3 # Loop delete a Product
puts "Delete a Product by its key number"
d = gets
while products.has_key?(d)!= false do
puts "You have selected a key that is not currently in use"
d = gets
end
puts "You have deleted"
products.delete(d)
puts products
end
This is what I ended up doing had some trouble with the until loop so swapped for a while loop though becasue it wouldn't accept newly entered keys for some reason

Resources