How can you use a symbol to reference an object in Ruby? - ruby

I'm trying to convert a project I've written in Ruby to use Classes.
Block I'm currently using:
elements.each do |element, value|
value /= 100
total.push(value * price[element])
end
Full code: https://gist.github.com/gbourdon/53d3d125b04a9394164daca01b657987
Hash structure:
price = {o: 0.30, c: 2.40, h: 12.00, etc.}
I need the symbol stored in the hash (For instance, :o) to be able to reference an object with the same name (For instance, the object o).
How can I get this to work?

Here's a reworked, Ruby-ified version of your code that avoids the need to have those variables entirely. If you look at the operations here you don't care, particularly, what the element is, you only care about it's value and relative abundance per unit of weight.
A retooled Element class looks like this:
class Element
attr_reader :symbol
attr_reader :price
attr_reader :amount
def initialize(symbol, price, amount)
# Cocerce both inputs into floats
#symbol = symbol
#price = price.to_f
#amount = amount.to_f
end
end
Now that contains information important to the element itself, like its symbol. Keeping the symbol some place like the variable name is actually quite annoying as variable names shouldn't have significant meaning like that, they should only be for readability.
Now you can define all of your elements in one shot, inside one container object:
ELEMENTS = [
Element.new('O', 0.30, 0.65),
Element.new('C', 2.40, 0.18),
Element.new('H', 12, 0.10),
Element.new('N', 0.40, 0.03),
Element.new('Ca', 11, 0.015),
Element.new('P', 4, 0.01),
Element.new('K', 85, 0.0035),
Element.new('S', 0.25, 0.0025),
Element.new('Cl', 0.15, 0.0015),
Element.new('Na', 7, 0.0015)
]
The resulting executable can be streamlined a bunch more, too, especially on the input conversion:
# Take input from the command-line to make re-running this easier
pounds = ARGV[0].to_i
# Quick conversion in one shot. Try and keep variables all lower_case
kg = pounds * 0.4536 * 1000
Now all you need to do is convert each element in that table into a net price based on the weight:
# Convert each element into its equivalent value by weight
total = ELEMENTS.map do |element|
element.price * element.amount * kg
end.reduce(:+) # Added together
Where reduce here is a replacement for the unnecessary Array method. It does wha you need. Rails actually has a sum method which is even easier.
Then to present:
puts "You are worth: $#{(total / 100).round(2)}"
That's it.
With this new structure you could expand on the functionality to give a detailed breakdown of price by element if you wanted, all the information necessary is contained within that element object. That's why a more self-contained object design is better.

Related

How to use value of a string to refer to variables?

Some programmer made a method that gets lots of arguements like this:
def age_entry(age_1, age_2, age_3, age_4, age_5, age_6, age_7, age_8)
end
They could pass an array but simply they didn't. I love automation and hate to repeatedly add these variables to and array like this
ages = [age_1, age_2, age_3 ,..., age_8]
I would like to use metaprogramming or other ways to loop with a for or each methods to add them variables to an array like this:
(1..8).each do |index| do
ages << "age_" + index #value of age_[index] get saved to ages
end
P.S. I know I can use copy and paste but this is only for doing automation stuff with Ruby.
"Some programmer" should remember that you can pass in arrays. This sort of method signature is really obnoxious to work with for a multitude of reasons, some of them you've already discovered.
One way to refactor this method and preserve functionality is to just take in varargs:
def age_entry(*ages)
end
Now those values are put in an array for you but you can call the method the same way as before. As a plus you can specify more or fewer entries.
Variables with names like x1, x2 and so on are violations of the Zero, One or Infinity Rule and are a sign you need to think about the problem differently.
You don’t need any metaprogramming here. Just splat them:
ages = [age_1, age_2, age_3 ,..., age_8]
# ⇓ HERE
age_entry(*ages)
If you want to collect age_(1..8) into the array, assuming all local vars are defined, use Kernel#binding:
b = binding
ages = (1..8).map { |i| b.local_variable_get("age_#{i}") }
Suppose the method is as follows.
def oldest(age_bill, age_barb, age_trixie)
puts "Barb is #{age_barb} years old"
[age_bill, age_barb, age_trixie].max
end
oldest(35, 97, 29)
#=> 97
As well as the calculation in the penultimate line (which the OP wishes to avoid), this method requires knowledge of an individual method argument (age_barb). The following is one way to accomplishing both requirements.
def oldest(age_bill, age_barb, age_trixie)
puts "Barb is #{age_barb} years old"
b = binding
args = method(__method__).parameters.map { |arg| b.local_variable_get(arg[1]) }
args.max
end
#=> 97
Barb is 97 years old
Here
args
#=> [35, 97, 29]

Calculating totals from two different hashes

I have two hashes:
For example, one contains a list of dishes and their prices
dishes = {"Chicken"=>12.5, "Pizza"=>10, "Pasta"=>8.99}
The other is a basket hash i.e. I've selected one pasta and two pizzas:
basket = {"Pasta"=>1, "Pizza"=>2}
Now I am trying to calculate the total cost of the basket but can't seem to get my references right.
Have tried
basket.inject { |item, q| dishes[item] * q }
But keep getting the following error
NoMethodError: undefined method `*' for nil:NilClass
basket.inject { |item, q| dishes[item] * q }
Let's look at the documentation for Enumerable#inject to see what is going on. inject "folds" the collection into a single object, by taking a "starting object" and then repeatedly applying the binary operation to the starting object and the first element, then to the result of that and the second element, then to the result of that and the third element, and so forth.
So, the block receives two arguments: the current value of the accumulator and the current element, and the block returns the new value of the accumulator for the next invocation of the block. If you don't supply a starting value for the accumulator, then the first element of the collection is used.
So, during the first iteration here, since you didn't supply a starting value for the accumulator, the value is going to be the first element; and iteration is going to start from the second element. This means that during the first iteration, item is going to be ['Pasta', 1] and q is going to be ['Pizza', 2]. Let's just run through the example in our heads:
dishes[item] * q # item is ['Pasta', 1]
dishes[['Pasta', 1]] * q # q is ['Pizza', 2]
dishes[['Pasta', 1]] * ['Pizza', 2] # there is no key ['Pasta', 1] in dishes
nil * ['Pizza', 2] # nil doesn't have * method
Ergo, you get a NoMethodError.
Now, I believe, what you actually wanted to do was something like this:
basket.inject(0.0) {|sum, (item, q)| sum + dishes[item] * q }
# ↑↑↑ ↑↑↑ ↑↑↑↑↑
You don't want to accumulate orders, you want to accumulate numbers, so you need to supply a number as the starting value; if you don't, the starting value will be the first element, which is an order, not a number
You were mixing up the meaning of the block parameters
You weren't actually summing anything
Now, while inject is capable of summing (in fact, inject is capable of anything, it is a general iteration operation, i.e. anything you could do with a loop, you can also do with inject), it is usually better to use more specialized operations if they exist. In this case, a more specialized operation for summing does exist, and it is called Enumerable#sum:
basket.sum {|item, q| dishes[item] * q }
But there is a deeper underlying problem with your code: Ruby is an object-oriented language. It is not an array-of-hash-of-strings-and-floats-oriented language. You should build objects that represent your domain abstractions:
class Dish < Struct.new(:name, :price)
def to_s; "#{name}: $#{price}" end
def *(num) num * price end
def coerce(other) [other, price] end
end
require 'bigdecimal'
require 'bigdecimal/util'
dishes = {
chicken: Dish.new('Chicken', '12.5'.to_d),
pizza: Dish.new('Pizza', '10'.to_d),
pasta: Dish.new('Pasta', '8.99'.to_d)
}
class Order < Struct.new(:dish, :quantity)
def to_s; "#{quantity} * #{dish}" end
def total; quantity * dish end
end
class Basket
def initialize(*orders)
self.orders = orders
end
def <<(order)
orders << order
end
def to_s; orders.join("\n") end
def total; orders.sum(&:total) end
private
attr_accessor :orders
end
basket = Basket.new(
Order.new(dishes[:pasta], 1),
Order.new(dishes[:pizza], 2)
)
basket.total
#=> 0.2899e2
Now, of course, for such a simple example, this is overkill. But I hope that you can see that despite this being more code, it is also much much simpler. There is no complex navigation of complex nested structures, because a) there are no complex nested structures and b) all the objects know how to take care of themselves, there is never a need to "take apart" an object to examine its parts and run complex calculations on them, because the objects themselves know their own parts and how to run calculations on them.
Note: personally, I do not think that allowing arithmetic operations on Dishes is a good idea. It is more of a "neat hack" that I wanted to show off in this code snippet.
With Ruby 2.4, you could use Hash(Enumerable)#sum with a block :
basket = {"Pasta"=>1, "Pizza"=>2}
prices = {"Chicken"=>12.5, "Pizza"=>10, "Pasta"=>8.99}
basket.sum{ |dish, quantity| quantity * prices[dish] }
# 28.99
Data structure
dishes
dishes (what I called prices to avoid writing dishes[dish]) is the correct data structure :
Hash lookup is fast
If you want to update the price of a dish, you only have to do it in one place
It's basically a mini database.
basket
basket is also fine as a Hash, but only if you don't oder any dish more than once. If you want to order 2 pizzas, 1 pasta and then 3 pizzas again :
{"Pizza"=>2, "Pasta" => 1, "Pizza" =>3}
=> {"Pizza"=>3, "Pasta"=>1}
you'll lose the first order.
In that case, you might want to use an array of pairs (a 2-element array with dish and quantity) :
basket = [["Pizza", 2], ["Pasta", 1], ["Pizza", 3]]
With this structure, you could use the exact same syntax to get the total as with a Hash :
basket.sum{ |dish, quantity| quantity * prices[dish] }
Try this one
basket.inject(0) do |acc, item|
dish, q = item
acc + (dishes[dish] * q)
end
=> 28.990000000000002
one line
basket.inject(0) { |acc, item| acc + (dishes[item.first] * item.last) }
Your variables for the block are wrong. You have the accumulator and an item (that it's an hash)
2.2.0 :011 > basket.inject(0){ |sum, (item, q)| sum + dishes[item].to_f * q }
=> 28.990000000000002

Reference to array cell in Ruby?

Can I have a reference to an array cell in Ruby? In C++, I can do something like:
int& ref = arr[x][y];
and later work with the variable ref without the need of typing the whole arr[x][y].
I want to do this as I need to access one and the same cell multiple times throughout a function (I'm doing memoization) and typing unnecessary indexes may only lead to errors.
All values in ruby are references, so this is certainly possible, but with some important limitations. One caveat is that ruby doesn't DIRECTLY support multidimensional arrays, but you can implement one as an array of arrays or as a hash keyed by tuples.
You can achieve this in cases where the value at (x, y) has already been set by assigning to the value at the given coordinates. If no value currently exists at that location, then you must initialize that value before you can have a reference to it:
# if x and y are indices and a is your "multidimensional array"
a[x][y] = 'First Value' # Initial value at (x, y)
ref = a[x][y] # take a reference to the value referenced by a[x][y]
ref.gsub! 'First', 'Second'
a[x][y] # => 'Second Value'
Keep in mind that the assignment operator in ruby generally means "make the reference on the left side refer to the value on the right". This means that if you use the assignment operator on your reference, then you're actually making it refer to a new value:
a[x][y] = 1 # Initialize value with 1
ref = a[x][y] # Take the reference
ref += 1 # Assignment
ref # => 2
a[x][y] # => 1
You might have better success by using a Hash and keying the hash with tuples of your coordinates, and then using these tuples to get references to specific locations:
a = {}
loc = [x, y]
a[loc] = 'First Value' # Initial value
a[[x,y]] # => 'First Value'
a[loc] = 'Second Value' # Assignment
a[[x,y]] # => 'Second Value'
a[loc] = 1 # Assignment
a[loc] += 1 # Assignment
a[[x,y]] # => '2'
Ruby is considered pass by value so to answer your question (not pass by reference like C++), it's not directly possible to do what you're asking.
There's a really good post in this answer by Abe that you should read through:
Is Ruby pass by reference or by value?
For ref to continue to point to the actual data of arr[x][y] at any given time, one possibiliy is to write it as a method :
def ref
ar[1][1]
end
In a high level language like ruby, all variables are references and there is no "pointers" or levels of indirections like C or C++, you should create objects to hold this references to get similar behavior
This is what I would do on ruby
Suppose you need to save a "pointer" to a ruby array, then you create a Class to access the array in a given index (there is no such thing like getting a "pointer" to a value in ruby)
class ArrayPointer
def initialize(array, index)
#array = array
#index = index
end
def read
#array[index]
end
def write(value)
#array[index] = value
end
end
Then, you use the clase this way
array = [1, 2, 3]
pointer = ArrayPointer.new(array, 1)
pointer.write(20)
puts array # [1, 20, 3]
You also can get "pointers" to local variables, but is too weird and uncommon in ruby world and it almost doesn't make sense
Note this kind of code is weird and not common in ruby, but it is interesting from the didactic point of view to compare two great languages like Ruby and C
In the Object Oriented nature of ruby, is preferable to design good abstractions (e.g. instead of using an array to represent your data, if preferable to define a class with methods like the ruby way) before only using elemental structures such as Array or Hash to represent the data used by your program (the last approach common in C, is not the ruby way)

ruby and references. Working with fixnums

I know a bit about ruby way to handle objects and references. The replace stuff, ect ...
I know it d'ont work on fixnum, cause the var is the fixnum. But i wish to change the value of a fixnum inside a function, and that the value changed in the ouside var.
How can i do this ?
I guess i can use a string like this "1" but that's quite dirty.
Ruby will always pass-by-reference (because everything is an object) but Fixnum lacks any methods that allow you to mutate the value. See "void foo(int &x) -> Ruby? Passing integers by reference?" for more details.
You can either return a value that you then assign to your variable, like so:
a = 5
def do_something(value)
return 1 #this could be more complicated and depend on the value passed in
end
a = do_something(a)
or you could wrap your value in an object such as a Hash and have it updated that way.
a = {:value => 5}
def do_something(dict)
dict[:value] = 1
end
do_something(a) #now a[:value] is 1 outside the function
Hope this helps.
You could pass an array with a single number, like [1], or a hash like {value: 1}. Less ugly than a string, as your number itself remains a number, but less overhead than a new class...
When I was building a game I had the same problem you have. There was a numeric score that represented how many zombies you've killed and I needed to manually keep it in sync between Player (that incremented the score), ScoreBar and ScoreScreen (that displayed the score). The solution I've found was creating a separate class for the score that will wrap the value and mutate it:
class Score
def initialize(value = 0)
#value = value
end
def increment
#value += 1
end
def to_i
#value
end
def to_s
#value.to_s
end
end

selecting hash values by value property in ruby

In Ruby, I have a hash of objects. Each object has a type and a value. I am trying to design an efficient function that can get the average of the values of all of objects of a certain type within the hash.
Here is an example of how this is currently implemented:
#the hash is composed of a number of objects of class Robot (example name)
class Robot
attr_accessor :type, :value
def initialize(type, value)
#type = type
#value = value
end
end
#this is the hash that inclues the Robot objects
hsh = { 56 => Robot.new(:x, 5), 21 => Robot.new(:x, 25), 45 => Robot.new(:x, 35), 31 => Robot.new(:y, 15), 0 => Robot.new(:y, 5) }
#this is the part where I find the average
total = 0
count = 0
hsh.each_value { |r|
if r.type == :x #is there a better way to get only objects of type :x ?
total += r.value
count += 1
end
}
average = total / count
So my question is:
is there a better way to do this that does not involve looping through the entire hash?
Note that I can't use the key values because there will be multiple objects with the same type in the same hash (and the key values are being used to signify something else already).
If there is an easy way to do this with arrays, that would also work (since I can convert the hash to an array easily).
Thanks!
EDIT: fixed error in my code.
hsh.values.select {|v| v.type == :x}.map(&:value).reduce(:+) / hsh.size
I am trying to design an efficient function that can get the average of the values of all of objects of a certain type within the hash
Unless I misunderstood what you are trying to say, this is not what the code you posted does. The average value of the :x robots is 21 (there's 3 :x robots, with values 5, 25 and 35; 5 + 25 + 35 == 65 and 65 divided by 3 robots is 21), but your code (and mine, since I modeled mine after yours) prints 13.
is there a better way to get only objects of type :x ?
Yes. To select elements, use the select method.
is there a better way to do this that does not involve looping through the entire hash?
No. If you want to find all objects with a given property, you have to look at all objects to see whether or not they have that property.
Do you have actual hard statistical evidence that this method is causing your performance bottlenecks?
You can also directly map the keys like this
hsh.values.map {|k| k[:x]}.reduce(:+) / hsh.size

Resources