Ruby中Haskell的scanl的等价物是什么?

z9zf31ra  于 11个月前  发布在  Ruby
关注(0)|答案(4)|浏览(99)

This question提供了Haskell的scanl在Python中的一个版本,但是这个函数有Ruby版本吗?

lc8prwob

lc8prwob1#

你可以使用reduce()并自己实现它。

def scanl(op, init, range)
  op = op.to_proc unless op.is_a?(Proc)
  range.reduce([init]) { |a, e| a.push(op.call(a.last,e)) }
end

p scanl(lambda { |a, b| a + b }, 0, 1..10)
#=> [0, 1, 3, 6, 10, 15, 21, 28, 36, 45, 55]
p scanl(:+, 0, 1..10)
#=> [0, 1, 3, 6, 10, 15, 21, 28, 36, 45, 55]

字符串
或者,您可以使用map()并将初始元素放在数组前面。

def scanl(op, init, range)                             
  op = op.to_proc unless op.is_a?(Proc)                
  acc = init                                           
  range.map { |e| acc = op.call(acc, e) }.unshift(init)
end

drnojrws

drnojrws2#

在Ruby 2.0中,Enumerator类存在,我们可以构建一个更好的实现,它可以在无限范围内正常工作:

def scanl(elem, &op)
  Enumerator.new do |yielder|
    acc = elem
    loop do
      yielder << acc
      acc = op.call(acc)
    end
  end.lazy
end

字符串
这样使用它:

scanl(1, &:next).take(10)                                                                                                                                                                                                                                     
#=> #<Enumerator::Lazy: ...>
scanl(1, &:next).take(10).force                                                                                                                                                                                                                                     
#=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
fib = scanl([0,1]) {|x, y| [y, x + y]}.map(&:first)
fib.take(10).force
#=> [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

8tntrjer

8tntrjer3#

使用普通阵列操作:

class Array
  def scanl(init, &f)
    return [init] if empty?
    reduce([init]){ |xs, x| xs << f.(xs.last, x) }
  end
  
  def scanl1(&f)
    return if empty?  # might consider returning []
    x, *xs = self
    xs.scanl(x, &f)
  end
end

字符串
一般化为Enumerable,返回Enumerator s:

module Enumerable
  def self._scanl(enum, a, &f)
    Enumerator.new do |y|
      y << a
      loop { b = enum.next; y << (a = f.(a, b)) }
    end
  end

  def scanl(a, &f)
    enum = each_entry
    Enumerable._scanl(enum, a, &f)
  end

  def scanl1(&f)
    enum = each_entry
    init = enum.next rescue return # might consider returning [].each_entry
    Enumerable._scanl(enum, init, &f)
  end
end


示例如下:

[5].scanl(4) {|*a| a.max }&.to_a            # => [4, 5]
[1,2].scanl(4) {|*a| a.max }&.to_a          # => [4, 4, 4]
[1,2,3,5,3,7].scanl(4) {|*a| a.max }&.to_a  # => [4, 4, 4, 4, 5, 5, 7]

[].scanl1(&:+)&.to_a         # => nil
[1].scanl1(&:+)&.to_a        # => [1]
[1,2].scanl1(&:+)&.to_a      # => [1, 3]
[1,2,3,4].scanl1(&:+)&.to_a  # => [1, 3, 6, 10]

wooyq4lh

wooyq4lh4#

也许可以写一个更好的版本,但这是我想到的:

require 'spec_helper'

class Array
  def scanl accumulator, &block
    results = [accumulator] + self
    results.each_with_index do |e, i|
      results[i] = block.call(accumulator, e)
      accumulator = results[i]
    end
    results
  end
end

describe "#scanl" do
  it 'is similar to foldl, but returns a list of successive reduced values from the left' do
    # example from http://learnyouahaskell.com/higher-order-functions
    expect([3, 5, 2, 1].scanl(0, &:+)).to eq([0,3,8,10,11])
  end
end

字符串
我考虑过把scanl改成只取一个方法名,比如:+,而不是像reduce那样的块。

相关问题