Why can't I reassign a variable in a Ruby code block? - ruby

Why doesn't calling these two .map methods bring about equivalent results? The first one works as expected, whereas the second has no effect.
array = ["batman","boobytrap"]
puts array.map { |x| x.reverse! }
=> namtab
=> partyboob
puts array.map { |x| x = x.reverse }
=> batman
=> boobytrap

Print array after puts array.map { |x| x.reverse! }. You will see - array has changed.
Read documentation for reverse! method.

The problem is that in your first map, the ! has modified the values in the original array, so it now contains reversed strings.
irb:001:0> array = ["batman","boobytrap"]
=> ["batman", "boobytrap"]
irb:002:0> puts array.map { |x| x.reverse! }
namtab
partyboob
=> nil
irb:003:0> array
=> ["namtab", "partyboob"]
So the second time is doing what you expect it to, but the entry data is not what you think it is.
If you try the second case standalone without doing the first one you will see that it works as you expect it to.

You have to change your view of what a variable is here. The variable is not the actual value, but only a reference to that value.
array = ["batman"]
# We are freezing this string to prevent ruby from creating a
# new object for a string with the same value.
# This isnt really necessary, just to "make sure" the memory
# address stays the same.
string = "boobytrap".freeze
array.each do |val|
puts "before: ",
"val.object_id: #{val.object_id}",
"string.object_id: #{string.object_id}",
"array[0].object_id: #{array[0].object_id}",
""
val = string
puts "after: ",
"val.object_id: #{val.object_id}",
"string.object_id: #{string.object_id}",
"array[0].object_id: #{array[0].object_id}"
end
# before:
# val.object_id: 70244773377800,
# string.object_id: 70244761504360,
# array[0].object_id: 70244773377800
#
# after:
# val.object_id: 70244761504360,
# string.object_id: 70244761504360,
# array[0].object_id: 70244773377800
Obviously the values will differ if you run this code on your machine, but the point is, the memory address for val changes, while array[0] (which is where val comes from) stays the same after we assigned string to val. So basically what we do with the reassignment is, we tell ruby the value for val is no longer found in 70244773377800, but in 70244761504360. The array still refers to its first value with 70244773377800 though!
The #reverse! method call you use in your example on x on the other hand changes the value of whatever is found at 70244773377800 in memory, which is why it works as you expected.
TLDR;
Your first example changes the value in memory, while the second example assigns a new memory address to a local variable.

When you do array.map { |x| x.reverse! }, it changed the array values.
array = ["batman","boobytrap"]
puts array.map { |x| x.reverse! }
=> namtab
=> partyboob
array
=> ["namtab", "partyboob"]
If you perform second operation on the same array, it will produce the same results as you have stated in the question. However, it will not change the value of original array.
array = ["batman","boobytrap"]
puts array.map { |x| x.reverse! }
=> namtab
=> partyboob
array
=> ["namtab", "partyboob"]
puts array.map { |x| x = x.reverse }
=> batman
=> boobytrap
array
=> ["namtab", "partyboob"]
To change the value of original array, use map! in second operation.
array = ["batman","boobytrap"]
puts array.map { |x| x.reverse! }
=> namtab
=> partyboob
array
=> ["namtab", "partyboob"]
puts array.map! { |x| x.reverse }
=> batman
=> boobytrap
array
=> ["batman", "boobytrap"]

There are several problems with your approach.
The first problem is that you don't properly isolate your test cases: in your first test case you reverse the strings in the array. In your second test case you reverse them again. What happens if you reverse something twice? That's right: nothing! So, the reason why you think it isn't working is actually precisely the fact that it is working! And if it weren't working (i.e. not reversing the strings), then it would print the previously-reversed strings, and you would think it does work.
So, lesson #1: Always isolate your test cases!
Problem #2 is that the second piece of code doesn't do what you (probably) think it does. x is a local variable (because it starts with a lowercase letter). Local variables are local to the scope they are defined in, in this case the block. So, x only exists inside the block. You assign to it, but you never do anything with it after assigning to it. So, the assignment is actually irrelevant.
What is relevant, is the return value of the block. Now, in Ruby, assignments evaluate to the value that is being assigned, so considering that the assignment to x is superfluous, we can just get rid of it, and your code is actually exactly equivalent to this:
array.map { |x| x.reverse }
Your third problem is that the first piece also doesn't do what you (probably) think it does. Array#map returns a new array and leaves the original array untouched, but String#reverse! mutates the strings! In other words: its primary mode of operation is a side-effect. In addition to the side-effect, it also returns the reversed string, which is another thing that confused you. It could just as well return nil instead to indicate that it performs a side-effect, in which case you would instead see the following:
array = %w[batman boobytrap]
array.map(&:reverse!)
#=> [nil, nil]
array
#=> ['namtab', 'partyboob']
As you can see, if String#reverse! did return nil, what you would observe is the following:
array.map returns a new array whose elements are the return values of the block, which is just nil
array now still contains the same String objects as before, but they have been mutated
Now, since String#reverse! actually does return the reversed String what you actually observe is this:
array = %w[batman boobytrap]
array.map(&:reverse!)
#=> ['namtab', 'partyboob']
array
#=> ['namtab', 'partyboob']
array.map(&:reverse)
#=> ['batman', 'boobytrap']
array
#=> ['namtab', 'partyboob']
Which brings me to lesson #2: side-effects and shared mutable state are evil!
You should avoid both side-effects and shared mutable state (ideally, mutable state in general, but mutable state that is shared between different pieces is especially evil) as far as possible.
Why are they evil? Well, just look at how much they confused even in this extremely simple tiny example? Can you imagine the same thing happening in a much, much larger application?
The problem with side-effects is that they "happen on the side". They are not arguments, they are not return values. You cannot print them out, inspect them, store them in a variable, place assertions on them in a unit test, and so on.
The problem with shared mutable state (mutation is just a form of side-effect, by the way) is that enables "spooky action at a distance": you have a piece of data in one place of your code, but this piece of data is shared with a different place of your code. Now, if one place mutates the data, the other place will seemingly magically have their data change out under them. In your example here, the shared state were the strings inside the array, and mutating them in one line of code made them also change in another line of code.

Related

Why isn't the following code working the way it should?

array = ["car","carrs"]
array.each { |x|
x.capitalize
}
I have tried doing with do too by removing the curly braces and adding do after .each, I have also tried for each in array, but that didnt work too. Am i doing something wrong because nothing gets capitalized?
String#capitalize returns a copy of the object with the first letter capitalized. What you're basically doing is looping through your array and generating new copies of the strings, but then immediately throwing them away.
You have a couple of ways to approach this:
You can use #map rather than #each to take each result of your loop block body and collect it into a new array:
array = ["car","carrs"]
capitalized_array = array.map { |x| x.capitalize }
Or, if you actually want to mutate the original strings, use String#capitalize! rather than capitalize, which mutates the input object, rather than returning a new object:
array = ["car","carrs"]
array.each { |x| x.capitalize! }
While it may seem tempting to use the mutative version, it is frequently a good idea to use non-mutative methods to produce transformations of your data, so you don't lose your original input data. Mutate-in-place can introduce subtle bugs by making the state of the data harder to reason about.
You have to understand the difference between map vs each. You can read it here.
For those who don't want to read that:
Each is like a more primitive version of map. It gives you every element so you can work with it, but it doesn’t collect the results. Each always returns the original, unchanged object. While map does the same thing, but. It returns a new array with the transformed elements.
So, you have to use map in order to return a new array:
array = ["car","carrs"]
capitalized_array = array.map { |x| x.capitalize }
# or
array = ["car","carrs"]
array.map! { |x| x.capitalize }
Now, what is the different between map and map!? We need to read the documentation
map invokes the given block once for each element of self. Creates a new array containing the values returned by the block. While map! invokes the given block once for each element of self, replacing the element with the value returned by the block.

why return change variables while inside a class

I cannot understand this ruby behavior, the code explains better what I mean:
class DoNotUnderstand
def initialize
#tiny_array = [3,4]
test
end
def messing(ary)
return [ary[0]+=700, ary[1]+=999]
end
def test
puts #tiny_array.join(",") # before => 3,4
messing(#tiny_array)
puts #tiny_array.join(",") # after => 703,1003
end
end
question = DoNotUnderstand.new
#tiny_array was [3,4] and became [703,1003]
if I don't use a class, that happens:
#tiny = [1,2]
def messing(ary)
return [ary[0]+693,ary[1]+999]
end
puts #tiny.join(",") # before => 1,2
messing(#tiny)
puts #tiny.join(",") # after => 1,2
the array simply remains [1,2]
why?
The class is a red herring, and completely irrelevant to the issue.
In the first case, where the array was modified, you defined messing as:
def messing(ary)
return [ary[0]+=700, ary[1]+=999]
end
Whereas in the second case, where the array was not modified, you defined messing as:
def messing(ary)
return [ary[0]+693,ary[1]+999]
end
In one case you used +=, and in the other, you used merely +.
ary[0] += 700 is exactly equivalent to ary[0] = ary[0] + 700. In other words you are changing the value stored in the 0th index of ary.
In the second case you merely add to the values stored in the array and return the result, but in the first case you not only return the result, you also store it back in the array.
For an explanation of why modifying ary modifies #tiny_array, see this answer to the question Is Ruby pass by reference or by value?.
You're second code example (the one from outside the class) is missing the two characters in the first that make it work the way it does. In the first example, the += operator is used, modifying the array in place:
return [ary[0]+=700, ary[1]+=999]
In your second example, the + operator is used, leaving the array as is:
return [ary[0]+693,ary[1]+999]
If you change it use the += operator, it works the same way as the first code snippet.

search for `target_item` in `items`; return the array index

Can someone help me understand the code below? I have been trying to add 'puts' to see what it does but keep getting errors. This is a code from an exercise/example I was supposed to already know, yet I have no idea what it is doing. It seems to me that I should have an items array defined before the method for this to make sense, but even then I wasn't able to make sense of it.
# search for `target_item` in `items`; return the array index
# where it first occurs
def find_item(items, target_item)
i = 0
while i < items.count
current_item = items[i]
if current_item == target_item
# found item; return its index; stop here and exit the
# method
return i
end
i += 1
end
# return nil to mean item not found
nil
end
You are correct about needing an array items to run your code. You will also need a value for the variable target_item. These two variables are passed as arguments to the method find_item. If the method finds the value of target_item in the array items it will return the index of that item in the array (0 for the first element, 1 for the second, and so on.); if it does not, it will return nil.
If you run your code in IRB, it will return => :find_item. That means it found no errors. That does not mean there are no errors, however, as error can surface when the code is run.
Let's try it with some data. After running the code defining the method (in IRB), run these three lines:
items = ['dog', 'cat', 'pig']
target_item = 'cat'
find_item(items, target_item) #=> 1
where #=> 1 means that the method returned 1, meaning that it found target_items at offset 1 in items, which is what we would expect.
This is equivalent to:
find_item(['dog', 'cat', 'pig'], 'cat')
On the other hand,
find_item(items, 'bird') #=> nil
as the array does not contain 'bird'. The two lines
current_item = items[i]
if current_item == target_item
can be combined into one:
if items[i] == target_item
but that's beside the point, because Rubiests would not write the method this way. Instead, you would commonly do it like this:
def find_item(items, target_item)
items.each_with_index { |e,i| return i if e == target_item }
nil
end
[Edit: #Mark has correctly pointed out a much simpler way of writing the method. I will stick with this version, however, as I think you will learn something useful by seeing how it works.]
The built-in Ruby method each_with_index is from the Enumgerable module, which is always available to you (along with 60+ other method defined in that module). When you reference a method, it's best to include the class or module in which it was defined. This method is Enumerable#each_with_index. Among other things, its easier to find documentation for the method if you know the class or module its from.
Although each_with_index was defined in the Enumerable module, it is an "enumerator", meaning that it feeds the values from items to the following block, denoted by {...} (as here) or do ... end.
Run this in IRB:
items = ['dog', 'cat', 'pig']
target_item = 'cat'
enum = items.each_with_index
#=> #<Enumerator: ["dog", "cat", "pig"]:each_with_index>
You can convert this to an array to see what it will feed the block:
enum.to_a
#=> [["dog", 0], ["cat", 1], ["pig", 2]]
Consider now the first element the enumerator will be passed to the block:
["dog", 0]
The block variables, e and i in
items.each_with_index { |e,i| return i if e == target_item }
will therefore be set equal to:
e => "dog"
i => 0
Therefore, e == target_item becomes "dog" == "cat", which is false, so return 0 is not executed. For the next items, however,
e => "cat"
i => 1
As "cat" == "cat" is true, return 1 is executed, and we are finished. If items did not contain cat, the enumerator would enumerate all items and control would go to the following statement, nil. As that is the last statement executed, the method would return nil.
This code appears to be a poorly rewritten Array#index. This is identical:
items.index(target_item)

Coercing a scalar or an array to become an array

Sometimes I want a variable to always be an array, whether its a scalar or already an array.
So I normally do:
[variable].flatten
which is compatible with ruby-1.8.5, 1.8.7, 1.9.x.
With this method when variable is a string (variable = "asdf"), it gives me ["asdf"]. If it's already an array (variable = ["asdf","bvcx"]), it gives me: ["asdf","bvcx"].
Does anyone have a better way? "Better" meaning more readable, more performant, succinct or more effective in other ways.
Array(variable)
should do the trick. It uses the little known Kernel#Array method.
The way I do, and think is the standard way, is using [*...]:
variable1 = "string"
variable2 = ["element1", "element2"]
[*variable1] #=> ["string"]
[*variable2] #=> ["element1", "element2"]
You might need something like Array.eat. Most other methods either call #to_a or #to_ary on the object. If you where using [ obj ].flatten that might give surprising results. #flatten will also mangle nested arrays unless called with a level parameter and will make an extra copy of the array.
Active support provides Array.wrap, but that also calls #to_ary, which might or might not be to your liking, depending on your needs.
require 'active_support/core_ext/array/wrap'
class Array
# Coerce an object to be an array. Any object that is not an array will become
# a single element array with object at index 0.
#
# coercing nil returns an empty array.
#
def self.eat( object )
object.nil? and return []
object.kind_of?( Array ) and return object
[object]
end
end # class Array
a = { a: 3 }
p [a].flatten # => [{:a=>3}]
p [*a] # => [[:a, 3]] -> OOPS
p Array a # => [[:a, 3]] -> OOPS
p Array.wrap a # => [{:a=>3}]
p Array.eat a # => [{:a=>3}]

Ruby: How to loop through an object that may or may not be an array?

I have an each method that is run on some user-submitted data.
Sometimes it will be an array, other times it won't be.
Example submission:
<numbers>
<number>12345</number>
</numbers>
Another example:
<numbers>
<number>12345</number>
<number>09876</number>
</numbers>
I have been trying to do an each do on that, but when there is only one number I get a TypeError (Symbol as array index) error.
I recently asked a question that was tangentally similar. You can easily force any Ruby object into an array using Array.
p Array([1,2,3]) #-> [1,2,3]
p Array(123) #-> [123]
Of course, arrays respond to each. So if you force everying into an array, your problem should be solved.
A simple workaround is to just check if your object responds to :each; and if not, wrap it in an array.
irb(main):002:0> def foo x
irb(main):003:1> if x.respond_to? :each then x else [x] end
irb(main):005:1> end
=> nil
irb(main):007:0> (foo [1,2,3]).each { |x| puts x }
1
2
3
=> [1, 2, 3]
irb(main):008:0> (foo 5).each { |x| puts x }
5
=> [5]
It looks like the problem you want to solve is not the problem you are having.
TypeError (Symbol as array index)
That error tells me that you have an array, but are treating it like a hash and passing in a symbol key when it expects an integer index.
Also, most XML parsers provide child nodes as array, even if there is only one. So this shouldn't be necesary.
In the case of arguments to a method, you can test the object type. This allows you to pass in a single object or an array, and converts to an array only if its not one so you can treat it identically form that point on.
def foo(obj)
obj = [obj] unless obj.is_a?(Array)
do_something_with(obj)
end
Or something a bit cleaner but more cryptic
def foo(obj)
obj = [*obj]
do_something_with(obj)
end
This takes advantage of the splat operator to splat out an array if it is one. So it splats it out (or doesn't change it) and you can then wrap it an array and your good to go.
I was in the same position recently except the object I was working with was either a hash or an array of hashes. If you are using Rails, you can use Array.wrap because Array(hash) converts hashes to an array.
Array({foo: "bar"}) #=> [[:foo, "bar"]]
Array.wrap({foo: "bar"}) #=> [{:foo=>"bar"}]
Array.wrap(123) #=> [123]
Array.wrap([123]) #=> [123]
I sometimes use this cheap little trick:
[might_be_an_array].flatten.each { |x| .... }
Use the splat operator:
[*1] # => [1]
[*[1,2]] # => [1,2]
Like Mark said, you're looking for "respond_to?" Another option would be to use the conditional operator like this:
foo.respond_to? :each ? foo.each{|x| dostuff(x)} : dostuff(foo);
What are you trying to do with each number?
You should try to avoid using respond_to? message as it is not a very object oriented aproach.
Check if is it possible to find in the xml generator code where it is assigning an integer value when there is just one <"number"> tag and modify it to return an array.
Maybe it is a complex task, but I would try to do this in order to get a better OO design.
I don't know much anything about ruby, but I'd assume you could cast (explicitly) the input to an array - especially given that if the input is simply one element longer it's automatically converted to an array.
Have you tried casting it?
If your input is x, use x.to_a to convert your input into an array.
[1,2,3].to_a
=> [1, 2, 3]
1.to_a
=> [1]
"sample string".to_a
=> ["sample string"]
Edit: Newer versions of Ruby seem to not define a default .to_a for some standard objects anymore. You can always use the "explicit cast" syntax Array(x) to achieve the same effect.

Resources