splitting an array into a hash - ruby

Im looking to split an array of strings and creating a hash out of it.
I have an algorithm that splits a string into an array by commas this:1, is:1, a:1, string:1
def split_answer_to_hash(str)
words = str.split(',')
answer = {}
words.each do |w|
a = w.split(':')
h = Hash[ *a.collect { |v| [ v, a[1] ] } ]
answer = h
end
answer
end
What I need to do now is to make the left side of the colon the key to the hash and the right side of the colon the value of the hash. example: {"this" =>1, "is"=>1, "a"=>1, "string"=>1 }
*a.collect is iterating through the array and making the value another key. How can I go about this with out that happening?

The easiest way is:
string = 'this:1, is:1, a:1, string:1'
hash = Hash[*string.split(/:|,/)]
#=> {"this"=>"1", " is"=>"1", " a"=>"1", " string"=>"1"}

Having just one answer to this question just won't do:
str = "this:1, is:1, a:1, string:1"
Hash[str.scan(/(?:([^:]+):(\d+)(?:,\s)?)/)]
.tap { |h| h.keys.each { |k| h[k] = h[k].to_i } }
#=> {"this"=>1, "is"=>1, "a"=>1, "string"=>1}
Object#tap is used merely to convert the values from strings to integers. If you'd prefer:
h = Hash[str.scan(/(?:([^:]+):(\d+)(?:,\s)?)/)]
h.keys.each { |k| h[k] = h[k].to_i }
h
#=> {"this"=>1, "is"=>1, "a"=>1, "string"=>1}
For Ruby 2.1, you can replace Hash[arr] with arr.to_h.

Related

How to solve the "retrieve_values" problem

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

Check whether a string contains all the characters of another string in Ruby

Let's say I have a string, like string= "aasmflathesorcerersnstonedksaottersapldrrysaahf". If you haven't noticed, you can find the phrase "harry potter and the sorcerers stone" in there (minus the space).
I need to check whether string contains all the elements of the string.
string.include? ("sorcerer") #=> true
string.include? ("harrypotterandtheasorcerersstone") #=> false, even though it contains all the letters to spell harrypotterandthesorcerersstone
Include does not work on shuffled string.
How can I check if a string contains all the elements of another string?
Sets and array intersection don't account for repeated chars, but a histogram / frequency counter does:
require 'facets'
s1 = "aasmflathesorcerersnstonedksaottersapldrrysaahf"
s2 = "harrypotterandtheasorcerersstone"
freq1 = s1.chars.frequency
freq2 = s2.chars.frequency
freq2.all? { |char2, count2| freq1[char2] >= count2 }
#=> true
Write your own Array#frequency if you don't want to the facets dependency.
class Array
def frequency
Hash.new(0).tap { |counts| each { |v| counts[v] += 1 } }
end
end
I presume that if the string to be checked is "sorcerer", string must include, for example, three "r"'s. If so you could use the method Array#difference, which I've proposed be added to the Ruby core.
class Array
def difference(other)
h = other.each_with_object(Hash.new(0)) { |e,h| h[e] += 1 }
reject { |e| h[e] > 0 && h[e] -= 1 }
end
end
str = "aasmflathesorcerersnstonedksaottersapldrrysaahf"
target = "sorcerer"
target.chars.difference(str.chars).empty?
#=> true
target = "harrypotterandtheasorcerersstone"
target.chars.difference(str.chars).empty?
#=> true
If the characters of target must not only be in str, but must be in the same order, we could write:
target = "sorcerer"
r = Regexp.new "#{ target.chars.join "\.*" }"
#=> /s.*o.*r.*c.*e.*r.*e.*r/
str =~ r
#=> 2 (truthy)
(or !!(str =~ r) #=> true)
target = "harrypotterandtheasorcerersstone"
r = Regexp.new "#{ target.chars.join "\.*" }"
#=> /h.*a.*r.*r.*y* ... o.*n.*e/
str =~ r
#=> nil
A different albeit not necessarily better solution using sorted character arrays and sub-strings:
Given your two strings...
subject = "aasmflathesorcerersnstonedksaottersapldrrysaahf"
search = "harrypotterandthesorcerersstone"
You can sort your subject string using .chars.sort.join...
subject = subject.chars.sort.join # => "aaaaaaacddeeeeeffhhkllmnnoooprrrrrrssssssstttty"
And then produce a list of substrings to search for:
search = search.chars.group_by(&:itself).values.map(&:join)
# => ["hh", "aa", "rrrrrr", "y", "p", "ooo", "tttt", "eeeee", "nn", "d", "sss", "c"]
You could alternatively produce the same set of substrings using this method
search = search.chars.sort.join.scan(/((.)\2*)/).map(&:first)
And then simply check whether every search sub-string appears within the sorted subject string:
search.all? { |c| subject[c] }
Create a 2 dimensional array out of your string letter bank, to associate the count of letters to each letter.
Create a 2 dimensional array out of the harry potter string in the same way.
Loop through both and do comparisons.
I have no experience in Ruby but this is how I would start to tackle it in the language I know most, which is Java.

Preserving key value pairs for duplicate keys when merging two Arrays to a hash

I have two arrays in ruby:
array_one = ["farmer_joe", "farmer_judy", "farmer_crazy_eyes", "farmer_joe"]
array_two = ["pigs", "chickens", "elephants", "cows"]
If I use the zip function I lose the duplicate value, key pair for Farmer Joe.
hash_one = Hash[array_one.zip array_two]
=> {"farmer_joe"=>"cows", "farmer_judy"=>"chickens", "farmer_crazy_eyes"=>"elephants"}
Ideally I want a function that allows me to overcome this in a nifty ruby flavoured oneline method. perhaps something like this that merges duplicate keys and adds their values to an array.
hash_one = Hash[array_one.super_special_zip array_two]
=> {"farmer_joe"=>["pigs","cows"], "farmer_judy"=>["chickens"], "farmer_crazy_eyes"=>["elephants"]}
Is there such a super_special_zip method? Or is there a good reason why this is a fools errand in the first place?
There are three standard ways of doing this.
a1 = ["farmer_joe", "farmer_judy", "farmer_crazy_eyes", "farmer_joe"]
a2 = ["pigs", "chickens", "elephants", "cows"]
pairs = a1.zip(a2) # or [a1,a2].transpose
#=> [["farmer_joe", "pigs"], ["farmer_judy", "chickens"],
# ["farmer_crazy_eyes", "elephants"], ["farmer_joe", "cows"]]
1. Use Hash.new to create a hash with a default value of an empty array
pairs.each_with_object(Hash.new { |h,k| h[k]=[] }) { |(f,l),h| h[f] << l }
# => {"farmer_joe"=>["pigs", "cows"], "farmer_judy"=>["chickens"],
# "farmer_crazy_eyes"=>["elephants"]}
A variant of this (which tends to be slightly faster) is:
pairs.each_with_object({}) { |(f,l),h| (h[f] ||= []) << l }
2. Use the form of Hash#update (aka merge!) that takes a block to determine the values of keys present in both hashes being merged
pairs.each_with_object({}) { |(f,l),h| h.update(f=>[l]) { |_,o,n| o+n } }
#=> {"farmer_joe"=>["pigs", "cows"], "farmer_judy"=>["chickens"],
# "farmer_crazy_eyes"=>["elephants"]}
3. Use Enumerable#group_by
h = pairs.group_by(&:first)
#=> {"farmer_joe"=>[["farmer_joe", "pigs"], ["farmer_joe", "cows"]],
# "farmer_judy"=>[["farmer_judy", "chickens"]],
# "farmer_crazy_eyes"=>[["farmer_crazy_eyes", "elephants"]]}
h.keys.each { |k| h[k] = h[k].map(&:last) }
h
#=> {"farmer_joe"=>["pigs", "cows"], "farmer_judy"=>["chickens"],
# "farmer_crazy_eyes"=>["elephants"]}
There are many alternative to the last two lines, one being:
h.merge(h) { |*_,v| v.map(&:last) }
One way to do it
array_one = ["farmer_joe", "farmer_judy", "farmer_crazy_eyes", "farmer_joe"]
array_two = ["pigs", "chickens", "elephants", "cows"]
hash_one = {}
array_one.each_with_index do |farmer,i|
if hash_one.has_key?(farmer)
hash_one[farmer] << array_two[i]
else
hash_one[farmer] = [array_two[i]]
end
end
hash_one # => {"farmer_joe"=>["pigs", "cows"], "farmer_judy"=>["chickens"], "farmer_crazy_eyes"=>["elephants"]}
And yet another way to do that would be ( no rails )
a1 = ["farmer_joe", "farmer_judy", "farmer_crazy_eyes", "farmer_joe"]
a2= ["pigs", "chickens", "elephants", "cows"]
a1.zip(a2).group_by(&:first).map{|key, value| [key, value.map(&:last)]}.to_h
# => {"farmer_joe"=>["pigs", "cows"], "farmer_judy"=>["chickens"], "farmer_crazy_eyes"=>["elephants"]}

Find highest value from hash that contains "nil"

I have a hash which looks like this
#hash = {
0=>[{"name"=>"guest", "value"=>7.9}],
1=>[nil], 2=>[nil], 3=>[nil], 4=>[nil], 5=>[nil], 6=>[nil], 7=>[nil], 8=>[nil],
9=>[nil], 10=>[nil], 11=>[nil], 12=>[nil], 13=>[nil], 14=>[nil], 15=>[nil],
16=>[nil], 17=>[nil], 18=>[nil],
19=>[{"name"=>"test", "value"=>2.5}],
20=>[{"name"=>"roam", "value"=>2.5}],
21=>[{"name"=>"test2", "value"=>1.58}],
22=>[{"name"=>"dff", "value"=>1.9}],
23=>[{"name"=>"dddd", "value"=>3.16}]
}
I want the highest value from this hash in a variable. The output should be
#h = 7.9 \\only float value which should be highest among all
so I am doing like this
#hash.each do |k, v|
if !v.nil?
#h= [v.flatten.sort{ |v1, v2| v2['value'] <=> v1['value'] }.first['value']]
end
end
but sometimes it works, and most of the times it doesn't.
#hash.values.flatten.compact.map { |h| h["value"] }.max
=> 7.9
Which equates to:
Get the values of the hash as an array
Flatten all the elements in the values array
Compact to remove all nil entries
Map the remaining entries to the ["value"] element in the hash
Return the maximum of all those value
It makes a lot of assumptions about the format of your #hash though.
I prefer #Shadwell's solution, but here's another way:
hash.select { |_,v| v.first }
.max_by { |_,v| v.first["value"] }
.last
.first["value"]
#=> 7.9
The steps (with all but one n=>[nil] element removed for readabiity):
hash = { 0=>[{"name"=>"guest", "value"=>7.9}],
1=>[nil],
19=>[{"name"=>"test", "value"=>2.5}],
20=>[{"name"=>"roam", "value"=>2.5}],
21=>[{"name"=>"test2", "value"=>1.58}],
22=>[{"name"=>"dff", "value"=>1.9}],
23=>[{"name"=>"dddd", "value"=>3.16}]}
h = hash.select { |_,v| v.first }
#=> { 0=>[{"name"=>"guest", "value"=>7.9}],
# 19=>[{"name"=>"test", "value"=>2.5}],
# 20=>[{"name"=>"roam", "value"=>2.5}],
# 21=>[{"name"=>"test2", "value"=>1.58}],
# 22=>[{"name"=>"dff", "value"=>1.9}],
# 23=>[{"name"=>"dddd", "value"=>3.16}]}
a = h.max_by { |_,v| v.first["value"] }
#=> [0, [{"name"=>"guest", "value"=>7.9}]]
b = a.last
#=> [{"name"=>"guest", "value"=>7.9}]
b.first["value"]
#=> 7.9

ruby string to hash conversion

I have a string like this,
str = "uu#p, xx#m, yy#n, zz#m"
I want to know how to convert the given string into a hash. (i.e my actual requirement is, how many values (before the # symbol) have the m, n and p. I don't want the counting, I need an exact value). The output would be better like this,
{"m" => ["xx", "zz"], "n" => ["yy"], "p" => ["uu"]}
Can help me anyone, please?
Direct copy/past of an IRB session:
>> str.split(/, /).inject(Hash.new{|h,k|h[k]=[]}) do |h, s|
.. v,k = s.split(/#/)
.. h[k] << v
.. h
.. end
=> {"p"=>["uu"], "m"=>["xx", "zz"], "n"=>["yy"]}
Simpler code for a newbie :)
str = "uu#p, xx#m, yy#n, zz#m"
h = {}
str.split(",").each do |x|
v,k = x.split('#')
h[k] ||= []
h[k].push(v)
end
p h
FP style:
grouped = str
.split(", ")
.group_by { |s| s.split("#")[1] }
.transform_values { |ss| ss.map { |x| s.split("#")[0] } }
#=> {"m"=>["xx", "zz"], "n"=>["yy"], "p"=>["uu"]}
This is a pretty common pattern. Using Facets.map_by:
require 'facets'
str.split(", ").map_by { |s| s.split("#", 2).reverse }
#=> {"m"=>["xx", "zz"], "n"=>["yy"], "p"=>["uu"]}

Resources