関数を備えた Ruby イテレータ。リスト全体を反復せずに関数から最初の値を返します
概要
配列: arr=[x1, x2, x3…] と、関数が真実であることを示す arr の最初の x に基づいて値を返す関数があります。
基本的に:
# 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)
arr の各 X が正規表現パターンであると仮定します。すべての正規表現を実行することなく、最初の一致からキャプチャ グループを返したいと考えています。
Python では、next を使用してジェネレーターを作成します。真実が返されるまで各述語を実行し、その値を m に渡します。真実が返されない場合は、デフォルトとして 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)
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
これが私が望む機能ですが、Python ジェネレーターと比較すると少し冗長に思えます。
最初の値を述語として grep を試してみました。しかし、ここでの問題は、結果配列全体が次に使用される前に生成されることです。
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>
find を試みましたが、キャプチャ グループではなく、true である最初のパターンが返されました。
> e=patterns.find{|p| p.match(s) }
=> /: (Value.*?pref)/
# Now I would have to rerun match with the pattern found to get the text
アイデアは?
役立つアイデアをありがとうございました。 Ruby キットバッグの中でいくつかの新しいことを学びました。
いくつか調べて試してみた結果、私にとって最良の方法は、Dogbert の Lazy.filter_map と Stefan の提案である s[regex, 1] を組み合わせることだと思います。
val_I_want=patterns.lazy.filter_map { |p| s[p, 1] }.first
興味深いことに、構文 s[p, 1] は、[] 演算子内の括弧のない動的正規表現 (Regexp.new “#{p.to_s}(.*)“) をサポートしていないため、魅力が損なわれます。
私は最終的に使用しました:
patterns.lazy.filter_map { |p| card.match("#{p}(.*)")&.[](1) }.first
しかし、これも機能します:
patterns.find{ |p| m = card.match("#{p}(.*)") and break m[1] }
より一般的なケースでは、次のようにすることができます。
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]
そして、engineersmnky による私の .find の試みの修正に対する非常に名誉ある言及です。
val_I_want = patterns.find {|p| m = p.match(s) and break m[1] }
解決策
.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
これにより、3 より大きい最初の x に対して x * 2 が返されます。私は 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"
以下のコメントで @Stefan が提案した String[Regexp, Integer] 構文を使用します。
regexes.lazy.filter_map { |regex| p regex; string[regex, 1] }.first
デモ:
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