Reimplementing Enumerable Map method in Ruby - ruby-on-rails

I'm practicing for an internship interview at a ruby shop. One of the job questions I'm expecting is to reimplement an enumerable method.
I'm trying to implement map right now and I'm having trouble figuring out how to implement the case where a block is not given.
class Array
def mapp()
out = []
if block_given?
self.each { |e| out << yield(e) }
else
<-- what goes here? -->
end
out
end
end
Using my current implementation. If I run:
[1,2,3,4,5,6].mapp{|each| each+1} #returns => [2,3,4,5,6,7]
However, I'm not sure how to get cases where a block isn't passed in:
[1,2,3,4].mapp("cat") # should return => ["cat", "cat", "cat", "cat"]
If someone could point me in the right direction. I'd really appreciate it. I tried looking through the source code but it seems to do things very differently than what i'm used to.
static VALUE
enum_flat_map(VALUE obj)
{
VALUE ary;
RETURN_SIZED_ENUMERATOR(obj, 0, 0, enum_size);
ary = rb_ary_new();
rb_block_call(obj, id_each, 0, 0, flat_map_i, ary);
return ary;
}

I suppose that by [1,2,3,4].mapp("cat") you mean [1,2,3,4].mapp{"cat"}.
That said, map without a block returns an enumerator:
[1,2,3,4].map
=> #<Enumerator: [1, 2, 3, 4]:map>
That is the same output of to_enum
[1,2,3,4].to_enum
=> #<Enumerator: [1, 2, 3, 4]:each>
So in your code, you just want to call to_enum:
class Array
def mapp()
out = []
if block_given?
self.each { |e| out << yield(e) }
else
out = to_enum :mapp
end
out
end
end

return to_enum :mapp unless block_given?
Should be sufficient.
See the Implementation of map of the Rubinius implementation which is entirely in Ruby:
https://github.com/rubinius/rubinius/blob/master/kernel/bootstrap/array19.rb
# -*- encoding: us-ascii -*-
class Array
# Creates a new Array from the return values of passing
# each element in self to the supplied block.
def map
return to_enum :map unless block_given?
out = Array.new size
i = #start
total = i + #total
tuple = #tuple
out_tuple = out.tuple
j = 0
while i < total
out_tuple[j] = yield tuple.at(i)
i += 1
j += 1
end
out
end
end

With rubinius there is a ruby implementation written in ruby wherever possible. You can look at their code for enumerable.#collect
Interesting is the difference between
their ruby 1.9 implementation and
their ruby 1.8 implementation.

class Array
def map!
return to_enum :map! unless block_given?
self.each_with_index { |e, index| self[index] = yield(e) }
end
end

Check out the documentation for Object#to_enum

Related

OpenStruct issue with Ruby 2.3.1

In Ruby 2.1.5 and 2.2.4, creating a new Collector returns the correct result.
require 'ostruct'
module ResourceResponses
class Collector < OpenStruct
def initialize
super
#table = Hash.new {|h,k| h[k] = Response.new }
end
end
class Response
attr_reader :publish_formats, :publish_block, :blocks, :block_order
def initialize
#publish_formats = []
#blocks = {}
#block_order = []
end
end
end
> Collector.new
=> #<ResourceResponses::Collector>
Collector.new.responses
=> #<ResourceResponses::Response:0x007fb3f409ae98 #block_order=[], #blocks= {}, #publish_formats=[]>
When I upgrade to Ruby 2.3.1, it starts returning back nil instead.
> Collector.new
=> #<ResourceResponses::Collector>
> Collector.new.responses
=> nil
I've done a lot of reading around how OpenStruct is now 10x faster in 2.3 but I'm not seeing what change was made that would break the relationship between Collector and Response. Any help is very appreciated. Rails is at version 4.2.7.1.
Let's have a look at the implementation of method_missing in the current implementation:
def method_missing(mid, *args) # :nodoc:
len = args.length
if mname = mid[/.*(?==\z)/m]
if len != 1
raise ArgumentError, "wrong number of arguments (#{len} for 1)", caller(1)
end
modifiable?[new_ostruct_member!(mname)] = args[0]
elsif len == 0
if #table.key?(mid)
new_ostruct_member!(mid) unless frozen?
#table[mid]
end
else
err = NoMethodError.new "undefined method `#{mid}' for #{self}", mid, args
err.set_backtrace caller(1)
raise err
end
end
The interesting part is the block in the middle that runs when the method name didn't end with an = and when there are no addition arguments:
if #table.key?(mid)
new_ostruct_member!(mid) unless frozen?
#table[mid]
end
As you can see the implementation first checks if the key exists, before actually reading the value.
This breaks your implementation with the hash that returns a new Response.new when a key/value is not set. Because just calling key? doesn't trigger the setting of the default value:
hash = Hash.new { |h,k| h[k] = :bar }
hash.has_key?(:foo)
#=> false
hash
#=> {}
hash[:foo]
#=> :bar
hash
#=> { :foo => :bar }
Ruby 2.2 didn't have this optimization. It just returned #table[mid] without checking #table.key? first.

How to trasform all values in a nested hash?

I want to convert all the values in a nested hash to a utf8 compatible string. I initially thought this would be easy and something like deep_apply should be available for me to use, but I am unable to find anything this simple on a quick google and SO search.
I do not want to write (maintain) a method similar to the lines of Change values in a nested hash . Is there a native API implementation or a shorthand available for this or do I have to write my own method?
I ended up implementing my own approach, that is in no way perfect but works well for my use case and should be easy to maintain. Posting it here for reference to anyone who wants to try it out
def deep_apply object, klasses, &blk
if object.is_a? Array
object.map { |obj_ele| deep_apply(obj_ele, klasses, &blk) }
elsif object.is_a? Hash
object.update(object) {|_, value| deep_apply(value, klasses, &blk) }
elsif klasses.any? { |klass| object.is_a? klass }
blk.call(object)
else
object
end
end
usage:
=> pry(main)> deep_apply({a: [1, 2, "sadsad"]}, [String, Integer]) { |v| v.to_s + "asd" }
=> {:a=>["1asd", "2asd", "sadsadasd"]}
Interesting to learn of the deep_merge approach taken in the answer by "The F". Here is another approach which requires adding a few helper methods.
First, the helper methods:
From the top answer here (converting-a-nested-hash-into-a-flat-hash):
def flat_hash(h,f=[],g={})
return g.update({ f=>h }) unless h.is_a? Hash
h.each { |k,r| flat_hash(r,f+[k],g) }
g
end
From a Github repo called ruby-bury (this functionality was proposed to Ruby core, but rejected)
class Hash
def bury *args
if args.count < 2
raise ArgumentError.new("2 or more arguments required")
elsif args.count == 2
self[args[0]] = args[1]
else
arg = args.shift
self[arg] = {} unless self[arg]
self[arg].bury(*args) unless args.empty?
end
self
end
end
And then a method tying it together:
def change_all_values(hash, &blk)
# the next line makes the method "pure functional"
# but can be removed otherwise.
hash = Marshal.load(Marshal.dump(hash))
flat_hash(hash).each { |k,v| hash.bury(*(k + [blk.call(v)])) }
hash
end
A usage example:
irb(main):063:0> a = {a: 1, b: { c: 1 } }
=> {:a=>1, :b=>{:c=>1}}
irb(main):064:0> b = change_all_values(a) { |val| val + 1 }
=> {:a=>2, :b=>{:c=>2}}
irb(main):066:0> a
=> {:a=>1, :b=>{:c=>1}}
There is deep_merge
yourhash.deep_merge(yourhash) {|_,_,v| v.to_s}
Merge the hash with itself, inspect the value and call to_s on it.
This method requires require 'active_support/core_ext/hash' at the top of file if you are not using ruby on rails.
Obviously, you may handle the conversion of v inside the deep_merge as you like to meet your requirements.
In rails console:
2.3.0 :001 > h1 = { a: true, b: { c: [1, 2, 3] } }
=> {:a=>true, :b=>{:c=>[1, 2, 3]}}
2.3.0 :002 > h1.deep_merge(h1) { |_,_,v| v.to_s}
=> {:a=>"true", :b=>{:c=>"[1, 2, 3]"}}
Well, it's quite simple to write it - so why don't write your own and be absolutely sure how does it behave in all situations ;)
def to_utf8(h)
if h.is_a? String
return h.force_encoding('utf-8')
elsif h.is_a? Symbol
return h.to_s.force_encoding('utf-8').to_sym
elsif h.is_a? Numeric
return h
elsif h.is_a? Array
return h.map { |e| to_utf8(e) }.to_s
else
return h.to_s.force_encoding('utf-8')
end
return hash.to_a.map { |e| result.push(to_utf8(e[0], e[1])) }.to_h
end
You may want to check if all behavior and conversions are correct - and change it if necessary.

Getting the number of same elements in both arrays in Ruby

I need to count the number of values that both arrays have.
def process_2arrays(arr1, arr2)
length1 = arr1.count
length2 = arr2.count
arr3 = []
i = 0
while length1 >= i do
ci = arr1[i]
if arr2.include?(ci)
arr3 << ci
damn = arr3.count
i = i + 1
end
return [(damn), (2), (3), (4)]
end
end
When I pass the values to the function it returns [nil, 2, 3, 4]
Whats the problem here?
To find elements that exist in both arrays, use the set intersection method &.
http://ruby-doc.org/core-2.2.0/Array.html#method-i-26
def count_same_elements(a1,a2)
(array1 & array2).length
end
Example
count_same_elements([1,2,3,4],[2,3,4,5])
=> 3
damn is initialized within a do .. end block, specifically the while block. Therefore, its value will live within that scope, and when you call the variable outside the block its value is nil.
If you want to preserve the value, you must initialize the variable to nil outside the block.
i = 0
damn = nil
...
As a side note, your code is lacking the most basic Ruby standards. In Ruby you generally use an iterator, not the while. Moreover, you don't use the return at the end of a method.
This is how you would write your method in Ruby using the iterators and taking advantage of some methods from the core library.
def process_2arrays(arr1, arr2)
arr3 = arr1.select { |e| arr2.include?(e) }
[arr3.size, 2, 3, 4]
end
Changing completely approach, you can use
def process_2arrays(arr1, arr2)
(arr1 & arr2).size
end

How to refactor each function with map in Ruby?

I have a loop building a hash for use in a select field. The intention is to end up with a hash:
{ object.id => "object name", object.id => "object name" }
Using:
#hash = {}
loop_over.each do |ac|
#hash[ac.name] = ac.id
end
I think that the map method is meant for this type of situation but just need some help understanding it and how it works. Is map the right method to refactor this each loop?
Data transformations like this are better suited to each_with_object:
#hash = loop_over.each_with_object({}) { |ac, h| h[ac.name] = ac.id }
If your brain is telling you to use map but you don't want an array as the result, then you usually want to use each_with_object. If you want to feed the block's return value back into itself, then you want inject but in cases like this, inject requires a funny looking and artificial ;h in the block:
#hash = loop_over.inject({}) { |h, ac| h[ac.name] = ac.id; h }
# -------------------- yuck -----------------------------^^^
The presence of the artificial return value is the signal that you want to use each_with_object instead.
Try:
Hash[loop_over.map { |ac| [ac[:name], ac[:id]] }]
Or if you are running on Ruby 2:
loop_over.map { |ac| [ac[:name], ac[:id]] }.to_h
#hash = Hash[loop_over.map { |ac| {ac.name => ac.id} }.map(&:flatten)]
Edit, a simpler solution as per suggestion in a comment.
#hash = Hash[ loop_over.map { |ac| [ac.name, ac.id] } ]
You can simply do this by injecting a blank new Hash and performing your operation:
loop_over.inject({}){ |h, ac| h[ac.name] = ac.id; h }
Ruby FTW
No a map isn't the correct tool for this.
The general use-case of a map is to take in an array, perform an operation on each element, and spit out a (possibly) new array (not a hashmap) of the same length, with the individual element modifications.
Here's an example of a map
x = [1, 2, 3, 4].map do |i|
i+1 #transform each element by adding 1
end
p x # will print out [2, 3, 4, 5]
Your code:
#hash = {}
loop_over.each do |ac|
#hash[ac.name] = ac.id
end
There is nothing wrong with this example. You are iterating over a list, and populating a hashmap exactly as you wished.
Ruby 2.1.0 introduces brand new method to generate hashes:
h = { a: 1, b: 2, c: 3 }
h.map { |k, v| [k, v+1] }.to_h # => {:a=>2, :b=>3, :c=>4}
I would go for the inject version, but use update in the block to avoid the easy to miss (and therefore error prone) ;h suffix:
#hash = loop_over.inject({}) { |h, ac| h.update(ac.name: ac.id) }

Caching, when nil or false are acceptable results [duplicate]

This question already has answers here:
How can I memoize a method that may return true, false, or nil in Ruby?
(2 answers)
Closed 8 years ago.
In some ruby classes, it is useful to cache the results of an expensive operation using the ||= operator, as in the following snippet:
class CacheableCalculations
def foobar
#foobar ||= lambda { some_expensive_calculation }.call
end
end
The issue arrises when the returned value is either nil or false, as this test shows:
class Example
attr_accessor :counter
def initialize(value)
#counter = 0
#value = value
end
def fancy_calculation
#foo ||= lambda { #counter += 1; #value }.call
end
end
first = Example.new(true)
5.times { first.fancy_calculation }
puts first.counter # <== 1, expected
second = Example.new(false)
5.times { second.fancy_calculation }
puts second.counter # <== 5, not caching
third = Example.new(nil)
5.times { third.fancy_calculation }
puts third.counter # <== 5, not caching
Is there any pros or cons with using the defined? operator instead, as in the following block of code?
class Example
attr_accessor :counter
def initialize(value)
#counter = 0
#value = value
end
def fancy_calculation
(defined? #foo) ? #foo : (#foo = lambda { #counter += 1; #value }.call)
end
end
This is still one 1 line, but is quite repetitive.
Is there a better way of easily returning cached results, regardless of what the value is?
The problem with the way it is written is that the ternary operator ?: has higher precedence than assignment = so it is parsed as
def fancy_calculation
((defined? #foo) ? #foo : #foo) = lambda { #counter += 1; #value }.call # NO
end
which you don't want, because #foo is always assigned to.
Instead, do this
def fancy_calculation
defined?(#foo) ? #foo : (#foo = lambda { #counter += 1; #value }.call)
end
This is probably about as succinct as can be without using a separate package/function specifically for memoization.
What you are trying to achieve is called memoization. There used to be a method doing what you need in Rails but at some point they extracted to a separate memoist gem. Check it out: https://github.com/matthewrudy/memoist
There is an alternative one as well: https://github.com/dkubb/memoizable

Resources