0

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?

Juanito Fatas
  • 9,419
  • 9
  • 46
  • 70

2 Answers2

1

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]] 
toro2k
  • 19,020
  • 7
  • 64
  • 71
  • Ah. I tried this: arrs[0].zip(arrs[1..-1]) but did not work, thanks! I am wondering what exactly is this star doing here? – Juanito Fatas Aug 08 '13 at 15:01
  • @juanitofatas The star is the splat operator. You can find plenty of questions about it here on SO, for example: http://stackoverflow.com/questions/4170037/what-does-the-star-mean-in-ruby/4170053. – toro2k Aug 08 '13 at 15:08
  • I found I've asked the same question before: http://stackoverflow.com/questions/17341053/splat-operator-in-ruby-a-quicksort-example Thanks again. I will study again! – Juanito Fatas Aug 08 '13 at 15:09
  • umm. Do you have a good way to find the elements of array have the same length? This looks awful: arrs.map { |e| e.size }.uniq.size == 1 – Juanito Fatas Aug 08 '13 at 15:18
  • @juanitofatas Ask a question! :-) – toro2k Aug 08 '13 at 15:21
  • Asked in http://stackoverflow.com/questions/18129959/check-arrays-elements-are-of-the-same-size-in-ruby :D – Juanito Fatas Aug 08 '13 at 15:25
0

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
Juanito Fatas
  • 9,419
  • 9
  • 46
  • 70