Initializing an array of instance variables - ruby

I'm making a game with a Board class Cell class. The Board class needs to be initialized with a unique instance variable for each Cell. I can hardcode it so that it works, but it seems inelegant and doesn't allow the size of the board to be chosen by the user at runtime. Here's what I have:
class Board
def initialize
#cell_1 = Cell.new(1)
#cell_2 = Cell.new(2)
#cell_3 = Cell.new(3)
#cell_4 = Cell.new(4)
#cell_5 = Cell.new(5)
#cell_6 = Cell.new(6)
#cell_7 = Cell.new(7)
#cell_8 = Cell.new(8)
#cell_9 = Cell.new(0)
#cells = [#cell_1, #cell_2, #cell_3,
#cell_4, #cell_5, #cell_6,
#cell_7, #cell_8, #cell_9]
end
end
I think I could use a loop to create a hash with unique key names pointing to unique Cell objects, but I don't know how I could make unique instance variables with a loop.

If you don't need to create each instance variables (#cell_1, #cell_2, ...), you can use Enumerable#map:
#cells = [*1..8, 0].map { |i| Cell.new(i) }

If you really need to refer every instance variable by name you can do something like this.
class Board
def initialize
#cells = (1..9).to_a.map { |i| Cell.new(i) }
end
def method_missing(method, *args, &block)
if method =~ /^cell_[1-9][0-9]*$/
index = method[/\d+/].to_i
#cells[index-1]
else
super
end
end
end
In this way you can call:
board = Board.new
board.cell_1 #=> first cell
Of course I'd use the solution proposed by #falsetru.

Related

Initializing multiple objects at once?

So, I'm trying to initialize multiple objects and add them to a list. What I'm wanting to happen is by running Market.new, I want every item from the api added as an object. Below is the code I thought might work. But, it's adding the same object to the list 100x. Is there a way to accomplish this?
def initialize
data = JSON.parse(open(BASE_URL + "markets?vs_currency=usd").read)
i = 0
# looping until we hit the end of the list. adding them all as objects.
while i < data.length
#id = data[i]["id"].to_s
#name = data[i]["name"].to_s
#symbol = data[i]["symbol"].to_s
#price = data[i]["current_price"].to_s
#price_movement_24h = data[i]["price_change_percentage_24h"].to_s
#market_cap = data[i]["market_cap"].to_s
##market << self
i += 1
end
end
This gives me the same object added to the ##market list 100x.
=> [#<Market:0x0000561b3f853f30
#id="iostoken",
#market_cap="43969067",
#name="IOST",
#price="0.0036523",
#price_movement_24h="-0.76702",
#symbol="iost">,
#<Market:0x0000561b3f853f30
#id="iostoken",
#market_cap="43969067",
#name="IOST",
#price="0.0036523",
#price_movement_24h="-0.76702",
#symbol="iost">,
I want to start by saying this is very strange Ruby code and not something you'd typically do. That's not meant as an insult, just to say that Ruby devs tend to follow the same or similar guidelines on structuring objects and this chunk of code feels like it's ported from another language.
The issue you're seeing is due to the fact that within the initialize method you're not creating any new objects but instead updating the instance variables and pushing self into a class variable. self is referencing this instance directly which means the class variable array is filling up with references to the same object. If you're adamant on keeping the code the same then you should instead push a duplicate of your object after you've updated the instance variables.
##market << self.dup
This creates a duplicate object that has a different memory address and reference.
If you're looking to write more idiomatic code you'd want to use multiple objects and not rely on class variables at all. If you're not interpolating a variable in a string use single quotes instead of double quotes. Keep object methods simple and focused on specific tasks. These are just a few things Ruby developers consider when writing code, but find what works best for you.
Take something like this for instance:
class Market
attr_accessor :id, :name, :symbol, :price, :price_movement_24h, :market_cap
def initialize(data = {})
#id = data['id'].to_s
#name = data['name'].to_s
#symbol = data['symbol'].to_s
#price = data['current_price'].to_s
#price_movement_24h = data['price_change_percentage_24h'].to_s
#market_cap = data['market_cap'].to_s
end
end
class ImportService
def self.from_api(url)
response = JSON.parse(open(url).read) || []
response.map { |data| Market.new(data) }
end
end
You could then call this as such:
#market_data = ImportService.from_api(BASE_URL + 'markets?vs_currency=usd')
When adding self to the ##market, you should change the code to this
##market << self.dup
However, I don't think it's a good practice to use a class variable here and add self to init an array of object. Instead, you should create a new class (for example MarketImporter)
class Market
attr_accessor :id, :name, :symbol, :price, :price_movement_24h, :market_cap
def initialize(data = {})
#id = data["id"].to_s
#name = data["name"].to_s
#symbol = data["symbol"].to_s
#price = data["current_price"].to_s
#price_movement_24h = data["price_change_percentage_24h"].to_s
#market_cap = data["market_cap"].to_s
end
end
class MarketImporter
attr_accessor :markets
def initialize
data = JSON.parse(open(BASE_URL + "markets?vs_currency=usd").read)
#markets = data.collect { |item| Market.new(item) }
end
end
Then you can init the collection by
MarketImporter.new

Exporting methodcalls to file

I'm fairly new to ruby and wanted to know if it's possible to export a method/method call.
Basically, I have this:
class B
def initialize
#bptimer = BP_Timer.new("MyName", method(:sayhello))
end
def sayhello()
msgbox("hello")
end
end
class BP_Timer
def initialize(name = nil, method = nil)
#name = name
#time = 0
#repeats = 0
#start_from = 0
#method = method
#active = false
end
def start()
#active = true
$timers.push(self)
end
end
(I removed all the rather uninteresting parts of the code)
Basically, I now have a "BP_Timer" object with the name and method that will later be pushed into a $timers array which I want to save to a file and later on load from when I restart the program. Is it possible to deconstruct the object in such a way that I can store and restore it?
This is not how we usually store/restore the state. The common approach would be to store a configuration and perform the whole initialization again.
That said, you are to store an array of initialization parameters, like ["MyName", :sayhello] in your case to _YAML,JSON`, or any other text format of your choice:
config.yml
bp_timers:
- "MyName": :sayhello
And then on program start you do:
config = YAML.load_file("config.yml")
config["bp_timers"].each do |k, v|
BP_Timer.new(*[k, method(v)]).start
end
Answering your initial question: yes, this is possible to some extent with Marshal class.

Dynamic Variables to access class methods in Ruby

Working in Ruby, we have to use a 3rd party Framework, which has a class setup something like this:
class Foo
attr_accessor :bar
def initialize()
end
end
class Poorly_Designed_Class
attr_accessor :thing1
attr_accessor :thing2
attr_accessor :thing3
attr_accessor :thing4
attr_accessor :thing5
# through :thing_n .. number defined at runtime
def initialize()
#thing1 = Foo.new
#thing2 = Foo.new
#thing3 = Foo.new
#thing4 = Foo.new
#thing5 = Foo.new
end
end
I don't know how many "things" there are until run time. there could be 5 or their could be 50.
What I would like to do is something like:
pdc = Poorly_Designed_Class.new
for i in 0..numberOfThings do
pdc."thing#{i}".bar = value[i]
end
The above doesn't work.
I've also tried accessing it via:
instance_variable_set("pdc.thing#{i}.bar",value)
I understand that the class should be using an array or hash. Unfortunately I can't do anything about how the class is designed and we have to use it.
Is what i'm trying to do even possible?
You could either try to call the getter (preferably, since it honors encapsulation):
pdc = PoorlyDesignedClass.new
1.upto(number_of_things.times do |i|
pdc.public_send(:"thing#{i}").bar = value[i]
end
or get the instance variable (less preferred, since it breaks encapsulation):
pdc = PoorlyDesignedClass.new
1.upto(number_of_things) do |i|
pdc.instance_variable_get(:"#thing#{i}").bar = value[i]
end
So, you were on the right track, there were just two problems with your code: instance variable names start with an # sign, and . is not a legal character in an identifier.
You're using Object#instance_variable_set incorrectly. The first argument must be a string or a symbol representing the name of an instance variable including the # prefix: e.g. "#thing{i}". However you actually want to get the value of an instance variable and then send #bar= to it. That can be done with Object#instance_variable_get:
1.upto(numberOfThings) { |i| pdc.instance_variable_get("#thing#{i}").bar = value[i] }
That's a bit long and since attr_acessor :thingX defines getter methods, it's usually preferable to call them with Object#public_send instead of directly accessing the instance variable (a getter method might do something else than just returning a value):
1.upto(numberOfThings) { |i| pdc.public_send("thing#{i}").bar = value[i] }

How to generate a random name in Ruby

I need to make a program in ruby to generate a robot name like KU765 or NG274 style
and to store them and check it to avoid repetition.
I also need to make a "reset" method to delete all stored names and start again.
This program is not working for some reason. I hope somebody helps me to find the mistake.
Thanks a lot.
class Robot
attr_accessor :named , :stored_names , :rl
def self.name
new.name
end
##rl = "_ABCDEFGHIJKLMNOPQRSTUVWXYZ"
def name
named = ""
named << ##rl[rand(26).to_i]
named << ##rl[rand(26).to_i]
named << rand(100..999).to_s
named.save_name
named.check_name
end
def save_name
stored_names = []
stored_names << named
end
def check_name
stored_names.uniq!
end
def reset
stored_names = Array.new
end
end
Here's another way to construct the Robot class that you may wish to consider. (My answers are not normally this long or detailed, but I wrote this in part to clarify aspects of Ruby's object model in my own mind. I hope it might help others do the same.)
Code
PREFACE = ('A'..'Z').to_a << ?_
SUFFIX = ('0'..'9').to_a
PREFACE_SIZE = 2
SUFFIX_SIZE = 3
class Robot
def self.reset() #bots = [] end
reset
def self.new() (#bots << super).last end
def self.bots() #bots end
def self.delete(bot) #bots.delete(bot) end
def self.bot_names() #bots.map { |b| b.name } end
attr_reader :name
def initialize() #name = add_name end
private
def add_name
loop do
#name = gen_name
return #name unless self.class.bot_names.include?(#name)
end
end
def gen_name
PREFACE.sample(PREFACE_SIZE).join << SUFFIX.sample(SUFFIX_SIZE).join
end
end
Example
Robot.bots #=> []
robbie = Robot.new #=> #<Robot:0x000001019f4988 #name="AP436">
robbie.name #=> "AP436"
Robot.bots #=> [#<Robot:0x000001019f4988 #name="AP436">]
r2d2 = Robot.new #=> #<Robot:0x000001019cd450 #name="KL628">
r2d2.name #=> "KL628"
Robot.bots #=> [#<Robot:0x000001019f4988 #name="AP436">,
# #<Robot:0x000001019cd450 #name="KL628">]
Robot.bot_names #=> ["AP436", "KL628"]
Robot.delete(robbie) #=> #<Robot:0x000001019f4988 #name="AP436">
Robot.bots #=> [#<Robot:0x000001019cd450 #name="KL628">]
Robot.bot_names #=> ["KL628"]
Robot.reset #=> []
c3po = Robot.new #=> #<Robot:0x000001018ff8c0 #name="VO975">
Robot.bots #=> [#<Robot:0x000001018ff8c0 #name="VO975">]
Explanation
When the class is parsed, the class method reset is first created, then the line reset is executed. As self => Robot when that occurs, the class method reset is executed, initializing #bots to an empty array.
The responsibility for saving and modifying a list of instances of Robot lies with the class. This list is held in the class instance variable #bots.
Instance of Robot are created by invoking Robot::new, which allocates memory and then invokes the (private) instance method initialize. Where is new? Since we have not defined it as a class method in Robot, there are two possibilities: it is inherited from one of Robot's ancestors (Robot.ancestors => [Robot, Object, Kernel, BasicObject]) or it is an instance method of the class Class, as that is the class for which Robot is an instance (i.e., Robot.class => Class) Let's find out which: Class.instance_method(:new) => #<UnboundMethod: Class#new> (or Class.instance_methods.include?(:new) => true), Object.method(:new) => #<Method: Class#new>. It's both! But that makes sense, because all classes are instances of Class, including Robot's superclass, Object. #<Method: Class#new> returned by Object.method(:new) shows new is defined in Class (which can alternatively be seen with Robot.method(:new).owner => Class. Very cool, eh? If you didn't know this already, and can follow what I've said in this paragraph, you've just learned the essence of Ruby's object model!
Suppose we add the class method new, shown below, to Robot. super invokes the class method Object::new (which is the instance method Class#new), passing any arguments of new (here there aren't any). Object::new returns the instance that it creates, which Robot::new in turn returns. Therefore, this method would simply be a conduit and and have no effect on the results.
def self.new
super
end
We can make a small change to the above method to add a copy of the instance that is created by Object::new to the array #bots:
def self.new
instance = super
#bots << instance
instance
end
I have written this a little more compactly as:
def self.new
(#bots << super).last
end
I've used the method Array#sample to randomly draw PREFACE_SIZE characters from PREFACE and SUFFIX_SIZE characters from SUFFIX_SIZE. sample samples without replacement, so you will not get, for example, "UU112". If you want to sample with replacement, replace the method gen_name with the following:
def gen_name
str = PREFACE_SIZE.times.with_object('') { |_,s| s << PREFACE.sample }
SUFFIX_SIZE.times { str << SUFFIX.sample }
str
end
I have created a class method bots to return the value of the class instance variable #bots. This could alternatively be done by defining a read accessor for #bots on Robots' singleton class:
class << self
attr_reader :name
end
When Robot.reset is invoked, what happens to all the instances of Robot it had contained? Will they be left to wander the forest, rejected and homeless? In languages like C you need to release their memory before casting them aside. In Ruby and many other modern languages that's not necessary (and generally can't be done). Ruby's "garbage collection" keeps track of all objects, and kills off (after releasing memory) any that are no longer referenced by any other object. Nice, eh?
The task itself is not hard, but I don't like the way your code is organised. This is what I would do in the first stage:
class Robot
class Name < String
class << self
def sign
"#{[*?A..?Z].sample}#{[*?A..?Z].sample}"
end
def number
"#{rand 1..9}#{rand 0..9}#{rand 0..9}"
end
def new
super << sign << number
end
end
end
end
And then:
Robot::Name.new
When constructing a list of names it is easy to check that they are unique. This is how I'd go about it:
class Robot
class Names < Array
def self.generate n
new.tap { |array| n.times do array.add_random_name end }
end
def add_random_name
name = Name.new
include?( name ) ? add_random_name : self << name
end
end
end
And then:
Robot::Names.generate 7
def save_name
stored_names = []
stored_names << named
end
Every time, you create a name, and call save_name you delete all previously created names, by assigning an empty array to stored_names
EDIT:
There were a few more errors, let me first post a working solution:
class Robot
attr_accessor :named , :stored_names , :rl
def initialize()
#stored_names = []
end
##rl = "_ABCDEFGHIJKLMNOPQRSTUVWXYZ".chars.to_a
def name
#named = ""
#named << ##rl.sample
#named << ##rl.sample
#named << rand(100..999).to_s
save_name
check_name
end
def save_name
#stored_names << #named
end
def check_name
#stored_names.uniq!
end
def reset
#stored_names = Array.new
end
end
To access the members of your object, you have to prefix them with #.
You called save_name and check_name on #named, which is a string and doesn't provide these methods
#stored_names must be initialized to an empty array, before you can push elements into it with <<. This is normally done in the class's constructor initialize()
I understand this isn't efficient, but this will work.
letters = [*'A'..'Z'] =>
numbers = [*100..999]
names = []
2.times{names.push(letters.shuffle.first)} => Randomizing array and choosing element
names.push(numbers.shuffle.first)
names.join => Creates a String object out of the array elements
This isn't pretty, but it gets the job done.
This is how I automate Cary's approach with my y_support/name_magic:
require 'y_support/all'
class Robot
★ NameMagic
def name_yourself
begin
self.name = "#{[*?A..?Z].sample( 2 ).join}#{rand 100..999}"
rescue NameError; retry end
end
end
3.times { Robot.new.name_yourself }
Robot.instances #=> [PR489, NS761, OE663]
Robot.forget "PR489"
Robot.instances #=> [NS761, OE663]
Robot.forget_all_instances
Robot.instances #=> []
Robot.new.name_yourself
Robot.instances #=> [IB573]

Setting an instance variable to 'self'?

I'm creating Conway's Game of Life with two classes: Board & Cell.
Board has access to Cell, but I'm not quite sure exactly how. Can't I place cell.board = self under Cell's initialize method? Why or why not? For sake of example, here's what I think are the relevant parts.
class Board
#omitted variables & methods
def create_cell
cell = Cell.new
cell.board = self
end
end
class Cell
attr_accessor :board
def initialize
end
end
Also, what does cell.board = self do exactly?
There is an error in your code. Instead of cell = Class.new you should do cell = Cell.new.
Yes you can pass the board (self) as a parameter in the Cell's constructor (initialize). In fact is more clean and functional in that way. Check out this code:
class Board
def create_cell
cell = Cell.new(self)
end
end
class Cell
attr_accessor :board
def initialize board
#board = board
end
end
And then some examples of use.
$> b = Board.new
# => #<Board:0x000001021c0298>
$> c1 = b.create_cell
# => #<Cell:0x000001021c27a0 #board=#<Board:0x000001021c0298>>
$> c2 = b.create_cell
# => #<Cell:0x000001021d4270 #board=#<Board:0x000001021c0298>>
$> c2.board == c1.board
# => true
As you can notice, either cell.board = self or using the constructor (initialize), is setting the current board instance into the created cell. So all these cells will point to that board.
cell.board = self sets the cell's board variable to the current board object (that is the board object on which you called the create_cell method.
If you put cell.board = self into Cell's initialize method, you'd get an error that cell is not defined in that scope. If you replaced it with #board = self, you'd set the board variable to the current Cell object (i.e. the one that's being created), not to a Board object. So that wouldn't be what you want.
Yes, you can. You can assign self to anything. You can't assign something else to self, but there's nothing wrong with assigning self to any other accessable variable or passing it to a method.
You could, for example, have written
class Board
#omitted variables & methods
def create_cell
cell = Class.new(self)
end
end
class Cell
attr_accessor :board
def initialize(board)
self.board = board
end
end
Also, what does cell.board = self do exactly?
It just passes a variable to the board= accessor. It's no different from passing any other variable.

Resources