Understanding hashes - ruby

An exercise says:
Create three hashes called person1, person2, and person3, with first
and last names under the keys :first and :last. Then create a params
hash so that params[:father] is person1, params[:mother] is person2,
and params[:child] is person3. Verify that, for example,
params[:father][:first] has the right value.
I did
person1 = {first: "Thom", last: "Bekker"}
person2 = {first: "Kathy", last: "Bekker"}
person2 = {first: "Anessa", last: "Bekker"}
then the params hash in Rails
params = {}
params[:father] = person1
params[:mother] = person2
params[:child] = person3
Now I can ask for father, mother or child's first or last name like so
params[:father][:first] gives me "Thom".
What makes params[:father][:first][:last] return an error? Is there a way to make that return "Thom Bekker"?
I have no way to check if the way I came up with is correct, is there a better way to do the exercise?
Is there a reason why symbol: is better than symbol =>?

Your Return Value is a Single Hash Object
You're misunderstanding the type of object you're getting back. params[:father] returns a single Hash object, not an Array or an Array of hashes. For example:
params[:father]
#=> {:first=>"Thom", :last=>"Bekker"}
params[:father].class
#=> Hash
So, you can't access the missing third element (e.g. :last) because there's no such element within the value of params[:father][:first].
Instead, you could deconstruct the Hash:
first, last = params[:father].values
#=> ["Thom", "Bekker"]
or do something more esoteric like:
p params[:father].values.join " "
#=> "Thom Bekker"
The point is that you have to access the values of the Hash, or convert it to an Array first, rather than treating it directly like an Array and trying to index into multiple values at once.

In Ruby, using square brackets on the Hash or other classes is actually using a method available for an object of that class (this one). When you call these methods in your example, each of these methods will be called and will return its result before the next method is called. So, as you've defined it:
Calling [:father] on params returns the hash represented by person1
[:first] is then called on {first: "Thom", last: "Bekker"}, returning the corresponding value in the hash, "Thom"
[:last] is called on "Thom", which results in an error. Calling square brackets on a string with an integer between them can access the character in a string at that index (person1[:first][0] returns "T"), but "Thom" doesn't have a way of handling the :last symbol inside the square brackets.
There are a number of ways you could get the names printed as you wanted, one of the simplest being combining the string values in person1:
params[:father][:first] + " " + params[:father][:last]
returns "Thom Bekker"

So here's my question, what exactly makes params[:father][:first][:last] return an error? Is there a way to make that return "Thom Bekker"?
Both of these would work
params[:father][:first] + params[:father][:last]
params[:father].values.join(' ')
But maybe it would be better to think about them like the nested structures that they are:
father = params[:father]
name = father[:first] + father[:last]
puts name
To answer your last question, pretend that there's no difference between a hashrocket => and symbol:. This is one where you don't need to care for a long time. Maybe around year 2 start asking this again, but for learning treat them as if they were equivalent.
(Full disclosure, there are differences, but this is a holy war that you really don't want to see played out)

Related

How to sort an array in Ruby

Persoane = []
Nume = gets
Persoane.push Nume.split(",")
puts Persoane.sort
I am trying to get an user to input carachters that get split into substrings which get inserted in an array, then the program would output the strings in alphabetical order. It doesnt seem to work and I just get the array's contents, like so:
PS C:\Users\Lenovo\Desktop\Ruby> ruby "c:\Users\Lenovo\Desktop\Ruby\ruby-test.rb"
Scrie numele la persoane
Andrei,Codrin,Bradea
Andrei
Codrin
Bradea
PS C:\Users\Lenovo\Desktop\Ruby>
you can do this :
Nume = gets
puts Nume.split(",").sort
or in 1 line
array = gets.chomp.split(",").sort
The error is because of your use of push. Let's assume that you define the constant Nume by
Nume='Andrei,Codrin,Bradea'
Then, Nume.split(',') would return the Array ['Andrei', 'Codrin', 'Bradea']. When you do a Persoane.push, the whole array is added to your array Persoane as a single element. Therefore, Persoane contains only one Element, as you can verify when you do a
p Persoane
If you sort a one-element array, the result will also be just that one element - there is nothing to sort.
What you can do is using concat instead of push. This would result in Persoane being a 3-element array which can be sorted.
I'm not sure you need use constants here
If you don't need keep user input and use it somewhere, you can just chain methods like this
persons = gets.chomp.split(",").sort
For something a little different, let's not split at all.
people = gets.scan(/[^,]+/).map(&:strip).sort
This will avoid problems like multiple commas in a row yielding empty strings. Of course, you could also avoid that with:
people = gets.split(/\,+/).map(&:strip).sort

How to check if nested hash attributes are empty

I have a Hash
person_params = {"firstname"=>"",
"lastname"=>"tom123",
"addresses_attributes"=>
{"0"=>
{"address_type"=>"main",
"catalog_delivery"=>"0",
"street"=>"tomstr",
"city"=>"tomcity"
}
}
}
With person_params[:addresses_attributes], I get:
# => {"0"=>{"address_type"=>"main", "catalog_delivery"=>"0", "street"=>"tomstr", "zip"=>"", "lockbox"=>"", "city"=>"tomcity", "country"=>""}}
1) How can I get a new hash without the leading 0?
desired_hash = {"address_type"=>"main", "catalog_delivery"=>"0", "street"=>"tomstr", "zip"=>"", "lockbox"=>"", "city"=>"tomcity", "country"=>""}
2) How can I check whether the attributes in the new hash are empty?
Answer 1:
person_params[:addresses_attributes]['0']
Answer 2:
hash = person_params[:addresses_attributes]['0']
hash.empty?
This looks just like a params hash from Rails =D. Anyway, it seems that your addresses_attributes contains some nested attributes. This means that what you have in practice is more of an array of hashes than a single hash, and that's what you see right? Instead of it being an actually Ruby Array, it is a hash with the index as a string.
So how do you get the address attributes? Well if you only want to get the first address, here are some ways to do that:
person_params[:addresses_attributes].values.first
# OR
person_params[:addresses_attributes]["0"]
In the first case, we will just take the values from the addreses_attributes hash, which gives us an Array from which we can take the first item. If there are no values in addresses_attributes, then we will get nil.
In the second case, we will just ask for the hash value with the key "0". If there are no values in addresses_attributes, we will get nil with this method also. (You might want to avoid using the second case, if you are not confident that the addresses_attributes hash will always be indexed from "0" and incremented by "1")

Ruby object references vs collection references

I was going through The Well Grounded Rubyist and got confused by the following example.
Suppose we have an array of strings:
numbers = ["one", "two", "three"]
If I freeze this array, I can't do the following:
numbers[2] = "four"
That statement is a Runtime error, but this:
numbers[2].replace("four")
is not.
The book explains that in the first of the last two statements, we are trying to access the array. That's what I found confusing because I thought we are trying to access the third element of the array, which is a string object. And how is that different from the last statement?
It's different because in the statement that works you are calling String#replace. As you might expect, a call to Array#replace will fail.
numbers.replace [1,2,3]
TypeError: can't modify frozen array
The object reference at any given array index might be arbitrarily complicated and it's not the job of the frozen array to keep those objects from changing ... it just wants to keep the array from changing. You can see this:
ree-1.8.7> numbers[2].object_id
=> 2149301040
ree-1.8.7> numbers[2].replace "four"
=> "four"
ree-1.8.7> numbers[2].object_id
=> 2149301040
numbers[2] has the same object_id after String#replace runs; the Array did not actually change.
An array is a list of object_id's. String#replace is special - it changes the string but it keeps the object_id. So the list of object_id's does not change and the Array does not detect any change.
You can freeze every string of the array. String#replace would then result in an error.

How do I generate a hash from an array

I'm trying to build a hash from an array. Basically I want to take the unique string values of the array and build a hash with a key. I'm also trying to figure out how to record how many times that unique word happens.
#The text from the .txt file:
# **Bob and George are great! George and Sam are great.
#Bob, George, and sam are great!**
#The source code:
count_my_rows = File.readlines("bob.txt")
row_text = count_my_rows.join
puts row_text.split.uniq #testing to make sure array is getting filled
Anyways I've tried http://ruby-doc.org/core/classes/Hash.html
I think I need to declare a empty hash with name.new to start I have no idea how to fill it up though. I'm assuming some iteration through the array fills the hash. I'm starting to think I need to record the value as a separate array storing the time it occurs and the word then assign the hash key to it.
Example = { ["Bob",2] => 1 , ["George",3], =>2 }
Leave some code so I can mull over it.
To get you started,
h={}
h.default=0
File.read("myfile").split.each do |x|
h[x]+=1
end
p h
Note: this is not complete solution

Troubles: Matrices, Vectors and Arrays

As far as I understood, matrices are very inflexible to work with. Therefor, I'm trying to get an array of vectors do deal with. My needs are: to be able to add vectors and make arithmetical operations on their components. Writing the code below,
require 'matrix'
x = Matrix.rows( IO.readlines("input.txt").each {|line| line.split} )
puts x.row_vectors
ruby falls into an exception. Why?
matrix.rb:1265:in `to_s': undefined method `join' for "1.2357 2.1742 -5.4834 -2.0735":String (NoMethodError)
OK then, I've calmed down and tried another approach. I wrote:
a = Array.[]( IO.readlines("input.txt").each {|line| Vector.[](line.split) } )
But the only way I can access my vectors inside an array is adressing the second index:
puts a[0][0]
This means, that when I would like to access desired scalar inside a vector, I'll will be forced to use the third index, like:
puts a[0][0][1]
So, the second question is - where the hell that second index comes from? How to get rid of it? Am I missing something when reading data into array?
I can't reproduce your first problem. Extracting what looks like input.txt, I can execute that first expression without an exception.
As to the second question, your expression seems kind of complex. How about:
b = IO.readlines("input.txt").map { |x| x.split(' ') }
This will get you a "2D" array of arrays, and you will need only two subscripts. (As to your question about where did the extra array come from, you got one from the Array constructor, one from IO.readlines, and one from the Vector constructor . . . I think.)
Or maybe:
result = []
IO.foreach('input.txt') { |ln| result << ln.split(' ') }

Resources