Modifying arguments in Ruby - ruby

I apologize for the excessive length, I just wanted to make sure I actually understand what's going on here. This is a follow up on my previous two questions Does 'upcase!' not mutate a variable in Ruby? and Destructive and non destructive methods in Ruby.
def changeMe(string)
string + "?"
end
phrase = "how are you"
puts changeMe(phrase) #how are you?
puts phrase #how are you
When changeMe is invoked with the phrase passed in as an argument, the parameter string points to the same object as phrase. When we change the line to string + "?" we are creating a new object different from the one the string parameter points to, the same if we assigned the newly created object to a variable.
def changeMe(string)
string += "?"
end
phrase = "how are you"
puts changeMe(phrase) #how are you?
puts phrase #how are you
If I do this -
def changeMe(string)
string + "?"
string.capitalize!
end
phrase = "how are you"
puts changeMe(phrase) #How are you
puts phrase #How are you
When changeMe is invoked with phrase passed in as an argument, the string + "?" creates a new object different from the one #capitalize! is called on in the next line. #capitalize! is called on the object that the variable phrase is referencing, the same object the string parameter points to but not the same object returned by string + ?. If we reassign it to a variable,
def changeMe(string)
string += "?"
string.capitalize!
end
phrase = "how are you"
puts changeMe(phrase) #How are you?
puts phrase #how are you
string += "?" will create a new object that is assigned to a variable called string. That new object has #capitalize! called on it. The method is invoked with phrase passed in as an argument and returns a new object different from the one the variable phrase references so the original value for the variable phrase is unchanged.
Are there flaws or misconceptions in my logic. Am I accurately explaining/understanding things?

That's largely correct, but perhaps a more complicated path to understanding than necessary. In Ruby one thing that helps a lot is calling object_id on a given object to see which object it is. Every object has a unique object_id.
For example:
"test" == ("te" + "st")
# => true
"test".object_id == ("te" + "st").object_id
# => false
Or more specifically for a method that creates a new copy:
x = 'test'
y = x + '?'
x.object_id == y.object_id
# => false
You can see how in-place modifications work:
x = 'test'
y = x << '?'
x.object_id == y.object_id
# => true
Where this allows you to differentiate between in-place modifications and methods that produce new objects or copies.
Remember that every Ruby expression returns an object. If this object is not captured into a variable or used as an argument will often can be discarded if not already used.
In other words there's a huge difference between this:
def add
1 + 2 # Computed and discarded
:three # The actual return value
end
And this:
def add
1 + 2 # Computed and returned
end
Though this depends on that return value being captured, as calling the function computes the value and throws out the results again unless it's captured or used.

Related

How to do user-inputted string templating in Ruby?

I know writing like
a=23
p "the value of a is #{a}"
it will print: the value of a is 23.
but now I am actually receiving this string as a parameter like
def evaluate string
a=23
puts string
end
calling method pass that string as a parameter
evaluate "the value of a is #{a}"
Is there any way to evaluate this string inside the method? puts string has to interpolate the value a=23.
Edit:
I have to read and execute the program from Excel.
At the first line,
Excel entry is,
"id=something" setvalue a
So now corresponding program will read the value from locator id=something and set it into the instance variable #a.
and user's next excel entry would be
"the value of a is 23" compare "the value of a is #{a}"
Now the program will read "the value of a is 23" and this "the value of a is #{a}" for comparison, but before it compares, it has to replace the value a. That's all I want. I hope now my question is very clear.
For ruby you can change how you "format" your strings in Excel, than you can use "classic" formatting
a = 23
s = 'the value of a is %s'
def evaluate(text, value)
puts text % value
end
You can use different formatting keys, for example %d for integers, %f for float numbers
You can use named arguments
dynamic_text = 'the value of the %<product_name>s is %<product_price>0.2f'
def evaluate(text, args)
puts text % args
end
name = "Product"
price = 78.99
evaluate dynamic_text, product_name: name, product_price: price
Without names, use order of the given values
dynamic_text = 'the value of the %s is %0.2f'
def evaluate(text, args)
puts text % args
end
name = "Product"
price = 78.99
evaluate dynamic_text, [name, price]
You can make a block and then evaluate the string:
def evaluate &block
a=23
block.call(a)
end
evaluate { |a| "the value of a is #{a}" } #=> "the value of a is 23"
It's a very odd thing you're attempting to do. When you have some sort of a pattern with placeholders, you do it like:
def evaluate(string)
a=23
format string, a: a
end
evaluate "the value of a is %{a}"
String interpolation with #{..} is not meant for the case you're describing as the value is evaluated at the time of constructing the string, not later. You could do some regexp matching and replace the #{..} with %{..} as a workaround.
There's a few ways:
"Code" Dynamic
lazy evaluation with lambdas:
def evaluate(str_template)
a = 23
str_template.call(a)
end
user_input = gets
my_lambda = lambda do |str|
user_input.size > 10 ? "dynamic 1 #{str}" : "dynamic 2 #{str}"
end
evaluate(my_lambda)
# => "dynamic 1/2 23"
This is "code dynamic", but not "input dynamic", i.e. you can't receive the string template from the user.
"Input" Dynamic 1
ERB templating:
require 'erb'
user_input_erb = gets
puts user_input_erb # "Hello <%= name %>"
name = gets # also user input, e.g. "World"
ERB.new(user_input_erb).result
# => "Hello World"
Note that in general, getting string templates from the user and evaluating them is a potential security vulnerability. If there's any possibility user input can be adversarial, you'll want to see if you can find a "guaranteed to be safe against all user input" string templating library.
"Input" Dynamic 2
user_input_template = gets
puts user_input_template # "Hello %s"
name = gets # also user input, e.g. "World"
user_input_template % name
# => "Hello World"
"Input" Dynamic 3
Really dangerous, but:
user_input_ruby_code = gets
puts user_input_ruby_code # '"Hello #{name}"'
name = gets # also user input, e.g. "World"
eval user_input_ruby_code # DANGER
# => "Hello World"

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.

Does it matter which way a string method is used?

Codeacademy teaches that you can chain multiple methods together as such:
user_input.method1.method2.method3
However, in a later lesson they display some methods like this:
user_input = gets.chomp
user_input.downcase!
I combined them:
user_input = gets.chomp.downcase!
When I use it this way:
user_input = gets.chomp.downcase!
if user_input.include? "s"
...
I receive an error "undefined method `include?'". If I change it to the following, it works fine:
user_input = gets.chomp
user_input.downcase!
if user_input.include? "s"
...
I'm at a loss. I'm concerned whether or not this is a quirk with their console or if this is just how I should be doing it in Ruby. If someone could tell me which way is right, I'd appreciate it. If both are right, that's OK too.
Firstly, in case you do not yet fully understand, assignment of values to variables are done through =, and that you could inspect what variable type it is by appending .class to anything.
Consider the following:
name = 'John'
puts name
# => John
puts name.class
# => String
Now, secondly, it should be noted that the return values of ALL methods are ALL different. But all of them can be identified into two types:
Methods that:
return self
return anything other than self
Example for 1.
-- methods that return self, which you could say methods that return the same type of object which in our specific case, a String
name = 'John'
puts name
# => 'John'
puts name.class
# => String
downcased_name = name.downcase
puts downcased_name
# => john
puts downcased_name.class
# => String
deleted_downcased_name = downcased_name.delete('h')
puts deleted_downcased_name
# => jon
puts deleted_downcased_name.class
# => String
# All of the above can be just simplified into:
deleted_downcased_name2 = 'John'.downcase.delete('h')
puts deleted_downcased_name2
# => jon
puts deleted_downcased_name2.class
# => String
Notice that deleted_downcased_name and deleted_downcased_name2 are the same, because you could treat the chained methods as if you are chaining the return values which is 'John' -> 'john' -> 'jon'.
Example for 2
-- methods that return anything but self, which you could say methods that return a different type.
In our specific case, String's downcase! returns either a String or NilClass (reference here)
returning String if the string changes, or
returning nil if string is already downcased to begin with (no change).
or another String's method: start_with? (reference here)
returning true or false
This is where chaining of methods will not work (raises an error), when you try to use a String method as a chain to nil value.
Consider the following
name = 'mary'
puts name
# => 'mary'
puts name.class
# => String
downcased_name = name.downcase!
puts downcased_name
# => nil
puts downcased_name.class
# => NilClass
downcased_name.delete('h')
# => This will raise the following error
# NoMethodError: undefined method `delete' for nil:NilClass
The error above is because downcased_name is a type of NilClass where you are expecting it to be a type of String. Therefore you cannot chain any string method on it anymore. You can only chain String methods on a String type of value. Similarly, you can only chain Number methods on a Number type of value.
Whenever in doubt, you could always check the documentation to check what a method does, and what its return value and type.
The problem you are encountering is with the bang method downcase!.
This is basically saying "mutate the original string so that it is downcase".
The important part is that this returns nil. As such you are actually calling include? on nil.
If you use the non bang method downcase instead, it is saying "downcase the previously chained thing but do not mutate the original". The key difference is that it returns the result rather than nil.
Here is an example:
str = "ABCD"
str.downcase!
=> nil
str
=> "abcd"
str = "ABCD"
str.downcase
=> "abcd"
str
=> "ABCD" # Note str is still unchanged unless you set str = str.downcase
Welcome to Ruby! While your apprenticeship at Codeacademy may be limited, you'll continue to refer to language API documentation throughout your career. API documentation is a description of what the language (or a library) does for you. In this case, you're using downcase! which, as one commenter points out, does not always return a String. When it takes no action, it returns nil. Nil is an Object in Ruby (like everything else), but the 'include?' method isn't defined for nil, which explains your error. (It's one of the most common errors in Ruby, learn its meaning.)
So, in fact, what's breaking here isn't your method chain. It's that one of the intermediate methods isn't returning a value of the type you expect (nil instead of some kind of String).
Chaining non destructive methods like:
string.chomp.downcase...
has the advantage that the code is concise, but is not efficient if you are not interested in the original state of the object, and just want the final result because it creates intermediate objects during the chain.
On the other hand, applying destructive methods sequentially to the same object:
string.chomp!
string.downcase!
...
is more efficient if you do not need to keep the original state of the object, but is not concise.
Combining methods that may return an object of a different class (particularly nil) as:
string = gets.chomp!.downcase!...
is wrong because the result can become nil at some point in the chain.
Applying a potentially nil-returning method at only the last position as you did:
string = gets.chomp.downcase!
is still not useful if you expect string to always be a string, and can easily lead to an error as you did.
If you want to chain these methods in you example, perhaps you can do this:
user_input = gets.tap(&:chomp!).tap(&:downcase!)
if user_input.include?("s")
...

Ruby: Why is my method non-destructive?

I'm trying to create two methods, one of which will destructively add "i" to any string, and one of which will do the same non-destructively.
def add_i(string)
new_string = string + "i"
end
def add_i!(string)
string = string + "i"
end
My questions are:
Both of these methods are non-destructive, even though I do not replace the argument in the second method with a new variable. Why?
In general, how do I convert a non-destructive method into a destructive one and vice versa?
The answer lies in the scope of the vars and the behavior of the methods/operators. the left hand side (left to the = )string inside add_i! is a different string than the one passed in (the right side string and the method arg). The old string continues to live on but the string var points to the new one.
to make the 2nd method "destructive" you could do something like:
def add!(string)
string << "i"
end
as a rule of the thumb, you need to understand if the methods/operators you are applying are operating on the data itself or are returning a copy of the data (for example the '+' upstairs returns a copy)
an easy way of dealing with string and making sure you don't destroy the data is to use dup() on whatever is passed in and after that operate on the copy.
The problem is + returns a copy of the string. I guess this is analgous to adding numbers which will return two numbers. string += 'i' also makes a copy. This a bit surprising, but it is the same as what numbers do.
You can see this by checking the object_id at each point.
def add_i!(string)
puts "string passed into add_i! #{string.object_id}"
string = string + "i"
puts "string in add_i after + i #{string.object_id}"
end
foo = "blah"
puts "string about to be passed into add_i! #{foo.object_id}"
add_i!(foo)
puts "string after calling add_i! #{foo.object_id}"
string about to be passed into add_i! 70364940039240
string passed into add_i! 70364940039240
string in add_i after + i 70364940039020
string after calling add_i! 70364940039240
Note that string in add_i after + i has a different object id.
<< and concat both append to the existing string. concat should probably be concat! but it isn't.

How to iterate only a specific value position in a ruby hash?

I know the first value of all ##logHash keys contains IP addresses. I want to iterate just that position to create keys for a new hash if its not a duplicate key.
Here is what I have but I know it can't be right...
def ipaddresses(##logHash)
##ipHash = Hash.new
##logHash[1].each_value do | value |
if ##ipHash.has_key?(value)
##ipHash[value] += "#"
else
##ipHash[value] = "#"
end
puts ""
##ipHash.sort.each { |key,value| puts "The frequency of #{key} is |#{value}"}
end
end
Any help is appreciated, thanks!
Lisa
Here's a reworked version that might be closer to what you want:
def ipaddresses(logHash)
ipHash = Hash.new(0)
logHash[1].each_value do | value |
ipHash[value] += 1
puts ""
end
ipHash.sort.each { |key,value| puts "The frequency of #{key} is |#{value}"}
end
It's not clear why you're using ## class variables in a method like this. They're very unusual to be using in any context. For temporary variables or method arguments, no prefix is required.
Here Hash.new(0) creates a new hash with a default value of 0. This avoids having to pre-initialize the keys before using them as in Ruby adding anything to nil is considered invalid.
You cannot have a class variable (or anything other than a local variable) as an argument. It does not make sense to do that. Arguments are something that are passed together with a method call. If you want to refer to a class variable within the method definition, you can just refer to that directly. Having it passed via argument is redundant, and is hence made impossible by design.

Resources