Max value of nested array in hash - ruby

What I have:
hash = {id =>[string, number], id =>[string, number]}
I need to get the max value of number. I have been able to do this, but when I puts it.
I get:
id
string
number
What I need please
id string number
This is what I've tried:
This brings the max value to the top of list, but I need to exclude the rest of the list.
hash.each{|x, y| puts "#{x} #{y[0]} #{y[1]}"}.max
This returns the max value but displays it vertically
puts hash.max_by{|y| "#{y}"}
I have tried numerous other things and am having a hard time wrapping my head around this one.
Not sure if it matters but I am read this in from a file into a hash, the number is a float

The max here doesn’t do anything (since it is called on hash and its return value never used):
hash.each{|x, y| puts "#{x} #{y[0]} #{y[1]}"}.max
This is the same as doing puts on an array (since that’s what max_by returns), which prints each element on a separate line. You’re also unnecessarily converting your number to a string (which can result in unexpected comparison results):
puts hash.max_by{|y| "#{y}"}
Instead let’s just get the max key/value pair:
max = hash.max_by { |id, (string, number)| number }
#=> ["the-id", ["the-string", 3.14]]
Now we can flatten and join the array before puts-ing it:
puts max.flatten.join(' ')
# prints: the-id the-string 3.14

I would re-arrange the hash with number as the key, then use sort_by(): http://www.rubyinside.com/how-to/ruby-sort-hash

Related

Ruby: If Values in a hash are equal if statement. Need a hint if possilbe?

Hi there: Ruby Beginner.
So I'm working on a little exercise.
I have this
donation = {
"Randy" => 50,
"Steve" => 50,
"Eddie" => 9,
"Bill" => 12
}
donation.max_by.first { |name, money| money.to_i }
name, money = donation.max_by { |name, money| money.to_i }
puts "#{name} donated the most, at $#{money}!"
But there's a little bug. "Randy" and "Steve" both donated the max, but it outputs "Randy" (because they're the first in key in the for hash, I assume?) and they get all the credit!
I'm thinking the way to go is, I need an IF ELSE statement; IF any? of the "money" values are equal, it moves on. Else, it puts the statement.
SO i guess I am wondering how to compare values?
Select Elements Matching Highest Value
There's more than one way to identify and extract multiple elements that share a maximum value. Based on the semantics you're trying to communicate in your code, one working example is as follows:
max_amt = donation.max_by(&:last)[-1]
donation.select { |name, amt| amt == max_amt }
#=> {"Randy"=>50, "Steve"=>50}
First, we capture the maximum value from the donations Hash. Next, the Hash#select method passes the name and amount of each donation into the block, which returns the Hash elements for which the comparison to the maximum value is truthy. Inside the block, each amount is compared to the maximum value found in the original Hash, allowing all matching key/value pairs to be returned when more than one of them contains a value equal to the max_amt.
As you discovered, max_by returns the first key that has the maximum value. It actually takes a parameter that is the number of elements to return, so doing something like .max_by(donation.size) will return the hash in descending order and you could then go through the elements until the value doesn't match the first (max) value.
But, there's a couple of simpler ways to go about it. If you just want to print the information as you go, one way would be to just iterate through the hash looking for values that match the max value:
mx = donation.values.max
puts "The following people donated the most ($#{mx}):"
donation.each { |k, v| puts k if v == mx }
Output:
The following people donated the most ($50):
Randy
Steve
You could also use .select to match for the max value, which preserves the hash form:
mx = donation.values.max
donation.select { |k, v| v == mx }
=> {"Randy"=>50, "Steve"=>50}
EDIT
Per the follow-up comment, if you use .select to capture the result as a new hash, you can then use conditional logic, loops, etc., to process the data from there. As a very simple example, suppose you want a different message if there's only one top donor vs. multiple:
mx = donation.values.max
max_donors = donation.select { |k, v| v == mx }
if max_donors.size > 1
puts "The following people donated the most ($#{mx}):"
max_donors.keys.each { |name| puts name }
elsif max_donors.size == 1
name, money = max_donors.first
puts "#{name} donated the most, at $#{money}!"
else
puts 'No one donated!'
end

Last element of the ruby array is nil

I have a simple ruby program that has 2 steps so far
1. Ask the user for the number of stock market symbols they want to track
2. Ask the user to input these symbols
puts("How many stocks do you want to track ?")
numStocks = gets.chomp()
puts("Please enter #{numStocks} stock symbols: ")
array = Array.new(numStocks.to_i)
for i in 1..numStocks.to_i do
array.insert(i-1, gets.chomp())
end
puts("Stock symbols entered ... #{array}")
The output that is printed onto the console is
Stock symbols entered ... ["aapl", nil]
Why is the last element of the array nil in this case and what's the proper way to get rid of it ?
Array.new creates a new array, filling it with the quantity of elements you specified. Array.new(3) is the same as [nil, nil, nil]. Array.new(2, 'a') is the same as ['a', 'a'].
You then use array.insert which adds instead of replaces the elements. You could use array[i-1] = gets.chomp() to set the values, but there's really no reason to initialize the array this way at all.
A "more Ruby" way to write this all would be:
puts 'How many stocks do you want to track ?'
num_stocks = gets.chomp
puts "Please enter #{num_stocks} stock symbols: "
array = 1.upto(num_stocks.to_i).map do
gets.chomp
end
puts "Stock symbols entered ... #{array}"
EDIT:
Also, it’s worth mentioning that in Ruby, arrays are not a fixed size. You can add and remove elements from them as you please. If I had to guess, you’re used to languages like C, where you have to define the size of your array up front and then it’s just that size forever (that’s what I’m guessing you were trying to do anyways).
And another thing, in Ruby it’s not very common to use Array.new. Most times people just define an array by typing an array literal.
array = [1,2,3]
A ruby array is more like a List in other languages. It supports push (though << is a more common method for adding to an array), pop, and other list-like features.
Thats because when you do Array.new(numStocks.to_i) it initializes an array with 3 nil values and you keep adding on to it,
the proper way to get rid of nil from array is to use .compact on the array but I suggest you change your logic,
maybe something like this
puts("How many stocks do you want to track ?")
numStocks = gets.chomp()
puts("Please enter #{numStocks} stock symbols: ")
array = Array.new() # or array = []
numStocks.to_i.times do
array << gets.chomp()
end
puts("Stock symbols entered ... #{array}")
or you could ask the user to enter the comma separated symbols, so you don't have to loop, and split them,
puts("Please enter #{numStocks} stock symbols separated by commas (a,b): ")
symbols = gets.chomp().split(',')
puts("Stock symbols entered ... #{array}")

Bug in my Ruby counter

It is only counting once for each word. I want it to tell me how many times each word appears.
dictionary = ["to","do","to","do","to","do"]
string = "just do it to"
def machine(word,list)
initialize = Hash.new
swerve = word.downcase.split(" ")
list.each do |i|
counter = 0
swerve.each do |j|
if i.include? j
counter += 1
end
end
initialize[i]=counter
end
return initialize
end
machine(string,dictionary)
I assume that, for each word in string, you wish to determine the number of instances of that word in dictionary. If so, the first step is to create a counting hash.
dict_hash = dictionary.each_with_object(Hash.new(0)) { |word,h| h[word] += 1 }
#=> {"to"=>3, "do"=>3}
(I will explain this code later.)
Now split string on whitespace and create a hash whose keys are the words in string and whose values are the numbers of times that the value of word appears in dictionary.
string.split.each_with_object({}) { |word,h| h[word] = dict_hash.fetch(word, 0) }
#=> {"just"=>0, "do"=>3, "it"=>0, "to"=>3}
This of course assumes that each word in string is unique. If not, depending on the desired behavior, one possibility would be to use another counting hash.
string = "to just do it to"
string.split.each_with_object(Hash.new(0)) { |word,h|
h[word] += dict_hash.fetch(word, 0) }
#=> {"to"=>6, "just"=>0, "do"=>3, "it"=>0}
Now let me explain some of the constructs above.
I created two hashes with the form of the class method Hash::new that takes a parameter equal to the desired default value, which here is zero. What that means is that if
h = Hash.new(0)
and h does not have a key equal to the value word, then h[word] will return h's default value (and the hash h will not be changed). After creating the first hash that way, I wrote h[word] += 1. Ruby expands that to
h[word] = h[word] + 1
before she does any further processing. The first word in string that is passed to the block is "to" (which is assigned to the block variable word). Since the hash h is is initially empty (has no keys), h[word] on the right side of the above equality returns the default value of zero, giving us
h["to"] = h["to"] + 1
#=> = 0 + 1 => 1
Later, when word again equals "to" the default value is not used because h now has a key "to".
h["to"] = h["to"] + 1
#=> = 1 + 1 => 2
I used the well-worn method Enumerable#each_with_object. To a newbie this might seem complex. It isn't. The line
dict_hash = dictionary.each_with_object(Hash.new(0)) { |word,h| h[word] += 1 }
is effectively1 the same as the following.
h = Hash.new(0)
dict_hash = dictionary.each { |word| h[word] += 1 }
h
In other words, the method allows one to write a single line that creates, constructs and returns the hash, rather than three lines that do the same.
Notice that I used the method Hash#fetch for retrieving values from the hash:
dict_hash.fetch(word, 0)
fetch's second argument (here 0) is returned if dict_hash does not have a key equal to the value of word. By contrast, dict_hash[word] returns nil in that case.
1 The reason for "effectively" is that when using each_with_object, the variable h's scope is confined to the block, which is generally a good programming practice. Don't worry if you haven't learned about "scope" yet.
You can actually do this using Array#count rather easily:
def machine(word,list)
word.downcase.split(' ').collect do |w|
# for every word in `word`, count how many appearances in `list`
[w, list.count { |l| l.include?(w) }]
end.to_h
end
machine("just do it to", ["to","do","to","do","to","do"]) # => {"just"=>0, "do"=>3, "it"=>0, "to"=>3}
I think this is what you're looking for, but it seems like you're approaching this backwards
Convert your string "string" into an array, remove duplicate values and iterate through each element, counting the number of matches in your array "dictionary". The enumerable method :count is useful here.
A good data structure to output here would be a hash, where we store the unique words in our string "string" as keys and the number of occurrences of these words in array "dictionary" as the values. Hashes allow one to store more information about the data in a collection than an array or string, so this fits here.
dictionary = [ "to","do","to","do","to","do" ]
string = "just do it to"
def group_by_matches( match_str, list_of_words )
## trim leading and trailing whitespace and split string into array of words, remove duplicates.
to_match = match_str.strip.split.uniq
groupings = {}
## for each element in array of words, count the amount of times it appears *exactly* in the list of words array.
## store that in the groupings hash
to_match.each do | word |
groupings[ word ] = list_of_words.count( word )
end
groupings
end
group_by_matches( string, dictionary ) #=> {"just"=>0, "do"=>3, "it"=>0, "to"=>3}
On a side note, you should consider using more descriptive variable and method names to help yourself and others follow what's going on.
This also seems like you have it backwards. Typically, you'd want to use the array to count the number of occurrences in the string. This seems to more closely fit a real-world application where you'd examine a sentence/string of data for matches from a list of predefined words.
Arrays are also useful because they're flexible collections of data, easily iterated through and mutated with enumerable methods. To work with the words in our string, as you can see, it's easiest to immediately convert it to an array of words.
There are many alternatives. If you wanted to shorten the method, you could replace the more verbose each loop with an each_with_object call or a map call which will return a new object rather than the original object like each. In the case of using map.to_h, be careful as to_h will work on a two-dimensional array [["key1", "val1"], ["key2", "val2"]] but not on a single dimensional array.
## each_with_object
def group_by_matches( match_str, list_of_words )
to_match = match_str.strip.split.uniq
to_match.
each_with_object( {} ) { | word, groupings | groupings[ word ] = list_of_words.count( word ) }
end
## map
def group_by_matches( match_str, list_of_words )
to_match = match_str.strip.split.uniq
to_match.
map { | word | [ word, list_of_words.count( word ) ] }.to_h
end
Gauge your method preferences depending on performance, readability, and reliability.
list.each do |i|
counter = 0
swerve.each do |j|
if i.include? j
counter += 1
needs to be changed to
swerve.each do |i|
counter = 0
list.each do |j|
if i.include? j
counter += 1
Your code is telling how many times each word in the word/string (the word which is included in the dictionary) appears.
If you want to tell how many times each word in the dictionary appears, you can switch the list.each and swerve.each loops. Then, it will return a hash # => {"just"=>0, "do"=>3, "it"=>0, "to"=>3}

Can't convert String onto integer (TypeError)

Following code return error "Can't convert String onto integer", please help
subject = ['eng','Math','Sci']
grade = ['grade 1','grade 2','grade 3']
subject.each do |sub|
puts ("some string")
grade[sub] .each do |grd|
puts ("some string")
end
end
grade[sub] .each do |grd| thats the problem.
Array elements are accessed by using a index of integer or a range of integers.
You are trying to access a array element by using the variable stored in sub. Since this is a ordinary .each loop it will loop all the elements in the array, in this case 'eng','Math','Sci'. If you want the position of for example 'eng' you could use a .each_with_index
it should probably just be
grade.each do |grd|
with each_with_index it would be
subject.each_with_index do |sub, index|
print sub
print grade[index]
end
If you want a subject -> grade collection it might be good to look into using a Hash like Dave Newton said.
{"eng" => "grade 1","Math" => "grade 2","Sci" => "grade 3"}.each do |subject, grade|
puts "#{subject| #{grade}"
end
When you do
grade[sub] .each do |grd|
Ruby expects sub to be using an integer to reference a position in the list of grades.
subject and grade are both arrays. They can only be accessed by their position. In your each loop, the block gets the actual element (in sub), not the position. So in line 5, you are trying to access grade['eng'], which will not work and produces the error. Note that unlike in e.g. PHP, an array and a hash (an associative array) are different things.
Guessing from your code, you might want to use each_index instead of each which will pass the index number to the block instead of the element.
I'm not sure I understand what you're trying to achieve; however, if you'd like to print subjects and grades and you're sure about the relative order of elements in the arrays, you could do this:
subject = ['eng','Math','Sci']
grade = ['grade 1','grade 2','grade 3']
subject.each_with_index do |sub, idx|
puts "#{sub} - #{grade[idx]}"
end
Output:
eng - grade 1
math - grade 2
sci - grade 3
An hash is however probably more suitable to your needs.

syntax for .max/.min and sum of hash values

Trying to get a value out of 2d array inside of a hash and determine its max or min value
This is what I have so far
pretend_hash = { 333 => [Dog,19.99], 222=> [Cat,25.55] }
if a == 5 # Loop for view highest priced product"
puts "View highest priced product"
puts pretend_hash.values.max
end
So this returns the highest alphabetical value in the first part of the array by default
Which would be Dog. How do I go about getting access to the 2nd part of the array where 25.55 gets declared the .max value? Something like pretend_hash.values.max[|k,v,z| print z]
or a reverse on something?
The other problem I'm having is iterating through 2nd hash elements and determining the sum. Again callling that 2nd element in the array is something I'm not able to find the syntax for. I mean its not hard to say 19.99+25.55 = some variable and slam it into a puts. I'm assuming its something like:
pretend_hash.sum{|k,v,z| ?? }
#I assume it iterates through the z element
#and adds all current z values and returns the sum?
Min/max can be solved like this:
pretend_hash.values.sort{|x,y| x[1] <=> y[1]}[0] # Gives min, -1 will be max
Or:
pretend_hash.values.map{|x| x[1]}.max
And sum can be had like this:
pretend_hash.values.inject(0){|sum,x| sum+x[1]}
pretend_hash = { 333 => ["Dog",19.99], 222=> ["Cat",25.55] }
key,value = pretend_hash.max{|a,b| b[1] <=> a[1]}
puts key.to_s
puts value.join(',')
puts pretend_hash.inject(0){|sum, hash| sum + hash[1][1]}.to_s
#returns:
#222
#Cat,25.55
#45.54

Resources