带有函数的 Ruby 迭代器。返回函数的第一个值而不迭代整个列表

daw*_*awg 1 ruby

我有一个数组:arr=[x1, x2, x3...]和一个函数,该函数基于第一个函数返回一个值x,因为arr该函数是真实的。

本质上:

# my_x is the return of func() 
# with the first x in arr that func(x) is true
# and the entire arr array is not processed.

my_x=arr.ruby_magic{|x| func(x) } 

my_x should be equal to first true value return of func(x)
Run Code Online (Sandbox Code Playgroud)

假设每个Xinarr都是一个正则表达式模式。无需运行每个正则表达式,我想返回第一场比赛的捕获组。

在 Python 中,我会用next. 它将运行每个谓词,直到返回真值,然后将该值传递给m。如果没有 true return,None则用作默认值,但该默认值可以是任何值:

import re 

patterns=[r"no match", r": (Value.*?pref)", r": (Value.*)", r"etc..."]

s=""" 
This is the input txt
This is a match if the other is not found: Value 1

This is the match I am looking for first: Value 1 pref

Last line.
"""

val_I_want=next(
        (m.group(1) for p in patterns 
            if (m:=re.search(rf'{p}', s))), None)
Run Code Online (Sandbox Code Playgroud)

我还没有在 Ruby 中找到类似的东西。

我可以做一个明确的循环:

# s in the same multiline string as above...

patterns=[/no match/, /: (Value.*?pref)/, /: (Value.*)/,/etc.../]

val_I_want=nil 
patterns.each{|p| 
    m=p.match(s)
    if m then
        val_I_want=m[1]
        break 
    end     
}
# val_I_want is either nil or 
# the first match capture group that is true
Run Code Online (Sandbox Code Playgroud)

这就是我想要的功能,但与 Python 生成器相比似乎有点冗长。

我尝试过grep将第一个值作为谓词。但这里的问题是整个next结果数组是在使用之前生成的:

patterns.grep(proc {|p| p.match(s)}) {|m| m.match(s)[1]}.to_enum
# can then use .next on that.
#BUT it runs though the entire array when all I want is the first

#<Enumerator: ["Value 1 pref", "Value 1"]:each>
Run Code Online (Sandbox Code Playgroud)

我尝试过find,但返回第一个正确的模式,而不是捕获组:

> e=patterns.find{|p| p.match(s) }
=> /: (Value.*?pref)/

# Now I would have to rerun match with the pattern found to get the text
Run Code Online (Sandbox Code Playgroud)

有想法吗?


非常感谢您提供的有用的想法。我在 Ruby 工具包中学到了一些新东西。

经过查看和尝试后,我认为对我来说最好的方法是将 Dogbert 的lazy.filter_map建议与 Stefans 的建议结合起来s[regex, 1]

val_I_want=patterns.lazy.filter_map { |p| s[p, 1] }.first
Run Code Online (Sandbox Code Playgroud)

有趣的是,该语法s[p, 1]不支持[]运算符内没有括号的动态正则表达式,这样(Regexp.new "#{p.to_s}(.*)")就失去了吸引力。

我最终使用了:

patterns.lazy.filter_map { |p| card.match("#{p}(.*)")&.[](1) }.first
Run Code Online (Sandbox Code Playgroud)

但这也有效:

patterns.find{ |p| m = card.match("#{p}(.*)") and break m[1] }
Run Code Online (Sandbox Code Playgroud)

在更一般的情况下,您可以执行以下操作:

def func(x)
  # silly function for show
  x*x
end     

arr=[1,3,5,6,7,8,9]

p arr.lazy.filter_map { |x| (fx=func(x))>30 ? [x,fx] : nil }.first
# [6, 36]
Run Code Online (Sandbox Code Playgroud)

非常荣幸地提及 Engineersmnky 对我的.find尝试的修改:

val_I_want = patterns.find {|p| m = p.match(s) and break m[1] }
   
Run Code Online (Sandbox Code Playgroud)

Dog*_*ert 5

您可以使用.lazy.filter_map { .. }.first。在找到第一个真值后,这不会运行元素块。

irb> [1, 2, 3, 4, 5].lazy.filter_map { |x| p x; x > 3 ? x * 2 : nil }.first
1
2
3
4
=> 8
Run Code Online (Sandbox Code Playgroud)

这将返回x * 2第一个x大于 3 的元素。我添加p x;此代码是为了表明此代码不处理列表的第 5 个元素。


正则表达式示例:

irb> regexes = [/(1)/, /(2)/, /(3)/]
=> [/(1)/, /(2)/, /(3)/]
irb> regexes.lazy.filter_map { |regex| p regex; regex.match("2")&.[](1) }.first
/(1)/
/(2)/
=> "2"
Run Code Online (Sandbox Code Playgroud)

使用String[Regexp, Integer]@Stefan 在下面的评论中建议的语法:

regexes.lazy.filter_map { |regex| p regex; string[regex, 1] }.first
Run Code Online (Sandbox Code Playgroud)

演示:

irb> regexes = [/(1)/, /(2)/, /(3)/]
=> [/(1)/, /(2)/, /(3)/]
irb> string = "2"
=> "2"
irb> regexes.lazy.filter_map { |regex| p regex; string[regex, 1] }.first
/(1)/
/(2)/
=> "2"
irb> string = "4"
=> "4"
irb> regexes.lazy.filter_map { |regex| p regex; string[regex, 1] }.first
/(1)/
/(2)/
/(3)/
=> nil
Run Code Online (Sandbox Code Playgroud)

  • 对于字符串,如果正则表达式匹配,则“str[regex, 1]”返回第一个捕获。 (3认同)
  • @dawg 方法是 [`MatchData#[]`](https://ruby-doc.org/core-3.1.0/MatchData.html#method-i-5B-5D),参数是 `1`。一般来说,这会被表示为“obj[1]”,这是解析器提供的一些糖;然而,如果中间有安全导航运算符,则对该方法的调用必须使用“method_name(args)”的传统方法语法 (2认同)