How to create a similar Ruby method using symbol? - ruby

I have been reading a Ruby book where I encountered below code to describe symbols
def walk(direction)
if direction == :north
# ...
end
end
I tried and failed to create a similar method ( where a comparison is made against a symbol such as
direction == :north
because most of the time I have seen symbols being used something like param[:name], so in my code I tried :north = 1 or :favourite = 'ruby' but got syntax error.
Is it really possible to have such a comparison using a symbol alone (without hash) ie instead of
if "ruby" == param[:name]
end
if "ruby" == :name
end
I am not sure if I have expressed the question clearly, if not I shall try and reword it.

I see a misunderstanding of what symbols are and what is their purpose.
if direction == :north
In this line, direction is a variable (it can hold any value) and :north is a value (it can be assigned to variables).
Trying to do this:
:north = 1
is like trying to do
2 = 1
Which doesn't make sense, of course.

Symbols are rather like identifiers, or a special version of strings.
With strings, you can have
str1 = 'SYM'
and
str2 = 'symbol'
str2 = str2[0,3].upcase
and now there are two identical strings in different places in memory. Ruby has to compare all the characters to evaluate str1 == str2.
However symbols are unique - you can't do character manipulation on them, and if you have
sym1 = :SYM
sym2 = :SYM
then it takes only a single comparison to test their equality. It demonstrates this clearly if we look at the object IDs for the strings and the symbols
puts str2.object_id
puts str1.object_id
puts sym1.object_id
puts sym2.object_id
puts str1.to_sym.object_id
puts str2.to_sym.object_id
output
22098264
22098228
203780
203780
203780
203780
So the two strings have different object IDs, whereas the two symbols are, in fact, the same object. Even converting the two strings to symbols gives the same object ID, so there is only one :SYM.
Because symbols are values, it makes no sense to write something like :north = 1 as it is like writing 'north' = 1.
Comparing strings to symbols, like 'north' = :north will always return false, because they are different classes of object.
param[:name] works only because you can index a hash with any object. (You can say param[Object.new] = 1.) It's different from writing either param['name'] (indexing the hash by a literal string) or param[name] (indexing the hash by the contents of variable name).
Does this answer your question?

Related

Last element of the ruby array is nil

I have a simple ruby program that has 2 steps so far
1. Ask the user for the number of stock market symbols they want to track
2. Ask the user to input these symbols
puts("How many stocks do you want to track ?")
numStocks = gets.chomp()
puts("Please enter #{numStocks} stock symbols: ")
array = Array.new(numStocks.to_i)
for i in 1..numStocks.to_i do
array.insert(i-1, gets.chomp())
end
puts("Stock symbols entered ... #{array}")
The output that is printed onto the console is
Stock symbols entered ... ["aapl", nil]
Why is the last element of the array nil in this case and what's the proper way to get rid of it ?
Array.new creates a new array, filling it with the quantity of elements you specified. Array.new(3) is the same as [nil, nil, nil]. Array.new(2, 'a') is the same as ['a', 'a'].
You then use array.insert which adds instead of replaces the elements. You could use array[i-1] = gets.chomp() to set the values, but there's really no reason to initialize the array this way at all.
A "more Ruby" way to write this all would be:
puts 'How many stocks do you want to track ?'
num_stocks = gets.chomp
puts "Please enter #{num_stocks} stock symbols: "
array = 1.upto(num_stocks.to_i).map do
gets.chomp
end
puts "Stock symbols entered ... #{array}"
EDIT:
Also, it’s worth mentioning that in Ruby, arrays are not a fixed size. You can add and remove elements from them as you please. If I had to guess, you’re used to languages like C, where you have to define the size of your array up front and then it’s just that size forever (that’s what I’m guessing you were trying to do anyways).
And another thing, in Ruby it’s not very common to use Array.new. Most times people just define an array by typing an array literal.
array = [1,2,3]
A ruby array is more like a List in other languages. It supports push (though << is a more common method for adding to an array), pop, and other list-like features.
Thats because when you do Array.new(numStocks.to_i) it initializes an array with 3 nil values and you keep adding on to it,
the proper way to get rid of nil from array is to use .compact on the array but I suggest you change your logic,
maybe something like this
puts("How many stocks do you want to track ?")
numStocks = gets.chomp()
puts("Please enter #{numStocks} stock symbols: ")
array = Array.new() # or array = []
numStocks.to_i.times do
array << gets.chomp()
end
puts("Stock symbols entered ... #{array}")
or you could ask the user to enter the comma separated symbols, so you don't have to loop, and split them,
puts("Please enter #{numStocks} stock symbols separated by commas (a,b): ")
symbols = gets.chomp().split(',')
puts("Stock symbols entered ... #{array}")

Using Ruby hash key as parameters

I am trying to use a parameter as my key to find the value in a hash, and I just confused about why I couldn't get the value by the first way. I am new to Ruby.
def getCards(player,hash)
a =$player
puts "a = "+a.to_s
puts "a.class = "+a.class.to_s
puts " hash[:a]"+" #{hash[:a]}"
puts " hash[:'1']"+" #{hash[:"1"]}"
end
edit:
def getCards(player,hash)
puts player
#result successfully 1 or any number that I gets from console
puts hash[player]
# nothing but 1 is actually a key in my hash
# {1=>["yellow3", "yellow8", "green9", "black11", "red1", "black7", "red5", #"yellow7", more results ..
end
Note that Ruby is not PHP or Perl, so that should be player and not $player. Argument names and their corresponding use as variables are identical.
$player refers to the global variable of that name, which is unrelated and will be presumed to be undefined unless otherwise set.
Now if by hash[:a] you mean to access the contents of the hash under the key with the player value you've assigned to a then what you actually want is:
hash[player]
Where that represents looking up an entry with that key. a is a variable in this case, :a is the symbol "a" which is just a constant, like a label, which has no relation to the variable.
Don't forget that "#{x}" is equivalent to x.to_s so just use interpolation instead of this awkward "..." + x.to_s concatenation.
Another thing to keep in mind is that in Ruby case has significant meaning. Variable and method names should follow the get_cards style. Classes are ClassName and constants are like CONSTANT_NAME.

I an getting an "Undefined method 'new' for.... (A number that changes each time)"

I made a simple program with a single method and I'm trying to test it, but I keep getting this weird error, and I have no idea why it keeps happening.
Here's my code for the only method I wrote:
def make_database(lines)
i = 0
foods = hash.new()
while i < lines.length do
lines[i] = lines[i].chomp()
words = lines[i].split(',')
if(words[1].casecmp("b") == 0)
foods[words[0]] = words[3]
end
end
return foods
end
And then here's what I have for calling the method (Inside the same program).
if __FILE__ == $PROGRAM_NAME
lines = []
$stdin.each { |line| lines << line}
foods = make_database(lines).new
puts foods
end
I am painfully confused, especially since it gives me a different random number for each "Undefined method 'new' for (Random number)".
It's a simple mistake. hash calls a method on the current object that returns a number used by the Hash structure for indexing entries, where Hash is the hash class you're probably intending:
foods = Hash.new()
Or more succinctly:
foods = { }
It's ideal to use { } in place of Hash.new unless you need to specify things like defaults, as is the case with:
Hash.new(0)
Where all values are initialized to 0 by default. This can be useful when creating simple counters.
Ruby classes are identified by leading capital letters to avoid confusion like this. Once you get used to the syntax you'll have an easier time spotting mistakes like that.
Note that when writing Ruby code you will almost always omit braces/brackets on empty argument lists. That is x() is expressed simply as x. This keeps code more readable, especially when chaining, like x.y.z instead of x().y().z()
Other things to note include being able to read in all lines with readlines instead of what you have there where you manually compose it. Try:
make_database($stdin.readlines.map(&:chomp))
A more aggressive refactoring of your code looks like this:
def make_database(lines)
# Define a Hash based on key/value pairs in an Array...
Hash[
# ...where these pairs are based on the input lines...
lines.map do |line|
# ...which have comma-separated components.
line.split(',')
end.reject do |key, flag, _, value|
# Pick out only those that have the right flag.
flag.downcase == 'b'
end.map do |key, flag, _, value|
# Convert to a simple key/value pair array
[ key, value ]
end
]
end
That might be a little hard to follow, but once you get the hang of chaining together a series of otherwise simple operations your Ruby code will be a lot more flexible and far easier to read.

Why won't my Ruby for-loop iterate over a String in my palindrome method?

I am having trouble with getting my for-loop to process a string. It's just a simple method to tell whether or not a word is a palindrome (a word that is spelled that same way backwards and forwards). I have tweaked the for-loop multiple times but keep getting the same error message below. Could anyone point me in the right direction?
Code:
def palindrome?(string)
string2 = ""
for i in string
string2 = string[i] + string2
end
if string2 == string1
return true
end
end
palindrome?("abcba")
Error:
hours.rb:7:in `palindrome?': undefined method `each' for 5:Fixnum (NoMethodError)
from hours.rb:17:in `<main>'
The problem is that you can't iterate over a string (like you can for example in Python). You'll first need to convert it to an Array with .split:
for c in string.split
string2 = c + string2
end
That being said, you shouldn't use for loops in Ruby. They're translated internally to an each method, hence your confusing error. It's better to just write each from the get-go:
string.split.each do |c|
string2 = c + string2
end
No Ruby programmer would ever use for in any circumstance, it's only used by people new to Ruby ;-)
Note that the Array.each is just one iteration method; for example there's also the String.each_char method:
string.each_char do |c|
string2 = c + string2
end
Lastly, your code is not correct in several other locations. I'm not going to point out all these errors to you, as it will be much more beneficial and educational for you if you solve this programming exercise yourself ;-)
TL;DR
Aside from being inefficient, your code doesn't work because a String is not an Array, nor does it mix in Enumerator to provide an #each method.
While the String#[] method allows indexing into the string, there is no String#each method to invoke. As a result, you can't use a String object in a Ruby for-loop because it's just syntactic sugar for #each.
Understanding the Exception
I'm not sure what version of Ruby you're running, but the exception you've listed in your post is not reproducible on my system. When running on Ruby 2.3.1, the code generates a fairly clear exception:
for i in string
string2 = string[i] + string2
end
NoMethodError: undefined method `each' for "abcba":String
This is pretty straighforward. It tells you that String has no #each method, which is what the syntactic sugar of for i in string is really invoking under the hood. If you want to iterate, you need some form of Enumerator or Enumerable to work with.
Iterating Over a String
The String class has a number of useful methods for converting a string to an iterable object. Some examples include:
String#each_char can be passed a block, or return an Enumerator
String#chars returns an Array
String#split also returns an Array (e.g. 'abcba'.split //)
For example, your code could be refactored to use a block like so:
string = 'abcba'
tmpstr = ''
string.each_char { |char| tmpstr < char; puts true if tmpstr == 'abcba' }
#=> "abcba"
However, while this highlights how to solve for your exception, it is still needlessly complex and inefficient.
Leveraging Built-In Methods
Unless you're doing this for homework, the right way to do this in Ruby is to leverage the built-in methods that operate at C speeds and don't create temporary Ruby objects that need to be garbage-collected later. For example, to test whether a given string reads the same backwards or forwards, you can simply compare a reversed string to the original using the String#reverse and String#eql? methods.
def palindrome? str
str.reverse.eql? str
end
palindrome? 'abcba'
#=> true
palindrome? 'abcde'
#=> false
You can also use String#== instead of #eql? if you prefer, but I think using the latter is clearer in this case. The method chain makes it clear that you're invoking a String method rather than a bit of language syntax for the comparison. That distinction can be a real help when learning the ins and outs of Ruby's core, but in this case the result will be the same either way.
As Carpetsmoker pointed out, you can't iterate directly over a string. However, Ruby provides both positive and negative indexing for elements. Negative indices are located relative to the end of the array or string. This allows you to do your checking quite efficiently, and short circuit your testing as soon as you identify you don't have a palindrome:
def palindrome?(str)
(0...str.length/2).all? { |i| str[i] == str[-(i+1)] }
end
If you want to be more object-oriented about it, you can convert it to a method in class String:
class String
def palindrome?
(0...length/2).all? { |i| self[i] == self[-(i+1)] }
end
end
p "abcba".palindrome? # => true
Note — Edited to utilize Cary Swoveland's excellent suggestion about using all? rather than an explicit return from the block. This makes it a one-liner.
what you're looking for is:
def palindrome?(string)
string2 = ""
for i in 0...string.length
string2 = string[i] + string2
end
if string2 == string
return true
end
end
Note you could define it simpler:
def palindrome?(string)
string == string.reverse
end
You can write it like this:
def palindrome?(str)
str == str.reverse
end

Ruby array changes by changing a 'copy' of one of its elements

I'm trying to confirm whether my understanding is correct of these six lines of code:
string="this is a sentence"
words=string.split
first_word=words[0]
first_word[0]=first_word[0].upcase
out=words.join(" ")
puts(out)
which prints "This is a sentence" (with the first letter capitalized).
It would appear that changing the "first_word" string, which is defined as the first element of the "words" array, also changes the original "words" array. Is this indeed Ruby's default behavior? Does it not make it more difficult to track where in the code changes to the array take place?
You just need need to distinguish between a variable and an object. Your string is an object. first_word is a variable.
Look for example
a = "hello"
b = a
c = b
now all variables contain the same object, a string with the value "hello". We say they reference the object. No copy is made.
a[0] = 'H'
This changes the first character of the object, a string which now has the value "Hello". Both b and c contain the same, now changed object.
a = "different"
This assigns a new object to the variable a. b and c still hold the original object.
Is this Rubys default behaviour? yes. And it also works like this in many other programming languages.
Does it make it difficult to track changes? Sometimes.
If you takes an element from an array (like your first_word), you need to know:
If you change the object itself, no matter how you access it,
all variables will still hold your object, which just happened to be changed.
But if you replace the object in the array, like words[0] = "That", then all your other variables will still hold the original object.
This behavior is caused by how ruby does pass-by-value and pass-by-reference.
This is probably one of the more confusing parts of Ruby. It is well accepted that Ruby is a pass-by-value, high level programming language. Unfortunately, this is slightly incorrect, and you have found yourself a perfect example. Ruby does pass-by-value, however, most values in ruby are references. When Ruby does an assignment of a simple datatypes, integers, floats, strings, it will create a new object. However, when assigning objects such as arrays and hashes, you are creating references.
original_hash = {name: "schylar"}
reference_hash = original_hash
reference_hash[:name] = "!schylar"
original_hash #=> "!schylar"
original_array = [1,2]
reference_array = original_array
reference_array[0] = 3
reference_array #=> [3,2]
original_fixnum = 1
new_object_fixnum = original_fixnum
new_object_fixnum = 2
original_fixnum #=> 1
original_string = "Schylar"
new_object_string = original_string
new_object_string = "!Schylar"
original_string #=> "Schylar'
If you find yourself needing to copy by value, you may re-think the design. A common way to pass-by-value complex datatypes is using the Marshal methods.
a = {name: "Schylar"}
b = Marshal.load(Marshal.dump(a))
b[:name] = "!!!Schylar"
a #=> {:name => "Schylar"}

Resources