文档结构  
翻译进度:已翻译     翻译赏金:0 元 (?)    ¥ 我要打赏

在上一次的 Ruby Tuesday 中我提到 compose 和 map 的使用,最后我说我们将看看如何使用组合过滤器。

作为复习,这里是我们之前的 Sequence 模块:

module Sequence

  def self.my_map(f, items)
    do_map = lambda do |accumulator, item|
      accumulator.dup << f.call(item)
    end

    my_reduce([], do_map, items)
  end

  def self.my_filter(predicate, items)
    do_filter = lambda do |accumulator, item|
      if (predicate.call(item))
        accumulator.dup << item
      else
        accumulator
      end
    end

    my_reduce([], do_filter, items)
  end

  def self.my_reduce(initial, operation, items)
    return nil unless items.any?{|item| true}

    accumulator = initial
    for item in items do
      accumulator = operation.call(accumulator, item)
    end
    accumulator
  end

  def self.my_compose(functions, initial)
    apply_fn = ->(accum, fn) { fn.(accum) }
    my_reduce(initial, apply_fn, functions)
  end

  @@map = method(:my_map).curry
  @@filter = method(:my_filter).curry
  @@reduce = method(:my_reduce).curry
  @@compose = method(:my_compose).curry

  def self.map
    @@map
  end

  def self.filter
    @@filter
  end

  def self.reduce
    @@reduce
  end

  def self.compose
    @@compose
  end
end
第 1 段(可获 2 积分)

所以我们开始来看看如果通过将过滤器 lambda 组合在一起实现查找所有同时是 3 和 5 的倍数的数值。

multiple_of_three = ->(x) { x % 3 == 0}
# => #<Proc:0x007faeb28926b0@(pry):1 (lambda)>
multiple_of_five = ->(x) { x % 5 == 0}
# => #<Proc:0x007faeb3866c58@(pry):2 (lambda)>
Sequence.compose.([Sequence.filter.(multiple_of_three),
                   Sequence.filter.(multiple_of_five)]).((1..100))
=> [15, 30, 45, 60, 75, 90]

上述代码中首先查找 100 以内 3 的倍数,然后再在这个结果中查找 5 的倍数。

第 2 段(可获 2 积分)

我们该如何将这个过程组合在一起?

此外我们必须进行测试以确保得到一个正确的结果,我们将这些谓词组合在一起,并使用 15 这个数值来测试组合函数。

three_and_five = Sequence.compose.([multiple_of_three, multiple_of_five])
=> #<Proc:0x007faeb4905d20 (lambda)>
three_and_five.(15)
# NoMethodError: undefined method `%' for true:TrueClass
# from (pry):2:in `block in __pry__'

上述代码调用失败,错误是 NoMethodError ,这表明我们不能在对象 TrueClass 中使用 % 方法。

第 3 段(可获 2 积分)

难道我们不想得到一个 true 而不是一个错误吗?

如果我们回滚组合到之前的嵌套函数调用版本,错误的来源将变得很清晰。

multiple_of_five.(multiple_of_three.(15))
# NoMethodError: undefined method `%' for true:TrueClass
# from (pry):2:in `block in __pry__'

当我们在调用 multiple_of_three 方法并传递参数 15 时到底发生什么使之返回 true 的,而这个 true 接着被传递到 multiple_of_five 方法,当我们传递 15 时同时我们也确保了它是 5 的倍数。

我们真正想要做的是使用 15 来评估每个 lambda 表达式,并在谓词函数中检查是否成功。

第 4 段(可获 2 积分)

因此我们开始使用一个非常幼稚而且 “un-curried” 的版本来证明我们的理论。

我们首先对每个谓词通过 map 进行遍历并传递一组我们想要测试的数据,然后返回判断的结果成功或者失败。然后通过 and 操作来减少这些条目的数量来获取一个整体的成功结果。

def my_all_succeed_naive(predicates, value)
  check_results = Sequence.map.(->(f) {f.(value)}, predicates)
  Sequence.reduce.(true, ->(accum, item) {accum && item}, check_results)
end

my_all_succeed_naive([multiple_of_three, multiple_of_five], 15)
# => true
my_all_succeed_naive([multiple_of_three, multiple_of_five], 14)
# => false
my_all_succeed_naive([multiple_of_three, multiple_of_five], 5)
# => false
第 5 段(可获 2 积分)

看似可以了,我们得到的 15 同时是 3 和 5 的倍数,即使检查是 3 的倍数失败,但我们仍需检查如果该数字是否是 5 的倍数。

我们能否有更好的方法,或者是更简单的检测来得到那个 false 吗?

现在来试试。

def my_all_succeed(predicates, value)
  for predicate in predicates do
    return false unless predicate.(value)
  end
  true
end

为了确保咱们在正确的轨道上,让我们来看看二者有何区别。

首先我们创建一个 “长时间运行的谓词检查”

第 6 段(可获 2 积分)
[22] pry(main)> pass_after = ->(x, value) { sleep(x); true }.curry
=> #<Proc:0x007faeb310d498 (lambda)>

接下来我们使用 Benchmark#measure (别忘了在最开始时 require 'benchmark' ) 来看看执行所需时间。首次测试成功,但是再次执行就正如之前所预料的,失败了。

Benchmark.measure do
  my_all_succeed([multiple_of_three, pass_after.(3), multiple_of_five], 15)
end
# => #<Benchmark::Tms:0x007faeb28f24c0
#  @cstime=0.0,
#  @cutime=0.0,
#  @label="",
#  @real=3.0046792929642834,
#  @stime=0.0,
#  @total=0.0,
#  @utime=0.0>


Benchmark.measure do
  my_all_succeed([multiple_of_three, pass_after.(3), multiple_of_five], 14)
end
# => #<Benchmark::Tms:0x007faeb6018c88
#  @cstime=0.0,
#  @cutime=0.0,
#  @label="",
#  @real=2.5073997676372528e-05,
#  @stime=0.0,
#  @total=0.0,
#  @utime=0.0>
第 7 段(可获 2 积分)

如果成功的话,我们能看到的时候运行时间是 3 秒,但是如果首次检查失败则只需要零点几秒。这仅仅是证明了我们早期的判断是对的。我们将对相同谓词列表使用 14 这个数值再次进行性能测试,因为它将在首次检查是否为 3 的倍数时失败。

Benchmark.measure do
  my_all_succeed_naive([multiple_of_three, pass_after.(3), multiple_of_five], 14)
end
# => #<Benchmark::Tms:0x007faeb487d218
#  @cstime=0.0,
#  @cutime=0.0,
#  @label="",
#  @real=3.0028793679666705,
#  @stime=0.0,
#  @total=0.0,
#  @utime=0.0>
第 8 段(可获 2 积分)

它确实需要超过 3 秒才能执行完成。

因此我们添加这个倒 Sequence 模块,并确保它起作用。

那么,如果我们只是针对一个字符串做这个操作,而不是在一个列表中的某一类呢?

def self.my_all_succeed(predicates, value)
  for predicate in predicates do
    return false unless predicate.(value)
  end
  true
end

@@all_succeed = method(:my_all_succeed).curry

def self.all_succeed
  @@all_succeed
end

然后检查我们可以部分使用 Sequence 模块。

Sequence.all_succeed.([multiple_of_three, multiple_of_five]).(14)
# => false
Sequence.all_succeed.([multiple_of_three, multiple_of_five]).(15)
# => true
第 9 段(可获 2 积分)

因此现在我们回到该如何通过全新的 Sequence::all_succeed filter 中使用这个的问题。

three_and_five_multiple = Sequence.all_succeed.([multiple_of_three, multiple_of_five])
# => #<Proc:0x007faeb28685e0 (lambda)>
Sequence.filter.(three_and_five_multiple).((1..100))
# => [15, 30, 45, 60, 75, 90]

好了,我们现在已经将谓词组合到一个函数里,并可传递给 Sequence::filter 做到只需一次的列表遍历。

正如我们所看到的,这篇文章介绍了如何使用 and 的风格来组合谓词。下周我们将看看如何使用 or 风格来组合谓词。

    第 10 段(可获 2 积分)

    文章评论