map-detect inplace in lazy map然后检测

时间:2017-11-20 05:50:41

标签: ruby

我有一个对象数组。我需要找到满足某些条件的第一个并返回它的转换版本。通常,此操作可以命名为“map-detect”。

input = [2, 3, 4]
mapper = ->(v) { result = v * v; result if result.odd? }
detector = ->(v) { v } # &itself
expected_output = 9

解决方案是懒惰映射所有内容然后detect

input.lazy.map(&mapper).detect(&detector)
#⇒ 9

这看起来有点多余;我需要的是通过一个Enumerable来映射它,检测第一个真相并返回它。其他方式是:

input.each do |v|
  mapped = mapper.(v)
  break mapped if detector.(mapped)
end

这看起来像是滥用each。所以,我的问题是:在ruby中有map-detect的原生方式吗?

4 个答案:

答案 0 :(得分:2)

如评论中所述,纯粹是为了检测并返回第一个非nil映射值,这有效:

die "Module refinements need 2.4" if RUBY_VERSION < "2.4.0"

module MapDetectFirstRefinement
  refine Enumerable do
    def map_detect_first
      each_with_object(nil) do |e, _|
        mapped = yield e
        return mapped if mapped
      end
    end
  end
end

module TestMapDetectFirst
  using MapDetectFirstRefinement
  puts [2,3,4].map_detect_first { |v| result = v * v; result if result.odd? }
end

答案 1 :(得分:1)

如果mapper是一项昂贵的操作,我可以理解你为什么要这样做。我认为问题源于map(带有一个块)返回一个数组而不是一个枚举器的事实。一种解决方案是简单地创建一个执行map工作的枚举器。

def doit(input, mapper, detector)
  Enumerator.new do |y|
    input.each do |e|
      y << mapper.(e)
    end
  end.find { |m| detector.(m) }
end

array = [2, 3, 4]
mapper = ->(v) { result = v * v; result if result.odd? }

doit(array, mapper, ->(v) { v })   #=> 9
doit(array, mapper, ->(v) { nil }) #=> nil

还有其他原因可能导致人们不希望将mapper应用于数组的所有元素(除了它是一项昂贵的操作)。假设

detector = ->(v) { v }
array = [2, 3, 4, 5, "cat"]

mapper如上所述(轰炸&#34; cat&#34;)或

->(v) { |v| v == 5 ? launch_missiles(); v * v if v.odd? } 

答案 2 :(得分:1)

感谢@Amadan的启发性评论:我们可以缩小映射器的强制条件(如果输入无效则返回nil)然后使用哑检测器,允许唯一的块到辅助方法:

def map_detect(input)
  return input.enum_for(:each_with_object, nil) unless block_given?
  input.each_with_object(nil) do |value, _|
    result = yield value
    break result if result
  end
end

答案 3 :(得分:0)

我不知道我的问题是否正确,但是如果你只是需要一种方法来找到ruby数组中的第一个匹配并用它做一些事情,你可以简单地做一些事情:

    my_array = [1,2,3,4,5]
    # creates a new array for each element in my_array where the given block returns true
    matches = my_array.select {|entry| what_ever_the_condition_is }
    # if there is a match
    if !matches.empty?
        # get the first entry
        first = matches.first
        # do something with the match
        # ....
        # ....
    end