generalize map and reduce lab - ruby

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.

Related

Find previous element in Ruby array Enumerable Enumerator

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

Find all indices of a substring within a string

I want to be able to find the index of all occurrences of a substring in a larger string using Ruby. E.g.: all "in" in "Einstein"
str = "Einstein"
str.index("in") #returns only 1
str.scan("in") #returns ["in","in"]
#desired output would be [1, 6]
The standard hack is:
indices = "Einstein".enum_for(:scan, /(?=in)/).map do
Regexp.last_match.offset(0).first
end
#=> [1, 6]
def indices_of_matches(str, target)
sz = target.size
(0..str.size-sz).select { |i| str[i,sz] == target }
end
indices_of_matches('Einstein', 'in')
#=> [1, 6]
indices_of_matches('nnnn', 'nn')
#=> [0, 1, 2]
The second example reflects an assumption I made about the treatment of overlapping strings. If overlapping strings are not to be considered (i.e., [0, 2] is the desired return value in the second example), this answer is obviously inappropriate.
This is a more verbose solution which brings the advantage of not relying on a global value:
def indices(string, regex)
position = 0
Enumerator.new do |yielder|
while match = regex.match(string, position)
yielder << match.begin(0)
position = match.end(0)
end
end
end
p indices("Einstein", /in/).to_a
# [1, 6]
It outputs an Enumerator, so you could also use it lazily or just take the n first indices.
Also, if you might need more information than just the indices, you could return an Enumerator of MatchData and extract the indices:
def matches(string, regex)
position = 0
Enumerator.new do |yielder|
while match = regex.match(string, position)
yielder << match
position = match.end(0)
end
end
end
p matches("Einstein", /in/).map{ |match| match.begin(0) }
# [1, 6]
To get the behaviour described by #Cary, you could replace the last line in block by position = match.begin(0) + 1.
#Recursive Function
def indexes string, sub_string, start=0
index = string[start..-1].index(sub_string)
return [] unless index
[index+start] + indexes(string,sub_string,index+start+1)
end
#For better Usage I would open String class
class String
def indexes sub_string,start=0
index = self[start..-1].index(sub_string)
return [] unless index
[index+start] + indexes(sub_string,index+start+1)
end
end
This way we can call in this way: "Einstein".indexes("in") #=> [1, 6]

Ruby: return vs puts

I have the following two methods using return and puts.
Method 1:
def method array
array.each do |n|
v = n * n
return n if v.even?
end
end
puts method [1, 2, 3, 4] #=> 2
Method 2:
def method array
array.each do |n|
v = n * n
puts n if v.even?
end
end
method [1, 2, 3, 4] #=> 2, 4
What makes the first method return the first item (2) that meets the condition, as opposed to returning the second one (4)?
You are using the return statement inside the first method so that it will exit from the loop when v.even? condition is true. That's why it is returning only 2.
In second method, your loop will check each element, and will return all elements when v.even? condition is true.
No need for a new method here
[1, 2, 3, 4].select{|e|e.even?}
allready does what you want and can be chained with other methods.

How to refactor this code to remove output variable?

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.

What is the difference between << and +=?

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

Resources