How to create a custom implementation of inject method? - ruby

It looks pretty easy at first. Here is the first code block that I wrote in a few minutes:
module Enumerable
def my_inject(memo=false)
memo = memo ? memo : self[0]
self.my_each do |item|
memo = yield(memo, item)
end
return memo
end
end
When the inject method is called with an initial value, it works as expected. But I haven't been able to find a way to make it properly work without any initial value.
For example:
puts [1,2,3,4].my_inject(0) { |memo, i| memo+i} #=> 10
puts [1,2,3,4].my_inject { |memo, i| memo+i} #=> 11
And this is what original Ruby inject method outputs:
puts [1,2,3,4].inject { |memo, i| memo+i} #=> 10
The Ruby docs says
If you do not explicitly specify an initial value for memo, then the
first element of collection is used as the initial value of memo.
So how to set proper data type for initial value? Should we have a condition for every data type in Ruby and set a default value for each of data types?

If you debug Ruby's own implementation you will see that it starts the iteration from the second element in case no default memo is given:
> [1,2,3].inject { |m, i| puts "M: #{m} | I: #{i}"; break }
M: 1 | I: 2
Therefore your implementation should look like this:
def my_inject(*args)
init = args.size > 0
memo = init ? args[0] : self[0]
self.drop(init ? 0 : 1).my_each do |item|
memo = yield(memo, item)
end
return memo
end
puts [1,2,3,4].my_inject(0) { |memo, i| memo+i} #=> 10
puts [1,2,3,4].my_inject { |memo, i| memo+i} #=> 10
The reason for init = args.size > 0 is you have to take into account that someone might want to do [...].my_inject(false) { ... }. Therefore you cannot check if memo == false to determine if you need to skip the first element or not, you have to check the actual argument count that was given.

So how to set proper data type for initial value? Should we have a condition for every data type in Ruby and set a default value for each of data types?
No, the default value is the first element of the collection.
Ruby allows you to define your own classes, so writing a case for every possible type is impossible anyway, there are infinitely many of them.

Just out of curiosity—tail-recursive solution:
class Array
def my_inject(memo = nil, enum = self.each)
return enum_for(:my_inject) unless block_given?
memo = enum.next if memo.nil?
my_inject(yield(memo, enum.next), enum, &Proc.new)
rescue StopIteration
memo
end
end
[1,2,3].my_inject(3) { |memo, v| memo += v }
#⇒ 9
[1,2,3].my_inject &:*
#⇒ 6

Related

How can I modify my ruby method so it takes in a block of code as well?

I have a method called myFilter that takes in an array, and filters out the elements that don't meet the requirement.
For example.
arr = [4,5,8,9,1,3,6]
answer = myfilter(arr) {|i| i>=5}
this run would return an array with elements 5,8,9,6 since they are all greater than or equal to 5.
How would I preform this? the algorithm is easy, but I don't understand how we take in that condition.
Thank you.
I take for granted you don't want to use select method or similar but you want to understand how blocks work.
def my_filter(arr)
if block_given?
result = []
arr.each { |element| result.push(element) if yield element } # here you use the block passed to this method and execute it with the current element using yield
result
else
arr
end
end
The idiomatic way would be:
def my_filter(arr)
return enum_for(:my_filter, arr) unless block_given?
arr.each_with_object([]) do |e, acc|
acc << e if yield e
end
end
More info on Enumerator::Lazy#enum_for.
you can do
def my_filter(arr, &block)
arr.select(&block)
end
then call
my_filter([1, 2, 3]) { |e| e > 2 }
=> [3]
but instead you can just call select with a block directly :)

Ruby returns instance when iterates over custom class instances

Please help me understand the difference between iteration over the instances of String and over the instances of custom class.
a = ["pew", "pie"]
a.inject do |memo, instance|
memo + instance
end
It works OK. Returns "pewpie" as expected.
class Boom
def slash
3
end
def ping
5
end
end
a = [Boom.new]
a.inject do |memo, instance|
memo + instance.slash + instance.ping
end
This for some reason returns the instance itself - Boom:0x00000005018a38
Key point is this note in the inject documentation:
If you do not explicitly specify an initial value for memo, then the first element of collection is used as the initial value of memo.
In your case you should do something like
a.inject(0) { |memo, instance| memo + instance.slash + instance.ping }
# 8
Otherwise you use a reference to an object of type Boom as memo.
When there's no initial value for accumulator, the first element is used. And if there's only one element in array - the block will not run:
[1].inject{ raise "Boom!" }
# => 1
[1].inject(0){ raise "Boom!" }
# => RuntimeError: Boom!
So you need either provide initial value, of redefine the + operator for your class.
Here are two solutions:
a = [Boom.new, Boom.new, Boom.new]
# => [#<Boom:0x007ffaf90a9c08>, #<Boom:0x007ffaf90a9be0>, #<Boom:0x007ffaf90a9bb8>]
# 1) memo is a String
a.inject('') {|memo, instance| memo + instance.slash.to_s + instance.ping.to_s}
# => "353535"
# 2) memo is an Array
a.inject([]) {|memo, instance| memo << instance.slash.to_s + instance.ping.to_s}
# => ["35", "35", "35"]
see: http://ruby-doc.org/core-2.3.1/Enumerable.html#method-i-inject

Trying to write my own all? method in Ruby

There is a method called all? in Enumerable.
I'm trying to learn all the methods of Enumberable's library by writing them myself.
This is what I've come up so far for the all? method. I sorta understand it but I got stumped when trying to pass initialized values to my method.
EDIT for the record, I'm aware that enum method that I have is not the right way ie, it's hard-coded array. This is for self-learning purposes. I'm just trying to figure out how to pass the initialized values to my all? method. That's why I wrote enum in the first place, to see that it is working for sure. Please don't take this class as a literal gospel. Thank you.
class LearningMethods
def initialize(values)
#values = values
end
def enum
array = [10, 3, 5]
end
def all?(a)
yield(a)
end
end
c = LearningMethods.new([10, 3, 5])
p c.enum.all? {|x| x >= 3 } #this works
p c.all?(10) { |x| x >= 3 } #this works
p c.all?(#values) { |x| x >= 3 } #this doesn't work. Why not? And how do I pass the initialized values?
I'm not sure why you need enum at all? Enumerable is a module included in array, so if you're not familiar with this I recommend you read about "modules and mix-ins" in Ruby.
all? works simply by passing EACH of the array elements to the block. If there is ANY element (at least 1) for which the block returns false, then all? evaluates to false. Try analyzing this code:
class MyAllImplementation
def initialize(array)
#array = array
end
def my_all?
#array.each do |element| # for each element of the array
return true unless block_given? # this makes sure our program doesn't crash if we don't give my_all? a block.
true_false = yield(element) # pass that element to the block
return false unless true_false # if for ANY element the block evaluates to false, return false
end
return true # Hooray! The loop which went over each element of our array ended, and none evaluted to false, that means all elements must have been true for the block.
end
end
a = MyAllImplementation.new([1,2,3])
p a.my_all? { |x| x > 0 } #=> true
p a.my_all? { |x| x > 1 } # false, because 1 is not bigger than 1, it's equal to 1

Return array of hashes based on hash element match

I am wondering how one would search through an array of hashes and return a value based on a search string. For example, #contacts contains the hash elements: :full_name, :city, and :email. The variable #contacts (I guess it would be an array) contains three entries (perhaps rows). Below is the code I have so far to conduct a search based on :city value. However it's not working. Can anyone give me an idea what's going on?
def search string
#contacts.map {|hash| hash[:city] == string}
end
You should use select instead of map:
def search string
#contacts.select { |hash| hash[:city] == string }
end
In your code you tried to map (or transform) your array using a block, which yields boolean values. map takes a block and invokes the block for each element of self, constructing a new array containing elements returned by the block. As the result, you got an array of booleans.
select works similar. It takes a block and iterates over the array as well, but instead of transforming the source array it returns an array containing elements for which the block returns true. So it's a selection (or filtering) method.
In order to understand the difference between these two methods it's useful to see their example definitions:
class Array
def my_map
[].tap do |result|
self.each do |item|
result << (yield item)
end
end
end
def my_select
[].tap do |result|
self.each do |item|
result << item if yield item
end
end
end
end
Example usage:
irb(main):007:0> [1,2,3].my_map { |x| x + 1 }
[2, 3, 4]
irb(main):008:0> [1,2,3].my_select { |x| x % 2 == 1 }
[1, 3]
irb(main):009:0>
You can try this:
def search string
#contacts.select{|hash| h[:city].eql?(string) }
end
This will return an array of hashes which matches string.

How to implement an enumerator in Ruby?

For example:
a = [1,2,3,4,5]
a.delete_if { |x| x > 3 }
is equivalent to:
a = [1,2,3,4,5]
a.delete_if.each.each.each.each { |x| x > 3 }
I know a.delete_if returns an enumerator. But how does it know it should delete object when the each block returns true? How to implement delete_if by hand(and in Ruby)?
You can take a look at the Rubinius source code: enumerable module
Here an example of the reject method:
def reject
return to_enum(:reject) unless block_given?
ary = []
each do |o|
ary << o unless yield(o)
end
ary
end
In the implementation of delete_if, the code can verify the value returned from yield to decide whether or not to delete the given entry from the array.
You can read Implementing Iterators in the Programming Ruby guide for more details, but it would looks something like:
class Array
def delete_if
reject { |i| yield i }.to_a
end
end
The above uses yield to pass each item in the array to the block associated with the call to delete_if, and implicitly returns the value of the yield to the outer reject call.

Resources