using a string (from gets) directly as a variable name - ruby

Starting out my Towers of Hanoi assignment, I have
a = [6,5,4,3,2,1]
b = []
c = []
puts "Type a, b, or c"
from = gets.chomp
# the user types a lower-case a
popped = from.pop
Now this obviously fails because pop is not a string method.
So other than
if from == a
popped = a.pop
elsif from == b
popped = b.pop
, is there a nice ruby shortcut to get the pop I intend?

You can use eval:
a = [6,5,4,3,2,1]
b = []
c = []
puts "Type a, b, or c"
from = gets.chomp
popped = eval(from).pop
But eval is typically seen as a bad idea for security, performance, and debugging reasons.

options = {
:a => [6,5,4,3,2,1]
:b => []
:c => []
}
puts "Type a, b, or c"
from = gets.chomp
popped = options[from.to_sym].pop
I have to strongly advise you avoid the use of the method using eval above, as it allows the user to input arbitrary code. Use a hash instead to store all your options.

Related

Order of optional parameters in initializer

Why am I getting the following error
a.rb:4: syntax error, unexpected '=', expecting ')'
...alize(a = "default val", b, c = [])
a.rb:18: syntax error, unexpected `end', expecting end-of-input
on the following code
class A
attr_reader :a, :b, :c
def initialize(a = "default val", b, c = [])
#a = a
#b = b
#c = c
end
def self.open(a)
args = {
b: "2",
c: [1, 2, 3]
}.values
A.new(a, *args)
end
end
when trying to call a property
a2 = A.open("something")
p a2.a
Removing the last default value from initializer =[] solves the problem. Reordering the arguments so the parameters with default values go at the end of initialize helps too.
def initialize(a = "default val", b, c)
or
def initialize(b, c=[], a = "default val") and A.new(*args, a) (but I suppose this is wrong)
As I remember there was a rule about ordering of optional params.
If you define optional parameters before AND after mandatory parameters, in some cases it will be impossible to decide how a goven list or arguments should map to the defined parameters.
In your case, when defining this method:
class A
def initialize(a = "default val", b, c = [])
#...
end
end
How would you handle this when giving two arguments,. i.e.
A.new 'hello', 'world'
You could then assign
a = 'hello'
b = 'world'
c = []
but you could equally set
a = 'default val'
b = 'hello'
c = 'world'
Given this unambiguity, Ruby rejects those constructs. You thus have to define all optional parameters either at the front or the back of your parameter list, while it is commonly accepted standard to define optional arguments only at the end.
If you want to be more specific about which arguments should be set with a large number of optional parameters, you can also use keyword arguments. Since you have to specify the name of the arguments when calling the method here, the order of mandatory and optional keyword arguments doesn't matter.

how to exit a while loop in ruby using the enter key

I am trying to write a program that keeps adding user input to an array until they hit enter on an empty line. Then it sorts the array in alphabetic order and display the sorted array back to you. It is not letting me break out of the loop.
this is my code:
wordList = []
puts "enter as many words as you like"
entry = gets.chomp
while true
wordList.push entry
if entry == ''
break
end
end
sortedWordList = wordList.sort
puts sortedWordList
You can do something like this:
wordList = []
puts "Enter as many words as you like:\n"
while (entry = gets.chomp)
break if entry.empty?
wordList.push entry
end
puts wordList.sort
wordList = []
puts "enter as many words as you like"
while true
entry = gets.chomp
wordList << entry
if entry.blank?
break
end
end
sortedWordList = wordList.sort
puts wordList.inspect
puts sortedWordList
You'll get output something like following
$ ruby test.rb
enter as many words as you like
d
f
g
h
y
a
b
c
j
k
["d", "f", "g", "h", "y", "a", "b", "c", "j", "k", ""]
a
b
c
d
f
g
h
j
k
y
Your code works fine if move down entry = gets.chomp below the while true. What it is doing now is adding the first entry infinitely to the wordList.
This is a way to shorten it while maintaining readability:
words = []
puts "enter as many words as you like"
until (entry = gets.chomp) == ""
words << entry
end

Why is the splat/unary operator changing the assigned value a when p is called before *a = ""?

To give a little context around how I understand the problem.
Using splat collect on a string sends :to_a or :to_ary to the String
class String
def method_missing method, *args, &block
p method #=> :to_ary
p args #=> []
p block #=> nil
end
end
*b = "b"
So I was thinking that redefining the :to_ary method would be what I'm after.
class String
def to_ary
["to_a"]
end
end
p *a = "a" #=> "a"
p a #=> "a"
*b = "b"
p b #=> ["to_a"]
Now this confuses me to no end.
Printing the result from the *a = "a" changes the value assigned to a?
To demonstrate further
class String
def to_ary
[self.upcase!]
end
end
p *a = "a" #=> "a"
p a #=> "a"
*b = "b"
p b #=> ["B"]
Very interesting question! Ruby takes this expression:
p *a = "a"
and translates it to something like this:
temp = (a = "a")
p *temp
So the first thing that happens is that a gets assigned to "a", and then the result of the assignment expression which is "a" gets splatted and sent to p. Since p's default behaviour when sent multiple arguments is just to iterate over and print each one, you only see "a" appear.
In short, it follows a "assign then splat" order of evaluation. So a gets assigned to "a" before the string gets splatted.
When you don't have a function call however, it is interpreted as something like this:
# *a = "a" gets interpreted as:
temp = "a"
a = *temp
This follows a "splat then assign" order of evaluation. So a gets assigned after the string gets splatted.
You can see what's being received by a function by going like this:
def foo *args
puts args.inspect
end
foo *a = "a" # outputs ["a"]
a # outputs "a"
Hope this clears up what's going on!
In short (thanks to Mark Reed):
p *a = "a" # interpreted as: p(*(a = "a"))
*a = "a" # interpreted as: a = *("a")

Why doesn't terse way of defining new hashes in Ruby work (they all refer to same object)

I need to establish a number of Hashes, and I didn't want to list one per line, like this
a = Hash.new
b = Hash.new
I also new that apart from for Fixnums, I could not do this
a = b = Hash.new
because both a and b would reference the same object. What I could to is this
a, b, = Hash.new, Hash.new
if I had a bunch it seemed like I could also do this
a, b = [Hash.new] * 2
this works for strings, but for Hashes, they still all reference the same object, despite the fact that
[Hash.new, Hash.new] == [Hash.new] * 2
and the former works.
See the code sample below, the only error message triggered is "multiplication hash broken". Just curious why this is.
a, b, c = [String.new] * 3
a = "hi"
puts "string broken" unless b == ""
puts "not equivalent" unless [Hash.new, Hash.new, Hash.new] == [Hash.new] * 3
a, b, c = [Hash.new, Hash.new, Hash.new]
a['hi'] = :test
puts "normal hash broken" unless b == {}
a, b, c = [Hash.new] * 3
a['hi'] = :test
puts "multiplication hash broken" unless b == {}
In answer to the original question, an easy way to initialize multiple copies would be to use the Array.new(size) {|index| block } variant of Array.new
a, b = Array.new(2) { Hash.new }
a, b, c = Array.new(3) { Hash.new }
# ... and so on
On a side note, in addition to the assignment mix-up, the other seeming issue with the original is that it appears you might be making the mistake that == is comparing object references of Hash and String. Just to be clear, it doesn't.
# Hashes are considered equivalent if they have the same keys/values (or none at all)
hash1, hash2 = {}, {}
hash1 == hash1 #=> true
hash1 == hash2 #=> true
# so of course
[Hash.new, Hash.new] == [Hash.new] * 2 #=> true
# however
different_hashes = [Hash.new, Hash.new]
same_hash_twice = [Hash.new] * 2
different_hashes == same_hash_twice #=> true
different_hashes.map(&:object_id) == same_hash_twice.map(&:object_id) #=> false
My understanding is this. [String.new] * 3 does not create three String objects. It creates one, and creates a 3-element array where each element points to that same object.
The reason you don't see "string broken" is that you have assigned a to a new value. So after the line a = "hi", a refers to a new String object ("hi") while b and c still refer to the same original object ("").
The same occurs with [Hash.new] * 3; but this time you don't re-assign any variables. Rather, you modify the one Hash object by adding the key/value [hi, :test] (via a['hi'] = :test). In this step you've modified the one object referred to by a, b, and c.
Here's a contrived code example to make this more concrete:
class Thing
attr_accessor :value
def initialize(value)
#value = value
end
end
# a, b, and c all refer to the same Thing object
a, b, c = [Thing.new(0)] * 3
# Here we *modify* that object
a.value = 5
# Verify b refers to the same object as a -- outputs "5"
puts b.value
# Now *assign* a to a NEW Thing object
a = Thing.new(10)
# Verify a and b now refer to different objects -- outputs "10, 5"
puts "#{a.value}, #{b.value}"
Does that make sense?
Update: I'm no Ruby guru, so there might be a more common-sense way to do this. But if you wanted to be able to use multiplication-like syntax to initialize an array with a bunch of different objects, you might consider this approach: create an array of lambdas, then call all of them using map.
Here's what I mean:
def call_all(lambdas)
lambdas.map{ |f| f.call }
end
a, b, c = call_all([lambda{Hash.new}] * 3)
You can verify that this approach works pretty easily:
x, y, z = call_all([lambda{rand(100)}] * 3)
# This should output 3 random (probably different) numbers
puts "#{x}, #{y}, #{z}"
Update 2: I like numbers1311407's approach using Array#new a lot better.

How do I use the for loop in ruby to grab different values from hash tables

This is probably easy to do! I'm not able envision the loop yet, I was thinking about a nested for loop but not quite sure how to alternate between the two hashes.
Lets say I have a class with a def that containts two hash tables:
class Teststuff
def test_stuff
letters = { "0" => " A ", "1" => " B ", "2" => " C " }
position = {"1" => "one ", "2"=> " two ", "3"=> " three ", "4"=>" four " }
my_array=[0,1,2,2] #this represents user input stored in an array valid to 4 elements
array_size = my_array.size #this represents the size of the user inputed array
element_indexer = my_array.size # parellel assignment so I can use same array for array in dex
array_start_index = element_indexer-1 #give me the ability later to get start at index zero for my array
#for loop?? downto upto??
# trying to get loop to grab the first number "0" in element position "0", grab the hash values then
# the loop repeats and grabs the second number "1" in element position "1" grab the hash values
# the loop repeats and grabs the third number "2" in elements position "2" grab the hash values
# the final iteration grabs the fourth number "2" in elements position "3" grab the hash values
# all this gets returned when called. Out put from puts statement after grabing hash values
# is: **A one B two C three C four**
return a_string
end
end
How do I go about returning string output to the screen like this:
**A one B two C three C four**
or simply letter position letter position...
Thanks for the help, put code up so I can try on my editor!
I think I figured out what it is you want, although I still have no idea what array_size, element_indexer, array_start_index and TestStuff are for.
def test_stuff
letters = { "0" => " A ", "1" => " B ", "2" => " C " }
position = {"1" => "one ", "2"=> " two ", "3"=> " three ", "4"=>" four " }
my_array = [0, 1, 2, 2]
"**#{my_array.map.with_index {|e, i|
"#{letters[e.to_s].strip} #{position[(i+1).to_s].strip}"
}.join(' ')}**"
end
[I took the liberty of reformatting your code to standard Ruby coding style.]
However, everything would be much simpler, if there weren't all those type conversions, and all those superfluous spaces. Also, the method would be much more useful, if it actually had a way to return different results, instead of always returning the same thing, because at the moment, it is actually exactly equivalent to
def test_stuff
'**A one B two C three C four**'
end
Something along these lines would make much more sense:
def test_stuff(*args)
letters = %w[A B C]
position = %w[one two three four]
"**#{args.map.with_index {|e, i| "#{letters[e]} #{position[i]}" }.join(' ')}**"
end
test_stuff(0, 1, 2, 2)
# => '**A one B two C three C four**'
If you don't want to pollute the Object namespace with your method, you could do something like this:
def (TestStuff = Object.new).test_stuff(*args)
letters = %w[A B C]
position = %w[one two three four]
"**#{args.map.with_index {|e, i| "#{letters[e]} #{position[i]}" }.join(' ')}**"
end
TestStuff.test_stuff(0, 1, 2, 2)
# => '**A one B two C three C four**'
You can use enumerators, like this:
l = letters.to_enum
p = position.to_enum
a_string = ''
loop do
a_string << l.next[1] << p.next[1]
end
How about :
a_string = ""
my_array.each_with_index { |x, index|
a_string += letters[my_array[index].to_s] + " " + (position.include?((index+1).to_s) ? position[(index+1).to_s] : "nil")
}

Resources