在其他字符串之间是否有 ruby 方法到 select 字符串?

Is there ruby methods to select string between other strings?

我正在开始编程,我正在寻找一个程序来提取文本中两个单词之间包含的所有单词(以便将它们存储在变量中)

例如 "START" 和 "STOP": "START 1 2 3 STOP 5 6 START 7 8 STOP 9 10"

我想存储在变量中:1 2 3 7 8

我开始用Ruby来做,你可以在下面的代码中看到,我目前的想法是将字符串"global"转换成一个数组,然后对string1和string2的位置进行编号;然后用初始数组#string1 + 1,… string2 -1 的值创建一个数组“string1”。 不幸的是,它只工作一次,因为 .index 函数只在第一次出现时工作...有更好的方法吗?

提前感谢您的帮助

text = "0 start 2 3 4 stop 6 7 start 9 10 stop 12"

start= text.split(' ')

a = start.index('start')
b = start.index('stop')

puts a
puts b
puts c = start[a+1,b-a-1].join(" ")

# returns 
#1
#5
#2 3 4 ```





您并没有收到错误,codereview might be a better place to ask. But since you are new in the community, here is a regular expression with lookaround assertions 可以完成工作:

text = "0 start 2 3 4 stop 6 7 start 9 10 stop 12"
text.scan(/start ((?:(?!start).)*?) stop/).join(' ')
# => "2 3 4 9 10"

顺便说一句,在 Ruby 中测试正则表达式的好地方是 https://rubular.com/

希望对您有所帮助。

您可以从 scan 方法和正则表达式开始:

text = "0 start 2 3 4 stop 6 7 start 9 10 stop 12"
res1 = text.scan(/start\s*(.*?)\s*stop/) #[["2 3 4"], ["9 10"]]
res2 = res1.flatten #["2 3 4", "9 10"]

或没有中间变量:

res = text.scan(/start(.*?)stop/).flatten #["2 3 4", "9 10"]

解释:

扫描方法见https://apidock.com/ruby/String/scan

正则表达式/start\s*(.*?)\s*stop/

的组合
  1. 开始
  2. \s*:任何space字符
  3. (.*?):

    1. ()负责记住内容。
    2. .表示任意字符,*表示重复(零个或多个字符),?将结果限制为最短的可能性(详见下文)
  4. \s*:任何space字符

  5. stop

结果是一个包含正则表达式匹配项的数组。正则表达式可以包含要检测的不同部分(多个 () 对)。所以它是一个数组的数组。在我们的例子中,每个内部数组都有一个元素,所以你可以使用 flatten 来得到一个 'flat' 数组。

如果您不在正则表达式中使用 ?,那么您会找到 2 3 4 stop 6 7 start 9 10 而不是较短的部分。

一行方法链

这是一种基于String#scan的方法:

text = "0 start 2 3 4 stop 6 7 start 9 10 stop 12"
text.scan(/\bstart\s+(.*?)\s+stop\b/i).flat_map { _1.flat_map &:split }
#=> ["2", "3", "4", "9", "10"]

这里的想法是:

  1. 提取不区分大小写的 startstop 关键字之间的所有字符串段。

    text.scan /\bstart\s+(.*?)\s+stop\b/i
    #=> [["2 3 4"], ["9 10"]]
    
  2. 提取关键词之间用空格分隔的词。

    [["2 3 4"], ["9 10"]].flat_map { _1.flat_map &:split }
    #=> ["2", "3", "4", "9", "10"]
    

注意事项

上述方法的值得注意的注意事项包括:

  • String#scan 创建嵌套数组,重复调用 Enumerable#flat_map 来处理它们并不像我希望的那样优雅。
  • \b 是零宽度断言,因此查找单词边界可能会导致 #scan 在结果中包含前导和尾随空格,然后需要由 String#strip or String#split 处理。
  • \s+ 代替 \b 可以处理一些边缘情况,同时创建其他情况。
  • 它没有做任何事情来防止不平衡的对,例如"start 0 start 2 3 4 stop 6 stop".

对于简单的用例,String#scan 和经过调整的正则表达式可能就是您所需要的。您的输入和数据结构越多样化和不可预测,您的解析例程需要处理的边缘情况就越多。

选项使用数组:作为起点我建议使用Enumerable#slice_before after String#split

根据您的命令和停用词:

command = "START 1 2 3 STOP 5 6 START 7 8 STOP 9 10"

start = 'START'
stop = 'STOP'

你可以这样使用它:

grouped_cmd = command.split.slice_before { |e| [start, stop].include? e } # .to_a
#=> [["START", "1", "2", "3"], ["STOP", "5", "6"], ["START", "7", "8"], ["STOP", "9", "10"]]

然后你可以随意操作,例如:

grouped_cmd.select { |first, *rest| first == start }
#=> [["START", "1", "2", "3"], ["START", "7", "8"]]

grouped_cmd.each_with_object([]) { |(first, *rest), ary| ary << rest if first == start }
#=> [["1", "2", "3"], ["7", "8"]]

甚至

grouped_cmd.each_slice(2).map { |(start, *stt), (stop, *stp)| { start.downcase.to_sym => stt, stop.downcase.to_sym => stp } }
#=> [{:start=>["1", "2", "3"], :stop=>["5", "6"]}, {:start=>["7", "8"], :stop=>["9", "10"]}]

以此类推