Retrieving values from corresponding keys in Ruby hash - ruby

The following only prints white lines. If this isn't the way to retrieve values from keys in Ruby, what is?
numbers = []
for i in 1..100 do
hash = {
:FizzBuzz => 1,
:Prime => 3,
:Fibonacci => 5
}
numbers << { i => hash }
end
numbers.each do |number|
puts number[:Prime]
end
Note this is a MCVE, in the final application 1, 3 and 5 will be function calls.
Try it
For those wondering what I was trying to do, the final (non-MCVE) result can be found on Code Review.

After the first loop, numbers is an array like this:
[
{ 1 => { :FizzBuzz => 1, :Prime => 3, :Fibonacci => 5 } },
{ 2 => { :FizzBuzz => 1, :Prime => 3, :Fibonacci => 5 } },
...
{ 100 => { :FizzBuzz => 1, :Prime => 3, :Fibonacci => 5 } }
]
number[:Prime] is trying to fetch the :Prime element out of your first array element, but you only have the key 1.
The end result is, you print empty lines, as nothing is found at each iteration. Hash access is okay; it is the logic of your code that is the problem (and you did not explain what you are trying to do precisely).

Each number is a hash of this form:
{1=>{:FizzBuzz=>1, :Prime=>3, :Fibonacci=>5}}
which has a number as the sole key. When you look for a hash key that does not exist using Hash#[], Ruby returns nil. And puts nil prints a blank line.
Indeed Hash#[] is the way to retrieve values from a hash.

The keys of number are all integers (coming from i). The symbol :Prime is not a key. Therefore the results are all nil.

Related

Testing method that needs to sort hash by value in RSpec

I am trying to test my method that takes a hash and re-orders it. I currently have:
def sort_views
help = view_counter.sort_by { |route, length| length }.reverse.to_h
p help #just so I can see the output in the test
end
Then for my test I have:
describe "#sort_views" do
let(:result) do {
"/help_page/1" => 3,
"/about/2" => 1,
"/contact" => 1,
"/home" => 1,
"/index" => 2,
} end
it "then sorts them via the view count" do
expect(subject.sort_views).to be(result)
end
end
My issue is that the test passes.... However, I purposely have the order wrong at the moment in the result. Yet the p help in the method CORRECTLY re-orders it, so the output is actually different. I have tried eq and eql but I believe they just test structure? I know be and equal won't work...
For context my p help outputs: {"/help_page/1"=>3, "/index"=>2, "/about/2"=>1, "/home"=>1, "/contact"=>1} when the test is run.
So is there a way for me to say test the order of the result hash in my test too without calling .sort_by { |route, length| length }.reverse.to_h on my result variable as well??
That's because the hashes are the same, see this post. Hashes don't really have a concept of order, when you call the sort_by method it is converting the data to an array, ordering the array, and then returning an array. If you are converting the array to a hash you will lose the order essentially.
If you care about the order here, remove the to_h and just deal with the data as an array instead. Then your tests should work.
You can use .to_a in the test, and you don't need reverse in the sort.
view_counter.sort_by { |route, length| -length }
let(:result) do
{
"/help_page/1" => 3,
"/about/2" => 1,
"/contact" => 1,
"/home" => 1,
"/index" => 2,
}.to_a
end

Difficulty when constructing a nested data structure

While trying to create a JSON message for an API, I found myself struggling to do something that I thought would be simple. I needed to create a message like the following:
{ "list": [ { "foo": 1, "bar": 2 } ] }
However, my first attempt did not work:
say to-json { foo => [ { a => 1, b => 2 } ] };
# {"foo":[{"a":1},{"b":2}]}
Trying to simplify things further confused me more:
say { foo => [ { a => 1 } ] };
# {foo => [a => 1]}
# Note that this is not JSON, but I expected to see curly braces
Then I tried to use some temporary variables, and that worked:
my #list = { a => 1 };
say to-json { foo => #list };
# {"foo":[{"a":1}]}
my %hash = ( a => 1 );
say to-json { foo => [ %hash ] };
# {"foo":[{"a":1}]}
What's going on here?
And is there a way I can achieve my desired output without an extra temporary variable?
You've discovered the single argument rule. Numerous constructs in Raku will iterate the argument they are provided with. This includes the [...] array composer. This is why when we say:
say [1..10];
We get an array that contains 10 elements, not 1. However, it also means that:
say [[1,2]];
Iterates the [1,2], and thus results in [1,2] - as if the inner array were not there. A Hash iterates to its pairs, thus:
{ foo => [ { a => 1, b => 2 } ] }
Actually produces:
{ foo => [ a => 1, b => 2 ] }
That is, the array has the pairs. The JSON serializer then serializes each pair as a one-element object.
The solution is to produce a single-element iterable. The infix , operator is what produces lists, so we can use that:
say to-json { foo => [ { a => 1, b => 2 }, ] };
# note the , here ^
Then the single argument to be iterated is a 1-element list with a hash, and you get the result you want.
Easy way to remember it: always use trailing commas when specifying the values of a list, array or hash, even with a single element list, unless you actually are specifying the single iterable from which to populate it.

Ruby inject condition

I have an array filled with hashes. The data structure looks like this:
students = [
{
"first_name" => "James",
"last_name" => "Sullivan",
"age" => 20,
"study_results" => {"CAR" => 1, "PR1" => 1, "MA1" => 1, "BEN" => 2, "SDP" => nil}
}
]
I want to find students with the mark 1 from at least two subjects.
I tried to convert the hash with marks into an array and then use the inject method to count the number of 1 and find out if the number is > 1:
students.select{|student
(
(student["study_results"].to_a)
.inject(0){|sum, x| sum += 1 if x.include?(1)}
) > 1
}
Is there any way to put a condition into the method, or should I find a different way to solve it?
I commend you for attempting to solve this before posting, but you made it unnecessarily complicated. I'd write it like this:
students.select{|student| student['study_results'].values.count(1) >= 2}
That's all, no need for inject. You were misusing it here.
Ruby collections have TONS of useful methods. If you find yourself using inject or each, there's a better method for this, 90% of the time.
Explanation:
student['study_results'] # => {"CAR"=>1, "PR1"=>1, "MA1"=>1, "BEN"=>2, "SDP"=>nil}
student['study_results'].values # => [1, 1, 1, 2, nil]
student['study_results'].values.count(1) # => 3
student['study_results'].values.count(2) # => 1
student['study_results'].values.count(3) # => 0
student['study_results'].values.count(nil) # => 1
Documentation
Hash#values
Array#count

return an array of keys from hash when values match pattern

I am trying to run through the following hash
my_family_pets_ages = {"Evi" => 6, "Hoobie" => 3, "George" => 12, "Bogart" => 4, "Poly" => 4, "Annabelle" => 0, "Ditto" => 3}
and return an array of the keys whose values match a specified integer for age. So, for example, if I wanted to find all of the pets that are 3 years old, it would return an array of just their names.
["Hoobie", "Ditto"]
I have the following method, but I can't seem to get the method to return an array of just the keys, but I keep just getting the key => value in an array like this:
["Hoobie"=>3, "Ditto"=>3]
Here is the method I have so far
def my_hash_finding_method(source, thing_to_find)
source.select {|name, age| name if age == thing_to_find}
end
Any pointers? I'm stuck on how to return only the keys
Just use #select, then #keys to get an array of the matching keys:
def my_hash_finding_method(source, thing_to_find)
source.select { |name, age| age == thing_to_find }.keys
end
See Hash#keys for more information.

How to get the first key and value pair from a hash table in Ruby

I'm trying to get the first key and value key from a hash table in ruby. I don't know the key values of the hash because it is passed to the method. I cant find anywhere online how to find the first key/value as a separate hash table.
I think hash[0] will just try to find an element with a name 0 it just returns nil when I run the code.
I know I can find the key name and the value and then create a new hash from them but i wonder if there is an easier way to do this so I get a hash right away.
here is my code:
def rps_game_winner(game)
rock_in_hash = game.invert['R']
paper_in_hash = game.invert['P']
scissors_in_hash = game.invert['S']
if(rock_in_hash)
if(paper_in_hash)
return paper_in_hash;
elsif(scissors_in_hash)
return rock_in_hash
end
elsif(paper_in_hash)
if(rock_in_hash)
return paper_in_hash
elsif(scissors_in_hash)
return scissors_in_hash
end
end
key = game.keys[-1]
value = game.values[-1]
winner = {key => value}
return winner
end
game_one = { "Bob" => 'P', "Jim" => 'P' }
puts rps_game_winner(game_one)
This gets me the correct result the problem is I don't understand why it's -1 instead of zero...
And i was hoping there was a better way to get the first key/value pair of a hash table instead of creating new hash table with the key and value you retrieved from the previous table.
You can just do
key, value = hash.first
or if you prefer:
key = hash.keys[0]
value = hash.values[0]
Then maybe:
new_hash = {key => value}
There is a shorter answer that does not require you to use extra variables:
h = { "a" => 100, "b" => 200 , "c" => 300, "d" => 400, "e" => 500}
Hash[*h.first] #=> {"a" => 100}
Or if you want to retrieve a key/value at a any single position
Hash[*h.to_a.at(1)] #=> {"b" => 200}
Or retrieve a key/values from a range of positions:
Hash[h.to_a[1,3]] #=> {"b"=>200, "c"=>300, "d"=>400}
[hash.first].to_h
Another way to do it.
my_hash = { "a" => "first", "b" => "second" }
{ my_hash.keys.first => my_hash.values.first }
This works too

Resources