I seem to be getting something elementary wrong when trying to set and or access these variables in my Quote instance when i call Quote#scrape_quote. I can see that the values are scraped and saved to the scraped_values hash just fine, I just can't access the variables, when I call quote.lives for e.g. I get nil.
What am i getting wrong here?
quote.rb
class Quote < ApplicationRecord
require 'watir'
attr_accessor :lives, :benefit, :payment
def scrape_quote
rows = #browser.trs
quote_rows = rows[1..8]
scraped_values = {}
quote_rows.each do |row|
scraped_values[row.tds[0].text] = row.tds[1].text
end
#lives = scraped_values[0]
#benefit = scraped_values[1]
#payment = scraped_values[2]
puts scraped_values
end
end
scraped_values is a hash not an array. You are trying to access it as if it were an array.
Use whatever is in row.tds[0].text to reference the hash:
h = {a:1,b:2}
h[:a]
=> 1
The general case
If you just want the values of the hash sequentially assigned to member variables, you can use parallel assignment from the hash#values return value like this:
2.4.1 :001 > h = {}
=> {}
2.4.1 :002 > h[:one] = 1
=> 1
2.4.1 :003 > h[:two] = 2
=> 2
2.4.1 :004 > h[:three] = 3
=> 3
2.4.1 :005 > #one, #two, #three = h.values
=> [1, 2, 3]
2.4.1 :006 > #one
=> 1
2.4.1 :007 > #two
=> 2
2.4.1 :008 > #three
=> 3
2.4.1 :009 >
Specific application
The specific code in your case would turn into:
class Quote < ApplicationRecord
require 'watir'
attr_accessor :lives, :benefit, :payment
def scrape_quote
rows = #browser.trs
quote_rows = rows[1..8]
scraped_values = {}
quote_rows.each do |row|
scraped_values[row.tds[0].text] = row.tds[1].text
end
#lives, #benefit, #payment = scraped_values.values
puts scraped_values
end
end
The idiomatic way to build the hash would be to use map instead of each and do not use upfront declarations of local variables.
scraped_values = quote_rows.map do |row|
[row.tds[0].text, row.tds[1].text]
end.to_h
Instead of scraped_values[0] you need something like this: scraped_values[scraped_values.keys[0]], because scraped_values isn't array and 0, 1, 2 is like any other missing key, so hash returns nil.
Related
I need some help with my +/- overridden method, it is not affecting the elements of wektor array.
class Wektor
attr_accessor :coords
def initialize(length)
#coords = Array.new(length, 0)
end
def set!(w)
#coords = w.dup
self
end
%i(* /).each do |op|
define_method(op) do |n|
coords.map! { |i| i.send(op, n) }
self
end
end
%i(- +).each do |op|
define_method(op) do |v|
#coords.zip(v).map { |a, b| a.send(op, b) }
self
end
end
def shift_right!
coords.rotate!
coords[0] = 0
self
end
end
So basically if a = Wektor.new(4).set!([1,2,3,4] and b = Wektor.new(4).set!([1,1,1,1] I want a-b to set a = [0,1,2,3] What is wrong with this method? It is not changing anything - a is still set to [1,2,3,4].
I tried debugging with IRB but it doesn't give me any clue on what is wrong.
The code looks good to me, but I'm a beginner when it comes to writing ruby-way code (I'm not the author of this piece of code) and I have trouble spotting the error.
* and / is working OK, the vector is supposed to be multiplied/divided by scalar.
Try this instead:
%i(- +).each do |op|
define_method(op) do |v|
#coords = #coords.zip(v.coords).map { |a, b| a.send(op, b) }
self
end
end
You want to zip the array, so calling coords on v makes that work. Also, map performs the given block and returns the collected results, you were discarding them.
Are you aware that Ruby has a Vector class?
2.1.5 :001 > require 'matrix'
=> true
2.1.5 :002 > a = Vector[1,2,3,4]
=> Vector[1, 2, 3, 4]
2.1.5 :003 > b = Vector[1,1,1,1]
=> Vector[1, 1, 1, 1]
2.1.5 :004 > a - b
=> Vector[0, 1, 2, 3]
2.1.5 :005 > a * 3
=> Vector[3, 6, 9, 12]
I have been making a chess game and I need some help with hashes. Specifically how do I automatically name a hash table symbol using an iterator 'i'
8.times do |i = 0, x = 0|
i += 1
x += 1
pawnHash[:P] = "P#{i}",Pawn.new(x,2,"P#{i}","black")
end
puts pawnHash
the symbol should look like this:
:P1. But is seems impossible to name a hash using the variable 'i'
The full set of 8 symbols should look like this: :P1, :P2, :P3 ... etc.
I tried doing :P + i when declaring the key/value pair, but I got a syntax error due to the '+' sign.
Are you trying to make the key a symbol?
You can do hash["P#{i}".to_sym]
2.0.0-p247 :016 > i = 2
=> 2
2.0.0-p247 :017 > h = {}
=> {}
2.0.0-p247 :018 > h["P#{i}".to_sym] = "value"
=> "value"
2.0.0-p247 :019 > h
=> {:P2=>"value"}
2.0.0-p247 :020 > h.keys.first.class
=> Symbol
Or you can do :"P#{i}"
You can simplify your loop and make it more Ruby-like:
pawn_hash = {}
8.times { |i| pawn_hash["P#{ i + 1 }".to_sym] = "P#{ i + 1}" }
pawn_hash
# => {:P1=>"P1",
# [...]
# :P8=>"P8"}
You could avoid using i + 1 by assigning it to an intermediate variable if you want to play the DRY game:
pawn_hash = {}
8.times do |i|
c = i + 1
pawn_hash["P#{ c }".to_sym] = "P#{ c }"
end
pawn_hash
# => {:P1=>"P1",
# [...]
# :P8=>"P8"}
Or, use a different loop:
pawn_hash = {}
1.upto(8) { |i| pawn_hash["P#{ i }".to_sym] = "P#{ i }" }
pawn_hash
# => {:P1=>"P1",
# [...]
# :P8=>"P8"}
In Ruby we use snake_case, instead of camelCase, for variable and method names. Classes and Modules get camelCase.
Also, meditate on these:
pawn_hash = 8.times.map { |i| ["P#{ i + 1 }".to_sym, "P#{ i + 1}"] }.to_h
# => {:P1=>"P1",
# :P2=>"P2",
# :P3=>"P3",
# :P4=>"P4",
# :P5=>"P5",
# :P6=>"P6",
# :P7=>"P7",
# :P8=>"P8"}
pawn_hash = Hash[8.times.map { |i| ["P#{ i + 1 }".to_sym, "P#{ i + 1}"] }]
# => {:P1=>"P1",
# :P2=>"P2",
# :P3=>"P3",
# :P4=>"P4",
# :P5=>"P5",
# :P6=>"P6",
# :P7=>"P7",
# :P8=>"P8"}
# :P8=>"P8"}
It's not necessary to loop and assign to the hash. Instead, it's very Ruby-like to do it all in one pass. The times method is an iterator. map can iterate over that and will return the block values for each iteration. to_h is a more modern way in Ruby to convert an array to a hash, just as is using Hash[...].
I am writing a TFIDF program - all of which should be okay, but I'm having a small (or large..) problem with the hashes working as intended.
To keep this short, the code at hand is:
#Word matrix is an array that contains hashes (obviously)
#i've done some stuff before this and these are working as expected
puts word_matrix[3][:yahoo] # => 2
puts word_matrix[100][:yahoo] # => 0
puts $total_words_hash[:yahoo] #=> 0
#Essentially, this block is taking a hash of all the words (values = 0) and trying
#to run through them adding the only the values of the other hash to the temporary
#and then setting the temp to the old hash position (so that there are 0 values
#and the values occurring in that document.. yet, it assigns the same values to
#ALL of the hashes of word_matrix[]
#now we run this block and everything breaks down for some reason..
for i in 0...word_matrix.size
tmp_complete_words_hash = $total_words_hash #all values should be zero...
word_matrix[i].each do |key,val| #for each key in the hash we do this..
tmp_complete_words_hash[key] = val
end
word_matrix[i] = tmp_complete_words_hash
end
puts word_matrix[3][:yahoo] # => 2
puts word_matrix[100][:yahoo] # => 2 -- THIS SHOULD BE 0 Still...
Could anyone shed any light as to why this is assigning the same values to ALL the hashes of the array? It is as if tmp_complete_words_hash is not being reset everytime.
You need to clone the hash.
tmp_complete_words_hash = $total_words_hash.clone
Otherwise, both variables are pointing to the same hash, and you're constantly modifying that hash.
In fact, most objects in Ruby are like this. Only a few (such as numerics, strings) aren't.
Try this in the IRB:
class MyClass
attr_accessor :value
end
x = MyClass.new
y = x
x.value = "OK"
puts y.value
why this is assigning the same values to ALL the hashes of the array?
There is only one hash. You are assigning the same hash (the one pointed to by $total_words_hash) to every element in the array:
tmp_complete_words_hash = $total_words_hash
Here, you make tmp_complete_words_hash point to the same object as $total_words_hash
word_matrix[i] = tmp_complete_words_hash
And here you assign that hash to every element of the array.
When you assign a hash variable to another hash variable. It will reference the same memory location, if you change one hash, same will be reflected to another hash.
total_words_hash = {}
tmp_complete_words_hash = total_words_hash
1.9.3 (main):0 > total_words_hash.object_id
=> 85149660
1.9.3 (main):0 > tmp_complete_words_hash.object_id
=> 85149660
total_words_hash[:test] = 0
1.9.3 (main):0 > tmp_complete_words_hash
=> {
:test => 0
}
1.9.3 (main):0 > tmp_complete_words_hash[:test_reverse] = 1
=> 1
1.9.3 (main):0 > tmp_complete_words_hash
=> {
:test => 0,
:test_reverse => 1
}
So you can create a duplicate hash for this purpose using hash method dup.
1.9.3 (main):0 > tmp_complete_words_hash = total_words_hash.dup
1.9.3 (main):0 > total_words_hash.object_id
=> 85149660
1.9.3 (main):0 > tmp_complete_words_hash.object_id
=> 97244920
In your case just use.
tmp_complete_words_hash = $total_words_hash.dup
>> a = 5
=> 5
>> b = a
=> 5
>> b = 4
=> 4
>> a
=> 5
how can I set 'b' to actually be 'a' so that in the example, the variable a will become four as well. thanks.
class Ref
def initialize val
#val = val
end
attr_accessor :val
def to_s
#val.to_s
end
end
a = Ref.new(4)
b = a
puts a #=> 4
puts b #=> 4
a.val = 5
puts a #=> 5
puts b #=> 5
When you do b = a, b points to the same object as a (they have the same object_id).
When you do a = some_other_thing, a will point to another object, while b remains unchanged.
For Fixnum, nil, true and false, you cannot change the value without changing the object_id. However, you can change other objects (strings, arrays, hashes, etc.) without changing object_id, since you don't use the assignment (=).
Example with strings:
a = 'abcd'
b = a
puts a #=> abcd
puts b #=> abcd
a.upcase! # changing a
puts a #=> ABCD
puts b #=> ABCD
a = a.downcase # assigning a
puts a #=> abcd
puts b #=> ABCD
Example with arrays:
a = [1]
b = a
p a #=> [1]
p b #=> [1]
a << 2 # changing a
p a #=> [1, 2]
p b #=> [1, 2]
a += [3] # assigning a
p a #=> [1, 2, 3]
p b #=> [1, 2]
You can't. Variables hold references to values, not references to other variables.
Here's what your example code is doing:
a = 5 # Assign the value 5 to the variable named "a".
b = a # Assign the value in the variable "a" (5) to the variable "b".
b = 4 # Assign the value 4 to the variable named "b".
a # Retrieve the value stored in the variable named "a" (5).
See this article for a more in-depth discussion of the topic: pass by reference or pass by value.
As has been noted the syntax you are using can not be done. Just throwing this out there though you could make a wrapper class it depends what you actually want to do
ruby-1.8.7-p334 :007 > class Wrapper
ruby-1.8.7-p334 :008?> attr_accessor :number
ruby-1.8.7-p334 :009?> def initialize(number)
ruby-1.8.7-p334 :010?> #number = number
ruby-1.8.7-p334 :011?> end
ruby-1.8.7-p334 :012?> end
=> nil
ruby-1.8.7-p334 :013 > a = Wrapper.new(4)
=> #<Wrapper:0x100336db8 #number=4>
ruby-1.8.7-p334 :014 > b = a
=> #<Wrapper:0x100336db8 #number=4>
ruby-1.8.7-p334 :015 > a.number = 6
=> 6
ruby-1.8.7-p334 :016 > a
=> #<Wrapper:0x100336db8 #number=6>
ruby-1.8.7-p334 :017 > b
=> #<Wrapper:0x100336db8 #number=6>
You can use arrays:
a = [5]
b = a
b[0] = 4
puts a[0] #=> 4
This idea is based on this answer.
Just for the sake of reference.
>> a = 5
=> 5
>> a.object_id
=> 11
>> b = a
=> 5
>> b.object_id
=> 11
>> b = 4
=> 4
>> b.object_id
=> 9
>> a.object_id
=> 11
# We did change the Fixnum b Object.
>> Fixnum.superclass
=> Integer
>> Integer.superclass
=> Numeric
>> Numeric.superclass
=> Object
>> Object.superclass
=> BasicObject
>> BasicObject.superclass
=> nil
I hope this gives us all a little better understanding about objects in Ruby.
One option in cases where you feel you would like to have direct pointer operations is to use the replace method of Hashes, Arrays & Strings.
this is useful for when you would like to have a method return a variable that a proc the method sets up will change at a later date, and don't want the annoyance of using a wrapper object.
example:
def hash_that_will_change_later
params = {}
some_resource.on_change do
params.replace {i: 'got changed'}
end
params
end
a = hash_that_will_change_later
=> {}
some_resource.trigger_change!
a
{i: 'got changed'}
It's probably better generally to use explicit object wrappers for such cases, but this pattern is useful for building specs/tests of asynchronous stuff.
I'm no Ruby expert. But for a technically crazy kluge...that would only work if you felt like going through eval every time you worked with a variable:
>> a = 5
=> 5
>> b = :a
=> :a
>> eval "#{b} = 4"
=> 4
>> eval "#{a}"
=> 4
>> eval "#{b}"
=> 4
Note that a direct usage of b will still give you :a and you can't use it in expressions that aren't in eval:
>> b
=> :a
>> b + 1
NoMethodError: undefined method `+' for :a:Symbol
...and there are certainly a ton of caveats. Such as that you'd have to capture the binding and pass it around in more complex scenarios...
'pass parameter by reference' in Ruby?
#Paul.s has an answer for if you can change the point of declaration to be a wrapper object, but if you can only control the point of reference then here's a BasicReference class I tried:
class BasicReference
def initialize(r,b)
#r = r
#b = b
#val = eval "#{#r}", #b
end
def val=(rhs)
#val = eval "#{#r} = #{rhs}", #b
end
def val
#val
end
end
a = 5
puts "Before basic reference"
puts " the value of a is #{a}"
b = BasicReference.new(:a, binding)
b.val = 4
puts "After b.val = 4"
puts " the value of a is #{a}"
puts " the value of b.val is #{b.val}"
This outputs:
Before basic reference
the value of a is 5
After b.val = 4
the value of a is 4
the value of b.val is 4
Excuse I am newbie in ruby.
My problem is about argument passes by value and reference.
I am coding this method
def show_as_tree(parents)
array = []
iterate_categories(parents, array)
end
def iterate_categories(parents, array)
parents.each do |p|
#return p.description or "-#{p.description} if the node is root or not
p.description = category_name(p)
#add to array
array << p
#call iterate categories with children of parent node and same array
iterate_categories(p.children, array)
end
end
however the array content is only the parent nodes.
I need understand the ruby mechanism for references and how could fix my problem?
I'm pretty sure Ruby just creates a copy of your array. Therefore what you should be doing is having
array << iterate_categories(p.children, new_array)
And return your array at the end of the function.
Just did a quick example: (Updating code based on Wayne Conrad's answer… it is correct)
class Person
attr_accessor :name, :children
end
class Test
def iterate_categories(parents,array)
parents.each do |p|
array << p.name
if !p.children.nil?
iterate_categories(p.children,array)
end
end
end
def iterate_categories_test
p1 = Person.new
p1.name = "Bob"
p2 = Person.new
p2.name = "Joe"
p3 = Person.new
p3.name = "Ann"
p4 = Person.new
p4.name = "John"
p1.children = [p2,p3]
p3.children = [p4]
array = []
iterate_categories([p1],array)
puts array
end
end
Then:
>> a = Test.new
>> a.iterate_categories_test
Bob
Joe
Ann
John
=> nil
Hopefully that helps.
Your show_as_tree method should return the array.
def show_as_tree(parents)
array = []
iterate_categories(parents, array)
array
end
Without array as the last line, the return value of show_as_tree is the return value of iterate_categories, which happens to be parents. That's why it looks like only parents is getting added to array. That's an illusion: It was parents being returned, not array.
Ruby does not make copies of its arguments. It passes references by value. That means it is the same array being acted upon throughout your functions.
I don't quite get the code, the formatting seems all wrong but I'll try to help.
When you pass an argument to ruby, it seems to give the method a copy, not a reference. Fortunately, you don't need to worry about that, as ruby lets you return multiple things in one call.
For example, in irb I ran something like this:
ruby-1.9.2-p180 :005 > def stuff(a, b)
ruby-1.9.2-p180 :006?> c = a + b
ruby-1.9.2-p180 :007?> [c, a]
ruby-1.9.2-p180 :008?> end
=> nil
ruby-1.9.2-p180 :009 > a, b = stuff(1, 2)
=> [3, 1]
ruby-1.9.2-p180 :010 > a
=> 3
ruby-1.9.2-p180 :011 > b
=> 1
That way you can easily return multiple values without problem
I hope I answered your question.