Ruby Enumerable #find返回映射值

时间:2017-10-24 04:34:59

标签: ruby

Ruby的Enumerable是否提供了更好的方法来执行以下操作?

output = things
  .find { |thing| thing.expensive_transform.meets_condition? }
  .expensive_transform

Enumerable#find非常适合在枚举中查找元素,但返回原始元素,而不是块的返回值,因此完成的任何工作都将丢失。

当然有很难实现这个目标......

副作用

def constly_find(things)
  output = nil

  things.each do |thing|
    expensive_thing = thing.expensive_transform
    if expensive_thing.meets_condition?
      output = expensive_thing
      break
    end
  end

  output
end

从一个区块返回

这是我正在尝试重构

的替代方法
def costly_find(things)
  things.each do |thing|
    expensive_thing = thing.expensive_transform

    return expensive_thing if expensive_thing.meets_condition?
  end

  nil
end

each.lazy.map.find

def costly_find(things)
  things
    .each
    .lazy
    .map(&:expensive_transform)
    .find(&:meets_condition?)
end

还有更好的东西吗?

3 个答案:

答案 0 :(得分:2)

  

当然有很难实现这个目标......

如果您的手术很便宜,那么您只需使用:

collection.map(&:operation).find(&:condition?)

根据需要使红宝石只调用operation"" (如文档所述),您可以简单地添加lazy

collection.lazy.map(&:operation).find(&:condition?)

我认为这根本就不丑 - 恰恰相反 - 它看起来很优雅。

应用于您的代码:

def costly_find(things)
  things.lazy.map(&:expensive_transform).find(&:meets_condition?)
end

答案 1 :(得分:1)

我倾向于创建一个生成值thing.expensive_transform的枚举器,然后在find的块中使meets_condition?成为find的接收器。首先,我喜欢读取的方式。

<强>代码

def costly_find(things)
  Enumerator.new { |y| things.each { |thing| y << thing.expensive_transform } }.
             find(&:meets_condition?)
end

示例

class Thing
  attr_reader :value
  def initialize(value)
    @value = value
  end
  def expensive_transform
    self.class.new(value*2)
  end
  def meets_condition?
    value == 12
  end
end

things = [1,3,6,4].map { |n| Thing.new(n) }
  #=> [#<Thing:0x00000001e90b78 @value=1>, #<Thing:0x00000001e90b28 @value=3>,
  #    #<Thing:0x00000001e90ad8 @value=6>, #<Thing:0x00000001e90ab0 @value=4>]
costly_find(things)
  #=> #<Thing:0x00000001e8a3b8 @value=12>

在示例中,我假设expensive_thingsthings是同一个类的实例,但如果不是这种情况,则需要以明显的方式修改代码。

答案 2 :(得分:0)

我不认为有一个明显的最佳通用解决方案&#34;为您的问题,这也很容易使用。您有两个涉及的过程(expensive_transformmeets_condition?),如果没有转换元素满足条件,您还需要 - 如果这是一个要使用的库方法 - 作为第三个参数返回的值。在这种情况下,您返回nil,但在一般解决方案中,expensive_transform也可能产生nil,并且只有调用者知道哪个唯一值表示条件未满足。

因此,Enumerable中的可能解决方案将具有签名

class Enumerable

  def find_transformed(default_return_value, transform_proc, condition_proc)
     ...
  end

end

或类似的东西,所以这也不是特别优雅。

如果您同意将两个过程的语义合并为一个,则可以使用单个块执行此操作:您只有一个过程,它会计算转换后的值并对其进行测试。如果测试成功,则返回转换后的值,如果失败,则返回默认值:

class Enumerable

  def find_by(default_value, &block)
    result = default_value
    each do |element|
      result = block.call(element)
      break if result != default_value
    end
  end

  result

end

你会在你的情况下使用它:

my_collection.find_by(nil) do |el|
  transformed_value = expensive_transform(el)
  meets_condition?(transformed_value) ? transformed_value : nil
end

我不确定这是否真的直观易用......