I have been playing around with arrays a bit, and found myself in trouble understanding the following code:
first_array = []
second_array = []
third_array = [] # I initialized 3 empty arrays
third_array << [1,2,3,4,5,6,7,8,9] # I loaded 1..9 into third_array[0]
puts third_array.size # => 1
first_array << third_array # 1..9 was loaded into first_array[0]
second_array += third_array # 1..9 was loaded into second_array[0]
puts first_array == third_array # false
puts second_array == third_array # true
puts first_array == second_array # false
puts first_array.size # 1
puts second_array.size # 1
puts third_array.size # 1
What happened with this?
second_array += third_array # I have no clue
Why aren't all the arrays equal to each other?
They exhibit fairly different behaviors. One creates and assigns a new Array object, the other modifies an existing object.
+= would be the same as second_array = second_array + third_array. This sends the + message to the second_array object passing third_array as the argument.
Per the documentation Array.+ returns a new array object built by concatenating the two arrays. This will return a new object.
Array.<< simply push the parameter to the end of the existing array object:
second_array = []
second_array.object_id = 1234
second_array += [1,2,3,4]
second_array.object_id = 5678
second_array << 5
second_array.object_id = 5678
There is also a difference in how the parameter is added. By adding other elements, it will help see why your arrays are not equal:
second_array = [1, 2, 3]
# This will push the entire object, in this case an array
second_array << [1,2]
# => [1, 2, 3, [1,2]]
# Specifically appends the individual elements,
# not the entire array object
second_array + [4, 5]
# => [1, 2, 3, [1,2], 4, 5]
This is because Array.+ uses concatenation instead of pushing. Unlike Array.concat which modifies the existing object, Array.+ returns a new object.
You can think of a Ruby implementation like:
class Array
def +(other_arr)
dup.concat(other_arr)
end
end
In your specific example, your objects look like this at the end:
first_array = [[[1, 2, 3, 4, 5, 6, 7, 8, 9]]] # [] << [] << (1..9).to_a
second_array = [[1, 2, 3, 4, 5, 6, 7, 8, 9]] # [] + ([] << (1..9).to_a)
third_array = [[1, 2, 3, 4, 5, 6, 7, 8, 9]] # [] << (1..9).to_a
<< appends an item to an array
+= adds an array to an array.
Examples:
[1,2] << 3 # returns [1,2,3]
[1,2] += [3,4] # returns [1,2,3,4]
The last difference not mentioned so far between << and += is, that << is a method:
class Array
def << other
# performs self.push( other )
end
end
whereas += is syntax:
a += b
and is merely a shorthand for writing:
a = a + b
So, in order to modify += behavior, one has to modify the + method:
class Array
def + other
# ....
end
end
Related
I'm working on a lab Using a generalized map method to pass an element and block through returning multiple outcomes.
Really struggled on this one. Found some responses but they don't really make sense to me.
Here is the code:
def map(s)
new = []
i = 0
while i < s.length
new.push(yield(s[i]))
i += 1
end
new
end
Here's is the test:
it "returns an array with all values made negative" do
expect(map([1, 2, 3, -9]){|n| n * -1}).to eq([-1, -2, -3, 9])
end
it "returns an array with the original values" do
dune = ["paul", "gurney", "vladimir", "jessica", "chani"]
expect(map(dune){|n| n}).to eq(dune)
end
it "returns an array with the original values multiplied by 2" do
expect(map([1, 2, 3, -9]){|n| n * 2}).to eq([2, 4, 6, -18])
end
it "returns an array with the original values squared" do
expect(map([1, 2, 3, -9]){|n| n * n}).to eq([1, 4, 9, 81])
end
end
I don't get how the above code can give you these 4 different results.
Could someone help me understand it ?
Thank you for your help!
How your method map works
To see how your method operates let's modify your code to add some intermediate variables and some puts statements to show the values of those variables.
def map(s)
new = []
i = 0
n = s.length
puts "s has length #{n}"
while i < n
puts "i = #{i}"
e = s[i]
puts " Yield #{e} to the block"
rv = yield(e)
puts " The block's return value is #{rv}. Push #{rv} onto new"
new.push(rv)
puts " new now equals #{new}"
i += 1
end
puts "We now return the value of new"
new
end
Now let's execute the method with one of the blocks of interest.
s = [1, 2, 3, -9]
map(s) { |n| n * 2 }
#=> [2, 4, 6, -18] (return value of method)
The following is displayed.
s has length 4
i = 0
Yield 1 to the block
The block's return value is 2. Push 2 onto new
new now equals [2]
i = 1
Yield 2 to the block
The block's return value is 4. Push 4 onto new
new now equals [2, 4]
i = 2
Yield 3 to the block
The block's return value is 6. Push 6 onto new
new now equals [2, 4, 6]
i = 3
Yield -9 to the block
The block's return value is -18. Push -18 onto new
new now equals [2, 4, 6, -18]
We now return the value of new
It may by of interest to execute this modified method with different values of s and different blocks.
A replacement for Array#map?
Is this a replacement for Array#map (or Enumerable#map, but for now let's just consider Array#map)? As you defined it at the top level your map is an instance method of the class Object:
Object.instance_methods.include?(:map) #=> true
It must be invoked map([1,2,3]) { |n| ... } whereas Array#map is invoked [1,2,3].map { |n| ... }. Therefore, for your method map to be a replacement for Array#map you need to define it as follows.
class Array
def map
new = []
i = 0
while i < length
new.push(yield(self[i]))
i += 1
end
new
end
end
[1, 2, 3, -9].map { |n| n * 2 }
#=> [2, 4, 6, -18]
Simplify
We can simplify this method as follows.
class Array
def map
new = []
each { |e| new << yield(e) }
new
end
end
[1, 2, 3, -9].map { |n| n * 2 }
#=> [2, 4, 6, -18]
or, better:
class Array
def map
each_with_object([]) { |e,new| new << yield(e) }
end
end
See Enumerable#each_with_object.
Note that while i < length is equivalent to while i < self.length, because self., if omitted, is implicit, and therefore redundant. Similarly, each { |e| new << yield(e) } is equivalent to self.each { |e| new << yield(e) } and each_with_object([]) { ... } is equivalent to self.each_with_object([]) { ... }.
Are we finished?
If we examine the doc Array#map carefully we see that there are two forms of the method. The first is when map takes a block. Our method Array#map mimics that behaviour and that is the only behaviour needed to satisfy the given rspec tests.
There is a second form, however, where map is not given a block, in which case it returns an enumerator. That allows us to chain the method to another. For example (with Ruby's Array#map),
['cat', 'dog', 'pig'].map.with_index do |animal, i|
i.even? ? animal.upcase : animal
end
#=> ["CAT", "dog", "PIG"]
We could modify our Array#map to incorporate this second behaviour as follows.
class Array
def map
if block_given?
each_with_object([]) { |e,new| new << yield(e) }
else
to_enum(:map)
end
end
end
[1, 2, 3, -9].map { |n| n * 2 }
#=> [2, 4, 6, -18]
['cat', 'dog', 'pig'].map.with_index do |animal, i|
i.even? ? animal.upcase : animal
end
#=> ["CAT", "dog", "PIG"]
See Kernel#block_given? and Object#to_enum.
Notes
You might use, say, arr, rather than s as the variable holding the array, as s often denotes a string, just as h typically denotes a hash. One generally avoids names for variables and custom methods that are the names of core Ruby methods. That is also an objection to your use of new as a variable name, as there are many core methods named new.
I have an array a = [1, 2, 3, 4, 5]
If I have an element in the array, I can find the next element in the array via the
a[0].next
Is there a method that I can use to find the previous element
a[1].previous or a[1].before such that the result is
#=> 1
I Googled around a bit and it seems as if most other similar questions involve a loop which I don't want to do. I also checked out the Ruby docs of Enumerator & Enumerable and couldn't seem to find one.
>> a = [1, 2, 3, 4, 5] #=> [1, 2, 3, 4, 5]
>> a[0].next #=> 2
>> a[2].previous
NoMethodError: undefined method `previous' for 3:Fixnum
from (irb):14
from /Users/Lois/.rvm/rubies/ruby-2.3.1/bin/irb:11:in `<main>'
>> a[2].before
NoMethodError: undefined method `before' for 3:Fixnum
from (irb):15
from /Users/Lois/.rvm/rubies/ruby-2.3.1/bin/irb:11:in `<main>'
Thanks in advance!
If you have an array:
a = [ 1, 2, 3, 4 ]
Then calling a[1] will return the object 2, and there's nothing special about that value, it's without context once you've retrieved it. As such, calling next on it will always yield 3 because that's what Integer#next does.
In order to navigate through that array you need an iterator of some sort, like Enumerator:
e = a.each
Now you can do what you want:
e.next
# => 1
e.next
# => 2
Note that this interface isn't as flexible as you're expecting. You can call next to advance, or rewind to go back to the beginning, but there's no previous. Ruby doesn't have what other languages term as bi-directional iterators, but you might be able to extend this class to add the functions you need if you're feeling brave.
I always end up whipping up something like this.
This approach, of course, isn't limited to Arrays - I didn't want to make it an EnumerableScanner, however, as some enumerations maintain complex state (think of a fibonacci sequence).
class ArrayScanner
attr_reader :index, :array
def initialize(array)
#array = array
#index = 0
end
def next
raise StopIteration unless next?
#array[#index += 1]
end
def peek
raise StopIteration unless next?
#array[#index + 1]
end
def next?
#index + 1 != #array.size
end
def prev
raise StopIteration unless prev?
#array[#index -= 1]
end
def peek_prev
raise StopIteration unless prev?
#array[#index - 1]
end
def prev?
#index - 1 >= 0
end
def eof?
!next?
end
def bof?
!prev?
end
def current
#array[#index]
end
def current=(new_value)
#array[#index] = new_value
end
def size
#array.size
end
def pos
#index
end
def rewind
#index = 0
end
end
a = ArrayScanner.new [1, 2, 3, 4, 5]
a.current #=> 1
a.next #=> 2
a.current #=> 2
a.prev #=> 1
a.prev? #=> false
a.bof? #=> true
4.times { a.next }
a.eof? #=> true
a.current #=> 5
The crystal language has a similar approach, with it's Iterator module.
a = [1, 2, 3, 4, 5]
i = 0
a[i] #=> 1
a[i.next] #=> 2
a[i.pred] #=> 5
i = 1
s = 1
a[i] #=> 2
a[i + s] #=> 3
a[i - s] #=> 1
I build the array here:
def initialize
#names = []
end
#names << page.all('//*[#id="USERS_AVAIL"]/option').map {|result| result.text.split(", ")}
later on I'm trying to compile and visit url's by iterating through the names array like so:
#names.each do |name|
visit "https://example.com/k=#{name}&tpe=1"
end
Some puts statements show me that the each method is calling every element of the array all at once instead of iterating as intended. I.E.: "https://example.com/k=#{[[%22Adviento%22,%20%22Justin%22],%20[%22Asamoah%22,%20%22Nathan%22],%20[%22Baughman%22,%20%22Zachary%22],}&tpe=1". #names.length has a count of only 4 but a puts of the #names array shows the proper output? I'm not sure what could be wrong, thanks in advance for any assist.
Replace << with +=. The << is inserting the entire array as a single element of its own, whereas += will concatenate the array, which seems to be your intention.
For example:
a = [1,2,3]
# => [1, 2, 3]
a << [4,5,6]
# => [1, 2, 3, [4, 5, 6]] # WRONG
a = [1,2,3]
# => [1, 2, 3]
a += [4,5,6]
# => [1, 2, 3, 4, 5, 6] # CORRECT
Try:
#names += page.all('//*[#id="USERS_AVAIL"]/option')
.map { |r| r.text.split(',').map(&:strip) }.flatten
If the quotes are in the literal form %22 and you want to capture the strings in between them:
#names += page.all('//*[#id="USERS_AVAIL"]/option')
.map { |r| r.text.scan(/%22([^%]+)%22/) }.flatten
def peel array
output = []
while ! array.empty? do
output << array.shift
mutate! array
end
output.flatten
end
I have not included the mutate! method, because I am only interested in removing the output variable. The mutate! call is important because we cannot iterate over the array using each because array is changing.
EDIT: I am getting an array as output, which is what I want. The method works correctly, but I think there is a way to collect the array.shift values without using a temp variable.
EDIT #2: OK, here is the mutate! method and test case:
def mutate! array
array.reverse!
end
a = (1..5).to_a
peel( a ).should == [ 1, 5, 2, 4, 3 ]
It doesn't matter if peel modifies array. I guess it should be called peel!. Yes, mutate! must be called after each element is removed.
All this reversing makes me dizzy.
def peel(array)
indices = array.size.times.map do |i|
i = -i if i.odd?
i = i/2
end
array.values_at(*indices) # indices will be [0, -1, 1, -2, 2] in the example
end
a = (1..5).to_a
p peel(a) #=>[1, 5, 2, 4, 3]
Another approach:
def peel(array)
mid = array.size/2
array[0..mid]
.zip(array[mid..-1].reverse)
.flatten(1)
.take(array.size)
end
Usage:
peel [1,2,3,4,5,6]
#=> [1, 6, 2, 5, 3, 4]
peel [1,2,3,4,5]
#=> [1, 5, 2, 4, 3]
Here's a way using parallel assignment:
def peel array
n = array.size
n.times {|i| (n-2-2*i).times {|j| array[n-1-j], array[n-2-j] = array[n-2-j], array[n-1-j]}}
array
end
peel [1,2,3,4,5] # => [1,5,2,4,3]
peel [1,2,3,4,5,6] # => [1,6,2,5,3,4]
What I'm doing here is a series of pairwise exchanges. By way of example, for [1,2,3,4,5,6], the first 6-2=4 steps (6 being the size of the array) alter the array as follows:
[1,2,3,4,6,5]
[1,2,3,6,4,5]
[1,2,6,3,4,5]
[1,6,2,3,4,5]
The 1, 6 and the 2 are in now the right positions. We repeat these steps, but this time only 6-4=2 times, to move the 5 and 3 into the correct positions:
[1,6,2,3,5,4]
[1,6,2,5,3,4]
The 4 is pushed to the end, it's correct position, so we are finished.
So the problem I'm having is understanding the difference between = self and = self dup. When I run the code below any array I put into it is permanently changed.
class Array
def pad(min_size, value = nil)
input = self
counter = min_size.to_i - self.length.to_i
counter.times do
input.push(value)
end
input
end
end
But then I noticed if I put input = self.dup it would not permanently change my array. Can someone explain why? Thanks!
class Array
def pad(min_size, value = nil)
input = self.dup
counter = min_size.to_i - self.length.to_i
counter.times do
input.push(value)
end
input
end
end
Check their object_id which will give you answer,by saying that self and self#dup are 2 different object.
class Array
def dummy
[self.object_id,self.dup.object_id]
end
def add
[self.push(5),self.dup.push(5)]
end
end
a = [12,11]
a.dummy # => [80922410, 80922400]
a.add # => [[12, 11, 5], [12, 11, 5, 5]]
a # => [12, 11, 5]
Because self.dup returns a shallow copy of the array and so the code will not change the array permanently.
Reference
When you made a duplicate of your array, you ended up with two separate arrays that had the same elements in them, like in this example:
x = [1, 2, 3]
y = [1, 2, 3]
x << 4
p x
p y
--output:--
[1, 2, 3, 4]
[1, 2, 3]
You wouldn't expect changes to the x array to affect the y array, would you? Similarly:
arr = [1, 2, 3]
puts "arr = #{arr}" #arr = [1, 2, 3
copy = arr.dup
puts "copy = #{copy}" #copy = [1, 2, 3]
arr << 4
puts "arr = #{arr}" #arr = [1, 2, 3, 4]
puts "copy = #{copy}" #copy = [1, 2, 3]
copy << "hello"
puts "arr = #{arr}" #arr = [1, 2, 3, 4]
puts "copy = #{copy}" #copy = [1, 2, 3, "hello"]
In that example, arr plays the role of self, and copy plays the role of self.dup. The arrays self and self.dup are different arrays which happen to have the same elements.
An array can have an unlimited number of variables that refer to it:
arr = [1, 2, 3]
puts "arr = #{arr}" #arr = [1, 2, 3]
input = arr
input << 4
puts "arr = #{arr}" #arr = [1, 2, 3, 4]
Now let's make input refer to another array:
copy = arr.dup
input = copy
input << "hello"
puts "copy = #{copy}" #copy = [1, 2, 3, 4, "hello"]
puts "input = #{input}" #input = [1, 2, 3, 4, "hello"]
puts "arr = #{arr}" #arr = [1, 2, 3, 4]