I have been doing a little reading up on Ruby. Like how simplistic the language is. I've been trying to look it up, and figure it out on my own. I'm looking for some help with Objects and how I add data to it. I want to make an Object called Athlete where I read it in from a .txt or .csv file their Jersey Number and Name.
class Athlete
def setNumber (jNum)
#mynum = jNum
end
def getNumber
return #mynum
end
def setName (jName)
#myname = jName
end
def getName
return #myname
end
end
Is that how I would set up the class?
Then I read in the file:
myAthlete = Athlete.new
fileObj = File.new(uInput, "r")
while (line = fileObj.gets)
jData = line.split(" ")
myAthlete.setNumber(jData.at(0))
myAthlete.setName(jData.at(1))
end
fileObj.close
this is where I start to get a bit lost. I know it splits the data perfectly, because I've already tried this with just Array.new -- That being said, I'm trying to make the array inside of the Athlete class. Can someone assist me with this?
So if my input file is:
52 Sabathia
19 Tanaka
17 Holliday
24 Sanchez
I would like for it to split and then if I call lets say uhhh myAthlete(1) it'd print out Tanaka's stuff
The thing about Ruby to embrace out of the gate is how clean the syntax is and how a lot of Ruby style conventions are driven by that. For example, Ruby advises against methods with get and set in them, instead preferring things like name and name= for accessor and mutator methods.
Yes, you can have = at the end of a method name, just like you can have ? or !, each of which have taken to mean particular things.
It's also a given that the last operation you perform in a method is implicitly the return value, so there's no need for return.
Here's a simple refactoring of your code with that in mind:
class Athlete
def number
#number
end
def number=(value)
#number = value
end
def name
#name
end
def name=(value)
#name = value
end
end
You can reduce this even further since Ruby has a method that will generate these for you automatically called attr_accessor. You can also make your life a little easier by making an initialize method to populate these:
class Athlete
attr_accessor :number
attr_accessor :name
def initialize(number, name)
#number = number
#name = name
end
end
So to put this all together:
athletes = [ ]
File.readlines(input) do |line|
number, name = line.chomp.split(/\s+/)
athletes << Athlete.new(number, name)
end
A lot of Ruby code uses blocks to define operations that should happen. In this case readlines calls that block for each line read from the file. These lines include the \n newline at the end which chomp removes. << is a quick way to append something to an array.
Try to keep your variable and method names all lower-case as well. Capitals have significant meaning in Ruby, so jData should be jdata or j_data.
Update: To make this more debugger-friendly:
class Athlete
def inspect
"Athlete [%s] %s" % [ #number, #name ]
end
end
First off, you don't need to define explicit getters/setters. Something like this will do
class Athlete
attr_accessor :name, :number
def initialize(name, number)
self.name = name
self.number = number
end
end
Or even shorter:
Athlete = Struct.new(:name, :number)
Then to create athletes:
athletes = File.foreach(file_path).map do |line|
number, name = line.chomp.split(' ')
Athlete.new(name, number)
end
You will now have an array full of Athletes.
Related
I have a class. Let's call it SomeClass:
class SomeClass
end
Instead of defining instances of this class the normal way, I would like to define them all using a constant:
MyConstant = SomeClass.new
I want to be able to capture the name of the constant that some class was set too, in much the same way that standard ruby classes do with the .class method.
MyConstant.name #-> "MyConstant"
I want to be able to do this to render better error messages from all instances of some class, like so:
class SomeClass
def display_error_message
"Error, some class #{self.name} has a problem"
end
end
MyConstant.display_error_message
#-> "Error, some class MyConstant has a problem"
Any way to accomplish this?
EDIT
Here's an example to clarify what I'm shooting for.
(Enum is the name of the class I'm creating, which is meant to act similar to Swifts 'Enum' type. Basically it sets a predefined list of options (:pepperoni, :sausage, :mushroom) with a raw_value ("Pepperoni", "Sausage", "Mushroom".) Obviously in this example a hash or a simple algorithm for converting a symbol to an UpperCamel case string could work, but in reality the enum class will do a lot more, but this example shows the gist of it.
class Pizza
attr_reader :topping
Toppings = Enum.new do
option(:pepperoni).set("Pepperoni")
option(:sausage).set("Sausage")
option(:mushrooms).set("Mushrooms")
end
def set_topping(symbol)
#topping = Toppings[symbol]
end
end
pizza = Pizza.new
### Happy Case
pizza.set_topping(:pepperoni)
### Sad Case (Error message shown below is what I'm trying to figure out)
pizza.set_topping(:spinach)
#-> Error. enum Toppings has no option spinach
A variable is just a way of referring to an object, and the variable's name is irrelevant. If you say X = Y and Y happens to be a class, then the Y class already has the name "Y", so you can't change that.
As far as Ruby is concerned X and Y are indistinguishable.
If you want to alter the name you can make a subclass even if that subclass doesn't do anything different:
X = Class.new(Y)
X.name
# => "X"
Z = X
Z.name
# => "X"
That way preserves the name properly but only in the context of the initialization. I think Ruby does something sneaky and if a new class is being assigned to a constant it assigns a name, but for ordinary variables it does not:
x = Class.new(Y)
x.name
# => nil
So this is a special case.
The key here is that there's a huge difference between a subclass, which does impact the name, and a variable reference, which doesn't.
There's some other strange stuff going on here as it seems like the class somehow "knows" when it's being assigned to something and if that something is a constant it steals the constant's name for itself:
z = Class.new
z.name
# => nil
Z = z
z.name
# => "Z"
As they say in programming: "Wat?"
Your Enum class could look something like this:
class Enum
def initialize(name, &blk)
#defined_options = {}
#name = name.freeze
instance_eval(&blk)
#defined_options.freeze
end
def [](key)
if #defined_options.key? key
#defined_options[key].value
else
unfound_option = Option.new(#name, key)
raise "Option #{unfound_option} not found."
end
end
def to_s
"#{#name}"
end
def inspect
keys = #defined_options.keys.join(',')
"#<#{self}::{#{keys}}>"
end
class Option
attr_reader :value
def initialize(enum_name, key)
#value_initialized = false
#enum_name = enum_name
#key = key
end
def set(value)
if #value_initialized
raise "Value for #{self} can't be set to #{value} " +
"because it is already initialized to #{#value}"
else
#value_initialized = true
#value = value.freeze
end
end
def to_s
"#{#enum_name}::#{#key}"
end
def inspect
"#<#{self}>"
end
end
private
def option(sym)
unless #defined_options.key? sym
option = Option.new(#name, sym)
#defined_options[sym] = option
end
#defined_options[sym]
end
end
Now you can almost keep the syntax you have in your question, and do the following:
class Pizza
attr_reader :topping
# I had to add the name in the initializer for better error reporting.
Toppings = Enum.new('Toppings') do
option(:pepperoni).set("Pepperoni")
option(:sausage).set("Sausage")
option(:mushrooms).set("Mushrooms")
end
def set_topping(symbol)
#topping = Toppings[symbol]
end
end
pizza = Pizza.new
### Happy Case
pizza.set_topping(:pepperoni)
#=> "Pepperoni"
### Sad Case (Error message shown below is what I'm trying to figure out)
pizza.set_topping(:spinach)
#=> RuntimeError: Option Toppings::spinach not found.
This might give you an idea how to solve this issue. This is just a rough sketch of the class and could probably be tweaked to your needs.
I have a class that can parse different types of messages and what I want to do is to create a hash that will use the msg type id as the keys and different instance methods as the values.
Something like this:
class Parser
def initialize(msg_id)
#my_methods = {1 => method_1, 2 => method_2, 3 => method_3}
#my_methods[msg_id]()
end
def method_1
end
def method_2
end
def method_3
end end
I know it's possible, but I am not sure how to do it. I tried using the self.method(:method_1) as a value but I got an error saying that method_1 is not defined.
Thank you
The simplest possible changes to fix your code are like this:
class Parser
def initialize(msg_id)
#my_methods = { 1 => method(:method_1), 2 => method(:method_2), 3 => method(:method_3) }
#my_methods[msg_id].()
end
def method_1; end
def method_2; end
def method_3; end
end
I.e. use the Object#method method to get a Method object, and use the Method#call method to execute it.
However, there are a few improvements we could make. For one, your Hash associates Integers with values. But there is a better data structure which already does that: an Array. (Note: if your message IDs are not assigned sequentially, then a Hash is probably the right choice, but from the looks of your example, they are just Integers counting up from 1.)
And secondly, hardcoding the methods inside the Parser#initialize method is probably not a good idea. There should be a declarative description of the protocol, i.e. the message IDs and their corresponding method names somewhere.
class Parser
# this will make your message IDs start at 0, though
PROTOCOL_MAPPING = [:method_1, :method_2, :method_3].freeze
def initialize(msg_id)
#my_methods = PROTOCOL_MAPPING.map(&method(:method))
#my_methods[msg_id].()
end
def method_1; end
def method_2; end
def method_3; end
end
Another possibility would be something like this:
class Parser
PROTOCOL_MAPPING = []
private_class_method def self.parser(name)
PROTOCOL_MAPPING << name
end
def initialize(msg_id)
#my_methods = PROTOCOL_MAPPING.map(&method(:method))
#my_methods[msg_id].()
end
parser def method_1; end
parser def method_2; end
parser def method_3; end
end
Or maybe this:
class Parser
PROTOCOL_MAPPING = {}
private_class_method def self.parser(msg_id, name)
PROTOCOL_MAPPING[msg_id] = name
end
def initialize(msg_id)
#my_methods = PROTOCOL_MAPPING.map {|msg_id, name| [msg_id, method(name)] }.to_h.freeze
#my_methods[msg_id].()
end
parser 1, def method_1; end
parser 2, def method_2; end
parser 3, def method_3; end
end
While provided answer would work fine, there are few "minor" issues with it:
If there'd be tons of methods, hardcoding such hash would take time, and since it is not dynamic (because you have to update the hash manually each time new method is added to the class body) it is very error prone.
Even though you are within the class, and technically have access to all methods defined with any visibility scope with implicit receiver (including private and protected), it is still a good practice to only rely on public interface, thus, I'd recommend to use Object#public_send.
So here is what I would suggest (despite the fact I do not see how the idea of having such map would work in real life):
class Parser
def initialize(msg_id)
# generate a dynamic hash with keys starting with 1
# and ending with the size of the methods count
methods_map = Hash[(1..instance_methods.size).zip(instance_methods)]
# Use public_send to ensure, only public methods are accessed
public_send(methods_map[msg_id])
end
# create a method, which holds a list of all instance methods defined in the class
def instance_methods
self.class.instance_methods(false)
end
end
After a quick thought I refactored it a bit, so that we hide the implementation of the mapping to private methods:
class Parser
def initialize(msg_id)
public_send(methods_map[msg_id])
end
# methods omitted
private
def methods_map # not methods_hash, because what we do is mapping
Hash[(1..instance_methods.size).zip(instance_methods)]
# or
# Hash[instance_methods.each.with_index(1).map(&:reverse)]
end
def instance_methods
self.class.instance_methods(false)
end
end
The method you're looking for is send.
Note that the values in your hash need to be symbols to be passed to send.
class Parser
def initialize(msg_id)
#my_methods = {1 => :method_1, 2 => :method_2, 3 => :method_3}
send(#my_methods[msg_id])
end
def method_1
end
def method_2
end
def method_3
end
end
Documentation here
Ok so I just started learning ruby and I'm making a Yhatzee game, now this is where I'm currently at:
class Yhatzee
def dices
#dices.to_a= [
dice1=rand(1..6),
dice2=rand(1..6),
dice3=rand(1..6),
dice4=rand(1..6),
dice5=rand(1..6)
]
end
def roll_dice
#dices.to_a.each do |dice|
puts dice
end
end
end
x = Yhatzee.new
puts x.roll_dice
Now the reason i typed .to_a after the array is i kept getting a "uninitialized variable #dices" error, and that seemed to fix it, i have no idea why.
anyways on to my question, i currently don't get any errors but my program still won't print anything to the screen. I expected it to print out the value of each dice in the array... any idea what I'm doing wrong? It seems to work when i do it in a procedural style without using classes or methods so i assumed it might work if i made the 'dices' method public. But no luck.
There are a few issues here. Firstly #dices is nil because it is not set anywhere. Thus when you call #dices.to_a you will get []. Also the dices method will not work either because nil does not have a to_a= method and the local variables you are assigning in the array will be ignored.
It seems a little reading is in order but I would do something like the following: (Not the whole game just refactor of your code)
class Yhatzee
def dice
#dice = Array.new(5){rand(1..6)}
end
def roll_dice
puts dice
end
end
x = Yhatzee.new
puts x.roll_dice
There are alot of additional considerations that need to be made here but this should at least get you started. Small Example of how I would recommend expanding your logic: (I did not handle many scenarios here so don't copy paste. Just wanted to give you a more in depth look)
require 'forwardable'
module Yahtzee
module Display
def show_with_index(arr)
print arr.each_index.to_a
print "\n"
print arr
end
end
class Roll
include Display
extend Forwardable
def_delegator :#dice, :values_at
attr_reader :dice
def initialize(dice=5)
#dice = Array.new(dice){rand(1..6)}
end
def show
show_with_index(#dice)
end
end
class Turn
class << self
def start
t = Turn.new
t.show
t
end
end
attr_reader :rolls
include Display
def initialize
#roll = Roll.new
#rolls = 1
#kept = []
end
def show
#roll.show
end
def roll_again
if available_rolls_and_dice
#rolls += 1
#roll = Roll.new(5-#kept.count)
puts "Hand => #{#kept.inspect}"
show
else
puts "No Rolls left" if #rolls == 3
puts "Remove a Die to keep rolling" if #kept.count == 5
show_hand
end
end
def keep(*indices)
#kept += #roll.values_at(*indices)
end
def show_hand
show_with_index(#kept)
end
def remove(*indices)
indices.each do |idx|
#kept.delete_at(idx)
end
show_hand
end
private
def available_rolls_and_dice
#rolls < 3 && #kept.count < 5
end
end
end
The main problem with this code is that you are trying to use the #dices instance variable inside of the roll_dice method, however you are not defining the instance variable anywhere (anywhere that is being used). You have created the dices method but you are not actually instantiating it anywhere. I have outlined a fix below:
class Yhatzee
def initialize
create_dices
end
def roll_dice
#dices.each do |dice|
puts dice
end
end
private
def create_dices
#dices = Array.new(5){rand(1..6)}
end
end
x = Yhatzee.new
x.roll_dice
I have done some simple refactoring:
Created an initialize method, which creates the #dice instance variable on the class initialization.
Made the 'dices' method more descriptive and changed the method visibility to private so only the class itself is able to create the #dice.
Cleaned up the creation of the dices inside of the #dice instance variable
I have omitted the .to_a from the roll_dice method, now that we create the variable from within the class and we know that it is an array and it will be unless we explicitly redefine it.
UPDATE
Although I cleaned up the implementation of the class, it was kindly pointed out by #engineersmnky that I oversaw that the roll would return the same results each time I called the roll_dice function, I have therefore written two functions which will achieve this, one that defines an instance variable for later use and one that literally just returns the results.
class Yhatzee
def roll_dice
#dice = Array.new(5){rand(1..6)} # You will have access to this in other methods defined on the class
#dice.each {|dice| puts dice }
end
def roll_dice_two
Array.new(5){rand(1..6)}.each {|dice| puts dice } # This will return the results but will not be stored for later use
end
end
x = Yhatzee.new
x.roll_dice
x.roll_dice # Will now return a new result
I have one class, that in its methods produce an output of a hash.
=> {"A"=>"1", "B"=>"2"}
My question is how can I send this produced hash to another class, with methods that will further process this hash data?
Ive read the ruby doc and searched on StackOverflow, but can't seem to figure out how to get this new class to pick up this data (from the original class).
I am getting "undefined local variable or method" error when attempting to call on the methods of the first class, while in the second class.
Thanks for your help on this.
Hopefully I supplied enough background on my issue that someone can provide some guidance.
EDIT--
Here is the code that I have, which produces a this above mentioned hash. Actually at this stage it's an array.
Ideally I would like to have all the code from build_list method, on downwards, to be in a totally separate class. Doing this would require me to pass the produced array data (from user input) to these other methods, in this new class. In this new class I would like to have the completed playlist printed. So I would like to spit this example code into two classes, with the second class doing all of the processing work on the user supplied artists. I hope this is clearer.
require 'lib/uri.rb'
require 'json'
require 'rest_client'
require 'colorize'
class Playlist
attr_accessor :artistInput, :artist_list
def initialize
#artistInput = artistInput
#artist_list = []
#artist = #artist
end
def self.start #welcome to the application + rules
puts "-------------------------------------------------------------------------------------------------------------------"
puts "|This Playlist Creator application will create you a static playlist from a given list of artists supplied by you.|".colorize(:color => :white, :background => :black)
puts "-------------------------------------------------------------------------------------------------------------------"
puts "Hint: To stop adding artists, type '\N\' + the [enter] key at any time."
# puts "Hint: For artists with spaces, eg \'Lady Gaga\' - please remove the spaces and write as such \'LadyGaga\'."
end
system "clear"
def artistInput #loop that creates an artist_list array of an infinate amount of artists, stops at the keyword 'N'
#artist_list = []
while #artist != ""
puts "\nWhat artist would you like to add to the Playlist?"
#artist = gets.gsub(/\s+/, "")
if #artist != 'N'
puts "You have chosen to add #{#artist} to the Playlist."
end
break if #artist =~ /N/
#artist_list << #artist.gsub(/\s+/, "") #.gsub removes spaces. 'Lady Gaga' => 'LadyGaga'
end
def build_list
##artist_list
list = #artist_list
list.join('&artist=')
end
def build_url_string
string = build_list
url = 'http://developer.echonest.com/api/v4/playlist/static?api_key=G3ABIRIXCOIGGCCRQ&artist=' + string
end
system "clear"
def parse_url
url = build_url_string
response = JSON.parse(RestClient.get url)
song = response['response']['songs'].each do |song|
song.delete("artist_id")
song.delete("id")
puts
song.each {|key, value| puts "#{key.colorize(:white)}: \'#{value.colorize(:color => :white, :background => :black)}\'" }
end
# a = response['response']['songs']
# a.fetch('id')
end
a = parse_url
puts "\nThere are #{a.length} songs in this Playlist\n\n"
#Uncomment this section to see the raw data feed
# puts "////////////////raw data hash feed//////////////"
# puts "#{a.to_s}"
end
end
Its going to be really difficult to answer your question without code samples. But I am going to try my best with this example
class A
def get_hash
{"A"=>"1", "B"=>"2"}
end
end
class B
def process_hash(hash)
#do something with the hash
end
end
hash = A.new.get_hash
B.new.process_hash(hash)
I am working on class methods.
I am trying to count the number of created instances of a class. I am able to do this by creating a counter variable in the initialize method.
The problem arises when I reassign the variable originally assigned to one class instance. Because the initialize method is called twice, it does not recognize that the variable is simply being reassigned to another class instance.
class Ticket
attr_accessor :price
attr_reader :event, :venue
##count = 0
##tickets = {}
def initialize(event, venue)
#event = event
#venue = venue
##count += 1
end
def self.count
##count
end
end
a = Ticket.new("Michael Buble", "Staples")
a = Ticket.new("Frank Sinatra", "Madison Square Garden")
puts "Ticket count of #{Ticket.count}"
When I run the above code in IRB, it gives me a Ticket count of 2 (as expected). How do I change my code so that it recognizes the overwrite?
NOTE: I know that this question has been asked before for Objective C, but the reassignment aspect of the question adds a different element to the problem. Let me know otherwise.
ObjectSpace.each_object(Ticket).count
Will give you the count of object currently in memory. On testing in IRB I find it runs into the problem you describe, objects persist in memory even though you have assigned a new one to the variable. Technically the object still exists, even though you assign a new instance to the variable "a".
See this article: Deleting an object in Ruby The answers have plenty of info about what you are trying to do.
In the real world you wouldn't be counting instances in memory, you'd be asking a database how many exist. You need to think in terms of a database.
Your use of a to repeatedly contain the Ticket instance is wrong. You should be using an Array, Hash or Set to maintain the list, then ask the container how many exist:
require 'set'
class Ticket
attr_accessor :price
attr_reader :event, :venue
##tickets = Set.new
def initialize(event, venue)
#event = event
#venue = venue
##tickets << self
end
def delete
##tickets.delete(self)
end
def self.count
##tickets.size
end
end
a = Ticket.new("Michael Buble", "Staples")
b = Ticket.new("Frank Sinatra", "Madison Square Garden")
puts "Ticket count of #{Ticket::count}"
b.delete
puts "Ticket count of #{Ticket::count}"
You can build this out by adding ways to retrieve a particular instance from ##tickets, add a to_s so you can list them, but, in the end, you'll want to use a real database. If your code were to crash for any reason, your entire list of tickets would disappear, which would be unacceptable in real life.
If you really want to count live instances of the Ticket class (for reasons I cannot fathom), #Beartech has the right idea:
class Ticket
attr_reader :event, :venue
def initialize(event, venue)
#event = event
#venue = venue
end
def self.count_live_instances
ObjectSpace.garbage_collect
ObjectSpace.each_object(self).to_a.size
end
end
a = Ticket.new("Michael Buble", "Staples")
b = Ticket.new("Cher", "Canadian Tire Center")
a = Ticket.new("Frank Sinatra", "Madison Square Garden")
puts "Ticket instances count = #{Ticket.count_live_instances}" # => 2
It is essential to garbage-collect before invoking ObjectSpace#each_object. If you are skeptical, insert p ObjectSpace.each_object(self).to_a.size as the first line of self.count_live_instances. It will print 3.
(There is also a method ObjectSpace#count_objects. This method returns a hash like this one: {:TOTAL=>56139,..., :T_ARRAY=>3139,..., :T_ICLASS=>32}. Unfortunately, the keys are "object types"; you won't find :TICKET among them.)
class Gs
def self.method1
code...
end
def self.method2
code...
end
def self.method3
code...
end
end
Gs.new
p Gs.singleton_methods.count
The Gs.singleton_methods.count will print 3
it will count the singleton methods,if we use the self keyword or
classname.method name..