How do I implement Common Lisp's mapcar in Ruby? - 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

Related

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"]]

Mapping enumerators

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

Ruby extract subarray from integer

I need to extract part of an array based on an integer and if there are no enough values, fill this array with specifics values if the array size doesn't feet with this integer.
As example :
I have an array like that:
[[1,2], [2,1], [3,3]]
If my integer is 2 I need this :
[[1,2], [2,1]]
If my integer is 4 I need this :
[[1,2], [2,1], [3,3], [nil, nil]]
You can do the same using Fixnum#times methos:
a = [[1,2], [2,1], [3,3]]
def extract_sub_array array, size
size.times.map { |i| array.fetch(i, [nil, nil]) }
end
extract_sub_array a, 2
# => [[1, 2], [2, 1]]
extract_sub_array a, 4
# => [[1, 2], [2, 1], [3, 3], [nil, nil]]
def convert(a,n)
Array.new(n) { |i| a[i] || [nil,nil] }
end
a = [[1,2], [2,1], [3,3]]
convert(a,2) #=> [[1, 2], [2, 1]]
convert(a,3) #=> [[1, 2], [2, 1], [3, 3]]
convert(a,4) #=> [[1, 2], [2, 1], [3, 3], [nil, nil]]
convert(a,5) #=> [[1, 2], [2, 1], [3, 3], [nil, nil], [nil, nil]]
Assuming, the desired_length is specified and an array is named arr:
arr = [[1,2], [2,1], [3,3]]
# Will shrink an array or fill it with nils
# #param arr [Array] an array
# #param desired_length [Integer] the target length
def yo arr, desired_length
arr[0...desired_length] + [[nil,nil]]*[0,desired_length-arr.length].max
end
yo arr, 2
#⇒ [[1,2], [2,1]]
yo arr, 4
#⇒ [[1,2], [2,1], [3,3], [nil, nil]]

How to search within a two-dimensional array

I'm trying to learn how to search within a two-dimensional array; for example:
array = [[1,1], [1,2], [1,3], [2,1], [2,4], [2,5]]
I want to know how to search within the array for the arrays that are of the form [1, y] and then show what the other y numbers are: [1, 2, 3].
If anyone can help me understand how to search only with numbers (as a lot of the examples I found include strings or hashes) and even where to look for the right resources even, that would be helpful.
Ruby allows you to look into an element by using parentheses in the block argument. select and map only assign a single block argument, but you can look into the element:
array.select{|(x, y)| x == 1}
# => [[1, 1], [1, 2], [1, 3]]
array.select{|(x, y)| x == 1}.map{|(x, y)| y}
# => [1, 2, 3]
You can omit the parentheses that correspond to the entire expression between |...|:
array.select{|x, y| x == 1}
# => [[1, 1], [1, 2], [1, 3]]
array.select{|x, y| x == 1}.map{|x, y| y}
# => [1, 2, 3]
As a coding style, it is a custom to mark unused variables as _:
array.select{|x, _| x == 1}
# => [[1, 1], [1, 2], [1, 3]]
array.select{|x, _| x == 1}.map{|_, y| y}
# => [1, 2, 3]
You can use Array#select and Array#map methods:
array = [[1,1], [1,2], [1,3], [2,1], [2,4], [2,5]]
#=> [[1, 1], [1, 2], [1, 3], [2, 1], [2, 4], [2, 5]]
array.select { |el| el[0] == 1 }
#=> [[1, 1], [1, 2], [1, 3]]
array.select { |el| el[0] == 1 }.map {|el| el[1] }
#=> [1, 2, 3]
For more methods on arrays explore docs.
If you first select and then map you can use the grep function to to it all in one function:
p array.grep ->x{x[0]==1}, &:last #=> [1,2,3]
Another way of doing the same thing is to use Array#map together with Array#compact. This has the benefit of only requiring one block and a trivial operation, which makes it a bit easier to comprehend.
array.map { |a, b| a if b == 1 }
#=> [1, 2, 3, nil, nil, nil]
array.map { |a, b| a if b == 1 }.compact
#=> [1, 2, 3]
You can use each_with_object:
array.each_with_object([]) { |(x, y), a| a << y if x == 1 }
#=> [1, 2, 3]

Replace array elements with map

I have two arrays:
#a = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]
#b = [a, b, c]
I need to replace n-th column in a with b like:
swap_column(0)
#=> [a, 2, 3]
[b, 5, 6]
[c, 8, 9]
(This is for using Cramer's rule for solving equations system, if anybody wonders.)
The code I've come up with:
def swap_column(n)
#a.map.with_index { |row, j| row[n] = #b[j] }
end
How do I get rid of assignment here so that map returns the modified matrix while leaving #a intact?
What you wanted is dup. Also, you had the return value of the map.with_index block wrong.
def swap_column(i)
#a.map.with_index{|row, j| row = row.dup; row[i] = #b[j]; row}
end
or
def swap_column(i)
#a.map.with_index{|row, j| row.dup.tap{|row| row[i] = #b[j]}}
end
The answer by sawa is good and the main point is you need to dup your inner arrays for this to work properly. The only reason for this additional post is to point out that often when you are using with_index so that you can directly 1:1 index into another array you can simplify the code by using zip.
def swap_column(n)
#a.zip(#b).map {|r,e| r.dup.tap{|r| r[n] = e}}
end
What zip does is combine your two arrays into a new array where each element is an array made of the two corresponding elements of the initial arrays. In this case it would be an array of an array and an element you want to later use for replacement. We then map over those results and automatically destructure each element into the two pieces. We then dup the array piece and tap it to replace the nth element.
You can use transpose to do the following:
class M
attr :a, :b
def initialize
#a = [[1,2,3],
[4,5,6],
[7,8,9]
]
#b = [:a, :b, :c]
end
def swap_column(n)
t = #a.transpose
t[0] = #b
t.transpose
end
end
m = M.new
=> #<M:0x007ffdc2952e38 #a=[[1, 2, 3], [4, 5, 6], [7, 8, 9]], #b=[:a, :b, :c]>
m.swap_column(0)
=> [[:a, 2, 3], [:b, 5, 6], [:c, 8, 9]]
m # m is unchanged
=> #<M:0x007ffdc2952e38 #a=[[1, 2, 3], [4, 5, 6], [7, 8, 9]], #b=[:a, :b, :c]>

Resources