Mapping enumerators - ruby

Using an Enumerator in Ruby is pretty straightforward:
a = [1, 2, 3]
enumerator = a.map
enumerator.each(&:succ) # => [2, 3, 4]
But can I do something similar with nested collections?
a = [[1, 2, 3], [4, 5, 6]]
a.map(&:map) # => [#<Enumerator: [1, 2, 3]:map>, #<Enumerator: [4, 5, 6]:map>]
But now how do I get [[2, 3, 4], [5, 6, 7]]?
This could always be done with a block:
a = [[1, 2, 3], [4, 5, 6]]
a.map { |array| array.map(&:succ) } # => [[2, 3, 4], [5, 6, 7]]
But I was wondering if there was a way that avoided the use of a block, partly because I find it annoying to have to type |array| array and also partly because I'm curious to find a way to do it.
Ideally, it would feel like this psuedocode:
a.map.map(&:succ)
# perhaps also something like this
a.map(&:map).apply(&:succ)

The only way I know of doing this is to do the following:
a = [[1, 2, 3], [4, 5, 6]]
a.map { |b| b.map(&:succ) } # => [[2, 3, 4], [5, 6, 7]]
Mainly because of the combination of Array#map/Enumerable#map and Symbol#to_proc, you cannot pass a second variable to the block that #map yields for, and thus pass another variable to the inner #map:
a.map(1) { |b, c| c } # c => 1, but this doesn't work :(
So you have to use the block syntax; Symbol#to_proc actually returns a proc that takes any number of arguments (you can test this by doing :succ.to_proc.arity, which returns -1). The first argument is used as the receiver, and the next few arguments are used as arguments to the method - this is demonstrated in [1, 2, 3].inject(&:+). However,
:map.to_proc.call([[1, 2, 3], [4, 5, 6]], &:size) #=> [3, 3]
How? :map.to_proc creates this:
:map.to_proc # => proc { |receiver, *args, &block| receiver.send(:map, *args, &block) }
This is then called with the array of arrays as an argument, with this block:
:size.to_proc # => proc { |receiver, *args, &block| receiver.send(:size, *args, &block) }
This results in .map { |receiver| receiver.size } being effectively called.
This all leads to this - since #map doesn't take extra arguments, and passes them to the block as parameters, you have to use a block.

To my knowledge there is no specific implementation as per the way you requested it.
You could just create a recursive function to handle this such as:
def map_succ(a)
a.map {|arr| arr.is_a?(Array) ? map_succ(arr) : arr.succ}
end
Then it will work no matter how deeply nested the Array's are (caveat if the elements do not respond to #succ this will fail).
If you really wanted to you could monkey_patch Array (IN NO WAY RECOMMENDED)
#note if the element does not respond to `#succ` I have nullified it here
class Array
def map_succ
map do |a|
if a.is_a?(Array)
a.map_succ
elsif a.respond_to?(:succ)
a.succ
#uncomment the lines below to return the original object in the event it does not respond to `#succ`
#else
#a
end
end
end
end
Example
a = [[1, 2, 3], [4, 5, 6], [7, 8, 9, [2, 3, 4]], {"test"=>"hash"}, "F"]
a.map_succ
#=> [[2, 3, 4], [5, 6, 7], [8, 9, 10, [3, 4, 5]], nil, "G"]
The nil is because Hash does not have a #succ method.
UPDATE
Based on this SO Post a similar syntax could be supported but note that recursion is still probably your best bet here so that you can support any depth rather than an explicit one.
#taken straight from #UriAgassi's from post above
class Symbol
def with(*args, &block)
->(caller, *rest) { caller.send(self, *rest, *args, &block) }
end
end
Then
a = [[1,2,3],[4,5,6]]
a.map(&:map.with(&:succ))
#=> [[2, 3, 4], [5, 6, 7]]
a << [7,8,[9,10]]
#=> [[2, 3, 4], [5, 6, 7],[7,8,[9,10]]]
a.map(&:map.with(&:succ))
#=> NoMethodError: undefined method `succ' for [9, 10]:Array

Related

Ruby: how to split a collection (Enumerable) in multiple (>2) partitions by some predicate?

Is there a function, to partition a collection (Enumerable) by some non-boolean predicate into more than 2 partitions? (my math knowledge got rusty - but I think, this was called "factorize" - or at least in German "Faktorisierung")
This follows my own yesterday question (Ruby: how to split a collection by predicate into two collections in ruby?)
and I wonder, is there another standard ruby method I just missed.
Here´s an example, showing the principle:
def factorize(eee)
h={}; h.default_proc= proc{|h,k| h[k]=[] }
eee.each{|e| k=yield e; h[k]<<e }
h.values
end
factorize( 1..10 ){|n| n%3 } #=> [[1, 4, 7, 10], [2, 5, 8], [3, 6, 9]]
You can use group_by: https://apidock.com/ruby/v2_5_5/Enumerable/group_by
Example:
(1..10).group_by { |n| n % 3 }.values
=> [[1, 4, 7, 10], [2, 5, 8], [3, 6, 9]]
While the Enumerable#group_by solution is clearly the answer, it may be instructive to streamline your original attempt using #each_with_object.
def factorize(eee)
eee.each_with_object({}) do |e, hsh|
k = yield e
hsh[k] ||= []
hsh[k] << e
end.values
end
factorize(1..10) { |x| x % 3 }
# => [[1, 4, 7, 10], [2, 5, 8], [3, 6, 9]]

Why does changing a duped object alter the original? [duplicate]

temp gets #board.dup, and #board array is modified. However, temp gets modified as well! I have tried reading all the related documentations but still couldn't figure out an explanation.
class Test
def initialize
#board = [[1,2],[3,4], [5,6]]
end
def modify
temp = #board.dup #Also tried .clone
print 'temp: ';p temp
print '#board: ';p #board
#board.each do |x|
x << "x"
end
print "\ntemp: ";p temp
print '#board: ';p #board
end
end
x = Test.new
x.modify
Output:
temp: [[1, 2], [3, 4], [5, 6]]
#board: [[1, 2], [3, 4], [5, 6]]
temp: [[1, 2, "x"], [3, 4, "x"], [5, 6, "x"]] # <= Why did it change?
#board: [[1, 2, "x"], [3, 4, "x"], [5, 6, "x"]]
What can I do to ensure temp doesn't get modified?
You have Array with Arrays, so you dup the first array but, inside object point to the same instance. In this case you just modify the same source.
like here:
arr = [[1, 2, 3]]
arr2 = arr.dup
arr2[0] << 1
p arr
# => [[1, 2, 3, 1]]
p arr2
# => [[1, 2, 3, 1]]
So you must use dup for all array instance like this.
arr = [[1, 2, 3]]
arr3 = arr.map(&:dup)
arr3[0] << 1
p arr
# => [[1, 2, 3]]
p arr3
# => [[1, 2, 3, 1]]
In your case use this map.
class Test
def initialize
#board = [[1,2],[3,4], [5,6]]
end
def modify
temp = #board.map(&:dup) # dup all
print 'temp: ';p temp
print '#board: ';p #board
#board.each do |x|
x << "x"
end
print "\ntemp: ";p temp
print '#board: ';p #board
end
end
x = Test.new
x.modify
# temp: [[1, 2], [3, 4], [5, 6]]
# #board: [[1, 2], [3, 4], [5, 6]]
#
# temp: [[1, 2], [3, 4], [5, 6]]
# #board: [[1, 2, "x"], [3, 4, "x"], [5, 6, "x"]]
The reason is clone and dup produce a shallow copy. Thus the object ids of the inner arrays are still the same: those are the same arrays.
What you need is a deep clone. There is no built-in method in the standard library able to do that.
From the docs:
dup produces a shallow copy of obj—the instance variables of obj are copied, but not the objects they reference.
That said: You will need to make a deep-copy of your array.
When you are using the ActiveSupport gem (Rails) you can use its deep_dup method instead of just calling dup:
temp = #board.deep_dup
Without gems mashaling might be a easy solution to deep dup almost everything, without knowing about the internals of an object:
temp = Marshal.load(Marshal.dump(#board))
You can add a deep_dup method for nested arrays:
class Array
def deep_dup
map {|x| x.deep_dup}
end
end
# To handle the exception when deepest array contains numeric value
class Numeric
def deep_dup
self
end
end
class Test
def initialize
#board = [[1,2], [3,4], [5,6]]
end
def modify
temp = #board.deep_dup
...
end
end
x = Test.new
x.modify
# temp: [[1, 2], [3, 4], [5, 6]]
# #board: [[1, 2], [3, 4], [5, 6]]
# temp: [[1, 2], [3, 4], [5, 6]]
# #board: [[1, 2, "x"], [3, 4, "x"], [5, 6, "x"]]

Actual Hash getting modified when copy is modified in ruby

I am trying to copy a Hash, and then later modifying the copy of hash. But when I compare the copy with the original one, even the original hash value is getting modified.
I have tried using this:
def deep_copy(o)
Marshal.load(Marshal.dump(o))
end
h1 = {:a => 'foo'}
h2 = deep_copy(h1)
h1[:a] << 'bar'
p h2
I have also tried doing this:
def dumpable_hash(h)
return h unless h.default_proc
copy = h.clone
copy.default = nil # clear the default_proc
copy
end
Hash object(which I want to copy and keep its original unmodified):
#original = {0=>{0=>[0, 4, 5, 6], 2=>[3, 7], 1=>[1, 2]}, 1=>{0=>[0, 4, 5, 6], 2=>[1], 1=>[2, 3, 7]}, 2=>{0=>[0, 4, 6], 1=>[1, 2, 5], 2=>[3, 7]}, 3=>{0=>[0, 4], 2=>[1, 2, 3, 6, 7], 1=>[5]}, 4=>{0=>[4], 2=>[1, 5], 1=>[2, 3, 6, 7, 0]}, 5=>{1=>[0, 1, 2, 5], 2=>[3, 6, 7], 0=>[4]}, 6=>{1=>[0, 1, 2, 5, 4], 2=>[3, 6, 7], 0=>[]}}
Tried copying the original into another object, using the given answer also.
Method used for updating its clone,
#outer loop
(1..5).each do |i|
#assigning original to another object in every loop
copy = #original.clone
(-6..0).each do |row|
if copy[row.abs][0].include? k
copy[row.abs][0] -= [k]
copy[row.abs][1] += [k]
puts "row #{row.abs}, col #{k}"
break
end
end
end
When the loop is over both the original and copy are updated.
Please help, I have been trying this from an hour now.
If want to copy one hash to another you can do it just like this. Then you can manipulate the copied hash or even do it in the loop. And then manipulate the copied hash it for your task. In here it copies the key-value pair for the hash,
#original = {0=>{0=>[0, 4, 5, 6], 2=>[3, 7], 1=>[1, 2]}, 1=>{0=>[0, 4, 5, 6], 2=>[1], 1=>[2, 3, 7]}, 2=>{0=>[0, 4, 6], 1=>[1, 2, 5], 2=>[3, 7]}, 3=>{0=>[0, 4], 2=>[1, 2, 3, 6, 7], 1=>[5]}, 4=>{0=>[4], 2=>[1, 5], 1=>[2, 3, 6, 7, 0]}, 5=>{1=>[0, 1, 2, 5], 2=>[3, 6, 7], 0=>[4]}, 6=>{1=>[0, 1, 2, 5, 4], 2=>[3, 6, 7], 0=>[]}}
copy = Hash.new
#original.each do |k, v|
copy[k] = v.dup
end
p copy #prints the copied hash
I think you need to do deep_dup here to completely separate one hash content from another.
h1 = {a: "foo"}
h2 = h1.deep_dup
h2[:a] << "bar"
puts h2 #returns {:a => "foobar"}
puts h1 # returns {:a => "foo"}
Use dup.
h1 = {a:1, b:2}
h2 = h1.dup
h2[:c] = 3
puts h1
{:a=>1, :b=>2}
puts h2
{:a=>1, :b=>2, :c=>3}
If you have a nested hash, you can use ActiveSupport deep_dup.
def deep_dup
each_with_object(dup) do |(key, value), hash|
hash[key.deep_dup] = value.deep_dup
end
end
You are modifying the original hash (Appending to h1 hash). Modify the deep copied one and you can see the original is staying as before,
def deep_copy(o)
Marshal.load(Marshal.dump(o))
end
h1 = {:a => 'foo'}
h2 = deep_copy(h1)
h2[:a] << 'bar'
p h2 #prints the cloned one
p h1 #prints the original one
See this for futher information about marshaling library here.

How do I implement Common Lisp's mapcar in Ruby?

I want to implement Lisp's mapcar in Ruby.
Wishful syntax:
mul = -> (*args) { args.reduce(:*) }
mapcar(mul, [1,2,3], [4,5], [6]) would yield [24, nil, nil].
Here is the solution I could think of:
arrs[0].zip(arrs[1], arrs[2]) => [[1, 4, 6], [2, 5, nil], [3, nil, nil]]
Then I could:
[[1, 4, 6], [2, 5, nil], [3, nil, nil]].map do |e|
e.reduce(&mul) unless e.include?(nil)
end
=> [24, nil, nil]
But I'm stuck on the zip part. If the input is [[1], [1,2], [1,2,3], [1,2,3,4]], the zip part would need to change to:
arrs[0].zip(arrs[1], arrs[2], arrs[3])
For two input arrays I could write something like this:
def mapcar2(fn, *arrs)
return [] if arrs.empty? or arrs.include? []
arrs[0].zip(arrs[1]).map do |e|
e.reduce(&fn) unless e.include? nil
end.compact
end
But I do not know how go beyond more than two arrays:
def mapcar(fn, *arrs)
# Do not know how to abstract this
# zipped = arrs[0].zip(arrs[1], arrs[2]..., arrs[n-1])
# where n is the size of arrs
zipped.map do |e|
e.reduce(&fn) unless e.include?(nil)
end.compact
end
Does anyone have any advice?
If I got your question properly you just need:
arrs = [[1,2], [3,4], [5,6]]
zipped = arrs[0].zip(*arrs[1..-1])
# => [[1, 3, 5], [2, 4, 6]]
Or a nicer alternative, IHMO:
zipped = arrs.first.zip(*arrs.drop(1))
If all arrays inside arrs are of the same length you can use the transpose method:
arrs = [[1,2], [3,4], [5,6]]
arrs.transpose
# => [[1, 3, 5], [2, 4, 6]]
According to toro2k, one of the possible implementations of mapcar in Ruby:
def mapcar(fn, *arrs)
return [] if arrs.empty? or arrs.include? []
transposed = if arrs.all? { |a| arrs.first.size == a.size }
arrs.transpose
else
arrs[0].zip(*arrs.drop(1))
end
transposed.map do |e|
e.collect(&fn) unless e.include? nil
end.compact!
end

Merge N sorted arrays in ruby lazily

How does one merge N sorted arrays (or other list-like data structures) lazily in Ruby? For example, in Python you would use heapq.merge. There must be something like this built into Ruby, right?
Here's a (slightly golfed) solution that should work on arrays of any 'list-like' collections that support #first, #shift, and #empty?. Note that it is destructive - each call to lazymerge removes one item from one collection.
def minheap a,i
r=(l=2*(m=i)+1)+1 #get l,r index
m = l if l< a.size and a[l].first < a[m].first
m = r if r< a.size and a[r].first < a[m].first
(a[i],a[m]=a[m],a[i];minheap(a,m)) if (m!=i)
end
def lazymerge a
(a.size/2).downto(1){|i|minheap(a,i)}
r = a[0].shift
a[0]=a.pop if a[0].empty?
return r
end
p arrs = [ [1,2,3], [2,4,5], [4,5,6],[3,4,5]]
v=true
puts "Extracted #{v=lazymerge (arrs)}. Arr= #{arrs.inspect}" while v
Output:
[[1, 2, 3], [2, 4, 5], [4, 5, 6], [3, 4, 5]]
Extracted 1. Arr= [[2, 3], [2, 4, 5], [4, 5, 6], [3, 4, 5]]
Extracted 2. Arr= [[3], [2, 4, 5], [4, 5, 6], [3, 4, 5]]
Extracted 2. Arr= [[4, 5], [3], [4, 5, 6], [3, 4, 5]]
Extracted 3. Arr= [[4, 5], [3, 4, 5], [4, 5, 6]]
Extracted 3. Arr= [[4, 5], [4, 5], [4, 5, 6]]
Extracted 4. Arr= [[5], [4, 5], [4, 5, 6]]
Extracted 4. Arr= [[5], [5], [4, 5, 6]]
Extracted 4. Arr= [[5, 6], [5], [5]]
Extracted 5. Arr= [[6], [5], [5]]
Extracted 5. Arr= [[5], [6]]
Extracted 5. Arr= [[6]]
Extracted 6. Arr= [[]]
Extracted . Arr= [[]]
Note also that this algorithm is also lazy about maintaining the heap property - it is not maintained between calls. This probably causes it to do more work than needed, since it does a complete heapify on each subsequent call. This could be fixed by doing a complete heapify once up front, then calling minheap(a,0) before the return r line.
I ended up writing it myself using the data structures from the 'algorithm' gem. It wasn't as bad as I expected.
require 'algorithms'
class LazyHeapMerger
def initialize(sorted_arrays)
#heap = Containers::Heap.new { |x, y| (x.first <=> y.first) == -1 }
sorted_arrays.each do |a|
q = Containers::Queue.new(a)
#heap.push([q.pop, q])
end
end
def each
while #heap.length > 0
value, q = #heap.pop
#heap.push([q.pop, q]) if q.size > 0
yield value
end
end
end
m = LazyHeapMerger.new([[1, 2], [3, 5], [4]])
m.each do |o|
puts o
end
Here's an implementation which should work on any Enumerable, even infinite ones. It returns Enumerator.
def lazy_merge *list
list.map!(&:enum_for) # get an enumerator for each collection
Enumerator.new do |yielder|
hash = list.each_with_object({}){ |enum, hash|
begin
hash[enum] = enum.next
rescue StopIteration
# skip empty enumerators
end
}
loop do
raise StopIteration if hash.empty?
enum, value = hash.min_by{|k,v| v}
yielder.yield value
begin
hash[enum] = enum.next
rescue StopIteration
hash.delete(enum) # remove enumerator that we already processed
end
end
end
end
Infinity = 1.0/0 # easy way to get infinite range
p lazy_merge([1, 3, 5, 8], (2..4), (6..Infinity), []).take(12)
#=> [1, 2, 3, 3, 4, 5, 6, 7, 8, 8, 9, 10]
No, there's nothing built in to do that. At least, nothing that springs instantly to mind. However, there was a GSoC project to implement the relevant data types a couple of years ago, which you could use.

Resources