我有一个数组: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)
假设每个X
inarr
都是一个正则表达式模式。无需运行每个正则表达式,我想返回第一场比赛的捕获组。
在 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)
您可以使用.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)