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 store and validate them to avoid duplication. I also need to do a "reset" method to remove all stored names and rerun. For some reason this program doesn't work. Hope someone can help me find the error. Many thanks.
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
source to share
def save_name
stored_names = []
stored_names << named
end
Each time you create a name and call save_name
, you remove all previously created names by assigning an empty arraystored_names
EDIT: There were a few more errors, let me write a working solution first:
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 must prefix them
@
. -
You have called
save_name
andcheck_name
on@named
which is a string and does not provide these methods -
@stored_names
must be initialized with an empty array before you can insert elements into it<<
. This is usually done in the constructor of the classinitialize()
source to share
Here's another way to build a class Robot
that you might want to consider. (My answers are usually not that long or detailed, but I wrote part of this to clarify aspects of the Ruby object model in my own mind. Hopefully this helps others to 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 parsing a class, the class method is created first
reset
, then the string is executedreset
. Howself => Robot
, when this happens, the class method is executedreset
, initializing to@bots
an empty array. -
It is the responsibility of the
Robot
class to maintain and modify the list of instances . This list is stored in an instance variable of the class@bots
. -
An instance
Robot
is created by a callRobot::new
that allocates memory and then calls a (private) instance methodinitialize
. Wherenew
? Since we have not defined it as a class method inRobot
, there are two possibilities: it inherits from one of theRobot
ancestors (Robot.ancestors => [Robot, Object, Kernel, BasicObject]
), or it is an instance method of the classClass
, since it is the class for which itRobot
is an instance (i.e.Robot.class => Class
). Find out that:Class.instance_method(:new) => #<UnboundMethod: Class#new>
(orClass.instance_methods.include?(:new) => true
)Object.method(:new) => #<Method: Class#new>
. And this! But it makes sense, because all classes are instancesClass
, includingRobot
superclassObject
.#<Method: Class#new>
the returnedObject.method(:new)
showsnew
is defined inClass
(which can alternatively be seen withRobot.method(:new).owner => Class
... Very cool, huh? If you didn't already know this and can follow what I said in this paragraph, you just learned the essence of the Ruby Object Model! -
Suppose we add the class method
new
shown below toRobot
.super
calls a class methodObject::new
(which is an instance methodClass#new
), passing any argumentsnew
(not here).Object::new
returns the instance it creates, which in turn returnsRobot::new
. Hence, this method will simply be a channel and will not affect the results.def self.new super end
-
We can make a small change to the above method to add a copy of the instance created
Object::new
to the array@bots
:def self.new instance = super @bots << instance instance end
-
I wrote this a little more compactly like:
def self.new (@bots << super).last end
-
I used Array # sample method to randomly draw
PREFACE_SIZE
characters fromPREFACE
andSUFFIX_SIZE
characters fromSUFFIX_SIZE
.sample
samples without replacement, so you will not receive, for example, "UU112". If you want fetch and replace, replace the methodgen_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 created a class method
bots
to return the value of a class instance variable@bots
. This could alternatively be done by specifying a read accessory for@bots
in theRobots
'singleton class:class << self attr_reader :name end
-
When called
Robot.reset
, what happens to all the instancesRobot
it contained? Will they be left to roam the woods, rejected and homeless? In languages ββlikeC
, you need to free memory before discarding them. In Ruby and many other modern languages ββthat are unnecessary (and cannot be done at all). Ruby garbage collection keeps track of all objects and kills (after freeing memory) any objects that are no longer referenced by any other object. Nice, huh?
source to share
The task itself is not difficult, but I don't like the way your code is organized. This is what I would do in the first step:
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 building a list of names, it is easy to verify that they are unique. This is how I would do 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
source to share
I understand this is not efficient, but it 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
It's not good, but it gets the job done.
source to share
This is how I can automate Cary's approach with mine 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]
source to share