Using Strings as Variable/Object Names in Ruby - ruby

I am dealing with fractals. You start with a rectangle, and that shape is decreased by a given decay rate. I have it set up to do the first 10 iterations of the given scenario, and each scenario looks like this:
y_1 = dec_y(y_1)
y_2 = dec_y(y_2)
a_y = [y_1, y_2]
rect_1 = TkcRectangle.new(canvas, [0,0], a_y)
where dec_y is defined as the following:
def dec_y(y)
to_ret = y / $rate
return to_ret
end
I want to turn the first snippet into a function/method (not exactly sure what the Ruby term is...), so that each iteration will just be a single line referencing a method, which makes the problem more extensible. But, I need each TkcRectangle to have a different name. The way I want to set it up, each TkcRectangle will have the same name. But, if I can set the name of the object to a string passed as an argument, then I should not have a problem.
How do I define the name of an object with a given string?

Edit : Code has not been tested, but will give you the idea.
Instead of naming each element, you can use an array and use the index instead
rectangles_array = Array.new
for each loop
rectangles_array << create_rectangle_object(y_1, y_2, canvas)
end for each loop
def dec_y(y)
to_ret = y / $rate
return to_ret
end
def create_rectangle_object(y_1, y_2, canvas)
return TkcRectangle.new(canvas, [0,0], [dec_y(y_1), dec_y(y_2)])
end
If you really want to name it read about structs.. Something like
MyRectangleStruct = Struct.new(:obj_name, :x1, :y1, :x2, :y2)
puts MyRectangleStruct.new(:obj_name => 'First_rec', .....)

define_method(method_name, &block)
with method_name being any string and &block being a block of ruby code; usually it looks something like this:
define_method(method_name) do
your code goes here
end

Related

Not displaying it's corresponding values with it's key for Hash

Ok i am not here to ask for an answer. But to be honest i am not really good in class variable. So i would appreciate you can guide me along with this piece of code.
I have read on class variable at those docs. I some what kind of understand it. But it comes to applying it for my own use. I would get confused.
class Square
##sqArray = {}
#attr_accessor :length
def initialize
if defined?(##length)
randno = "%s" % [rand(20)]
##length = randno.to_i
##sqArray = ##length
else
randno = "%s" % [rand(20)]
##length = randno.to_i
##sqArray = ##length
end
end
def Area
##area = ##length * ##length
return ##area
##sqArray[##length.to_sym] = ##area
puts ##sqArray
end
end
s1 = Square.new
puts s1.Area
Let me explain this piece of code. Basically every time i create a Square object it would go to initialize method. A random number will be generated and pass it to ##length, and ##length will be assigned to hash ##sqArray as it's key. But now the problem is when i create a new object s1. When i want to display the Area i want to test out to print the hash ##sqArray with it's length as it's key and area as it's value. But now the problem is only returning it's area only. e.g 114 only.
suppose to be e.g [ 24 => 114]
When defining the object's property (i.e. it's length), the correct approach is to use an instance variable, not a class variable. This is because (in your particular example), length is an attribute of a specific square and not something that applies to all squares. Your code should look something like this:
class Square
def initialize(length = rand(20))
#length = length
end
def area
#length * #length
end
end
s1 = Square.new
puts s1.area
Now, I am a little unclear what exactly you aim to achieve by use of that class variable ##sqArray - but for example, you could use this store a list of all defined Squares:
class Square
##squares_list = []
def self.all_known
##squares_list
end
def initialize(length = rand(20))
#length = length
##squares_list << self
end
def area
#length * #length
end
end
This would allow you to write code like:
s1 = Square.new #=> #<Square:0x0000000132dbc8 #length=9>
s2 = Square.new(20) #=> #<Square:0x000000012a1038 #length=20>
s1.area #=> 81
s2.area #=> 400
Square.all_known #=> [#<Square:0x0000000132dbc8 #length=9>, #<Square:0x000000012a1038 #length=20>]
Class variables have some odd behaviour and limited use cases however; I would generally advise that you avoid them when starting out learning Ruby. Have a read through a ruby style guide to see some common conventions regarding best practice - including variable/method naming (use snake_case not camelCase or PascalCase), whitespace, etc.

why return change variables while inside a class

I cannot understand this ruby behavior, the code explains better what I mean:
class DoNotUnderstand
def initialize
#tiny_array = [3,4]
test
end
def messing(ary)
return [ary[0]+=700, ary[1]+=999]
end
def test
puts #tiny_array.join(",") # before => 3,4
messing(#tiny_array)
puts #tiny_array.join(",") # after => 703,1003
end
end
question = DoNotUnderstand.new
#tiny_array was [3,4] and became [703,1003]
if I don't use a class, that happens:
#tiny = [1,2]
def messing(ary)
return [ary[0]+693,ary[1]+999]
end
puts #tiny.join(",") # before => 1,2
messing(#tiny)
puts #tiny.join(",") # after => 1,2
the array simply remains [1,2]
why?
The class is a red herring, and completely irrelevant to the issue.
In the first case, where the array was modified, you defined messing as:
def messing(ary)
return [ary[0]+=700, ary[1]+=999]
end
Whereas in the second case, where the array was not modified, you defined messing as:
def messing(ary)
return [ary[0]+693,ary[1]+999]
end
In one case you used +=, and in the other, you used merely +.
ary[0] += 700 is exactly equivalent to ary[0] = ary[0] + 700. In other words you are changing the value stored in the 0th index of ary.
In the second case you merely add to the values stored in the array and return the result, but in the first case you not only return the result, you also store it back in the array.
For an explanation of why modifying ary modifies #tiny_array, see this answer to the question Is Ruby pass by reference or by value?.
You're second code example (the one from outside the class) is missing the two characters in the first that make it work the way it does. In the first example, the += operator is used, modifying the array in place:
return [ary[0]+=700, ary[1]+=999]
In your second example, the + operator is used, leaving the array as is:
return [ary[0]+693,ary[1]+999]
If you change it use the += operator, it works the same way as the first code snippet.

Ruby inject method

I'm doing some ruby exercises. In one of the solutions of my exercise, I found this code.
def make_change(amount)
{ H:50, Q:25, D:10, N:5, P:1 }.inject({}) do |res, (k,v)|
change, amount = amount.divmod(v)
res[k] = change unless change==0
res
end
end
This method takes an amount as parameter and associates coins to equal it. For example:
make_change(75)
#=> {H:1, Q:1}
(50 + 25 = 75)
But I don't understand where the change variable come from. How is the variable filled?
But i doesnt understand where does the "change" variable come from, how the variable is filled ?
change is defined and initialized in the first line of the block:
change, amount = amount.divmod(v)

Ruby - Create a hash, where Keys are newly initialized Array objects

Please bear with me...I need basic concepts...I am not aware of advanced prog concepts yet.
I have a class called Circle which initializes and calculates area
class Circle
def initialize (radius)
#radius = radius
end
def area
3.14*#radius*#radius
end
end
I want to take user input and create however many instances of Circle objects and its sides.
p "How many Circles"
i = gets.to_i
j = 1
while j != i+1
p "Enter radius of Circle #{j}"
$s << Circle.new(gets.to_i)
j = j +1
end
The $s[] now holds array of objects I created.
Now, I want to do something like,
area_array[] = 0
area_array[Circle1] = Circle1.area
area_array[Circle1] = Circle2.area
and so on...where Circle1 and Circle2 are the objects I created earlier in my while loop....
Can someone tell me how can I put each of the created object in another array and assign an area value to it?
Do you need another array because you will modify or destroy the properties of the Circles in the first array? If so, and you can rely on the Cirlces' order in the array remaining the same, then just use the index value to correlate the values:
circle_area_hash = $s.reduce{|a, c| a[c.object_id] = c.area }
Also, consider that for your analyses, you may care more about the values, than the objects, per se. So then you could create
circle_area_hash = $s.reduce do |a, c|
a[c.area] = (a[c.area].nil?) ? [c] : a[c.area] << c
end
This make the hash-keys bey the area value as, and the hash-values are each an array of the objects that have that area.
Then to get the key (largest area) you can:
circle_area_hash.max_by{|k,v| v.count}
Also, as a thought:
puts "How many Circles"
$s = (1...gets.to_i).each |j|
puts "Enter radius of Circle #{j}"
$s << Circle.new(gets.to_i)
end
$s[3].area
To create a new array of areas:
area_array = $s.map{ |circle| circle.area }
area_array = $s.map( &:area ) # Same thing, but shorter
To create one big hash:
areas = Hash[ $s.map{ |circle| [ circle, circle.area ] } ]
This creates an array of arrays like:
[
[ <Circle #radius=3>, 28.27 ],
[ <Circle #radius=4>, 50.27 ],
…
]
…and then uses the Hash.[] method to convert that into a Hash.
Another technique is:
areas = $s.inject({}){ |hash,circle| hash.merge(circle=>circle.area) }
For more details, read up on Array#map and Enumerable#inject.
However, why would you want to create this hash? It seems like you're perhaps wanting to only calculate the area once each. Although it's not needed for this simple calculation, you can memoize a method's return value with a pattern like this:
class Circle
def initialize(radius)
#radius = radius
end
def area
#area ||= Math::PI*#radius*#radius
end
end
This will calculate the area the first time it's needed, and store it in an instance variable; thereafter it will just use the value of that variable as the return value of the method, without needing to recalculate it.
This is very straightforward. You should just iterate over $s, using each element as a hash key and the result of its area as the corresponding value.
Another few points that should be useful to you:
You can use Math::PI instead of 3.14
You should only use p for debugging. It prints the result of the inspect method of its parameter, which is rarely what you want for tidy output. Use print if you want to make your newlines explicit in the string, or puts to append a newline if there isn't one already
It is rarely appropriate to use while in Ruby. In this instance you just want i.times do { ... }
class Circle
def initialize (radius)
#radius = radius
end
def area
Math::PI * #radius * #radius
end
end
print 'How many Circles: '
i = gets.to_i
shapes = []
i.times do |n|
print "Enter radius of Circle #{n+1}? "
shapes << Circle.new(gets.to_i)
end
area_hash = {}
shapes.each do |shape|
area_hash[shape] = shape.area
end
However it seems more appropriate to memoize the area method here, writing it as
def area
#area = Math::PI * #radius * #radius unless #area
#area
end
Then you can use the method repeatedly and the calculation will be done only once.
After reading your comment on NewAlexandria's answer, perhaps something like this would work for you:
p "How many Circles"
(1..gets.to_i) do |j|
c = Circle.new
p "Enter radius of Circle #{j}"
s[c] = c.area(gets.to_i)}
end
where s is a pre-defined hash that may contain keys for instances of other circles, rectangles, etc.
This only makes sense, however, if you plan to add additional constants or methods to your shape classes that you will want to reference with the keys of s.
You should edit your question to incorporate your comment above.

Passing a method as a parameter in Ruby

I am trying to mess around a little bit with Ruby. Therefor I try to implement the algorithms (given in Python) from the book "Programming Collective Intelligence" Ruby.
In chapter 8 the author passes a method a as parameter. This seems to work in Python but not in Ruby.
I have here the method
def gaussian(dist, sigma=10.0)
foo
end
and want to call this with another method
def weightedknn(data, vec1, k = 5, weightf = gaussian)
foo
weight = weightf(dist)
foo
end
All I got is an error
ArgumentError: wrong number of arguments (0 for 1)
The comments referring to blocks and Procs are correct in that they are more usual in Ruby. But you can pass a method if you want. You call method to get the method and .call to call it:
def weightedknn( data, vec1, k = 5, weightf = method(:gaussian) )
...
weight = weightf.call( dist )
...
end
You want a proc object:
gaussian = Proc.new do |dist, *args|
sigma = args.first || 10.0
...
end
def weightedknn(data, vec1, k = 5, weightf = gaussian)
...
weight = weightf.call(dist)
...
end
Just note that you can't set a default argument in a block declaration like that. So you need to use a splat and setup the default in the proc code itself.
Or, depending on your scope of all this, it may be easier to pass in a method name instead.
def weightedknn(data, vec1, k = 5, weightf = :gaussian)
...
weight = self.send(weightf)
...
end
In this case you are just calling a method that is defined on an object rather than passing in a complete chunk of code. Depending on how you structure this you may need replace self.send with object_that_has_the_these_math_methods.send
Last but not least, you can hang a block off the method.
def weightedknn(data, vec1, k = 5)
...
weight =
if block_given?
yield(dist)
else
gaussian.call(dist)
end
end
...
end
weightedknn(foo, bar) do |dist|
# square the dist
dist * dist
end
But it sounds like you would like more reusable chunks of code here.
You can pass a method as parameter with method(:function) way. Below is a very simple example:
def double(a)
return a * 2
end
=> nil
def method_with_function_as_param( callback, number)
callback.call(number)
end
=> nil
method_with_function_as_param( method(:double) , 10 )
=> 20
The normal Ruby way to do this is to use a block.
So it would be something like:
def weightedknn(data, vec1, k = 5)
foo
weight = yield(dist)
foo
end
And used like:
weightedknn(data, vec1) { |dist| gaussian( dist ) }
This pattern is used extensively in Ruby.
You can use the & operator on the Method instance of your method to convert the method to a block.
Example:
def foo(arg)
p arg
end
def bar(&block)
p 'bar'
block.call('foo')
end
bar(&method(:foo))
More details at http://weblog.raganwald.com/2008/06/what-does-do-when-used-as-unary.html
You have to call the method "call" of the function object:
weight = weightf.call( dist )
EDIT: as explained in the comments, this approach is wrong. It would work if you're using Procs instead of normal functions.
I would recommend to use ampersand to have an access to named blocks within a function. Following the recommendations given in this article you can write something like this (this is a real scrap from my working program):
# Returns a valid hash for html form select element, combined of all entities
# for the given +model+, where only id and name attributes are taken as
# values and keys correspondingly. Provide block returning boolean if you
# need to select only specific entities.
#
# * *Args* :
# - +model+ -> ORM interface for specific entities'
# - +&cond+ -> block {|x| boolean}, filtering entities upon iterations
# * *Returns* :
# - hash of {entity.id => entity.name}
#
def make_select_list( model, &cond )
cond ||= proc { true } # cond defaults to proc { true }
# Entities filtered by cond, followed by filtration by (id, name)
model.all.map do |x|
cond.( x ) ? { x.id => x.name } : {}
end.reduce Hash.new do |memo, e| memo.merge( e ) end
end
Afterwerds, you can call this function like this:
#contests = make_select_list Contest do |contest|
logged_admin? or contest.organizer == #current_user
end
If you don't need to filter your selection, you simply omit the block:
#categories = make_select_list( Category ) # selects all categories
So much for the power of Ruby blocks.
Similarly to a Proc or a method call, you can also pass a lambda as weightf parameter :
def main
gaussian = -> (params) {
...
}
weightedknn(data, vec1, k = 5, gaussian, params)
# Use symbol :gaussian if method exists instead
end
def weightedknn(data, vec1, k = 5, weightf, params)
...
weight = weightf.call(params)
...
end
you also can use "eval", and pass the method as a string argument, and then simply eval it in the other method.

Resources