【问题标题】:Is there ruby methods to select string between other strings?是否有 ruby​​ 方法可以在其他字符串之间选择字符串?
【发布时间】:2020-07-27 17:06:08
【问题描述】:

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

例如使用“开始”和“停止”这两个词: "开始 1 2 3 停止 5 6 开始 7 8 停止 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 ```





【问题讨论】:

  • 你可以标记这个 (split(/\s+/)) 然后分成开始/停止块。
  • 你需要学习正则表达式。

标签: ruby string select methods extract


【解决方案1】:

你并没有完全得到错误,codereview 可能是一个更好的地方问。但由于您是社区中的新手,这里有一个带有 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/

希望对您有所帮助。

【讨论】:

  • ? 感谢您提供的优质资源 ?? 编辑:好吧,显然我不能在评论中说谢谢...
【解决方案2】:

您可以从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*: 任意空格符
  3. (.*?)

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

  5. stop

结果是一个包含正则表达式命中的数组。正则表达式可能包含要检测的不同部分(多个()-pairs)。所以它是一个数组数组。在我们的例子中,每个内部数组都有一个元素,因此您可以使用 flatten 来获得一个“平面”数组。

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

【讨论】:

  • ? 也感谢您的详细解释?? 编辑:好吧,显然我不能在评论中说谢谢,那是☑ ...奇怪..❓
【解决方案3】:

单行方法链

这是一种基于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#stripString#split 处理。
  • \s+ 代替\b 可以处理一些极端情况,同时创建其他情况。
  • 它不会做任何事情来防止不平衡对,例如"start 0 start 2 3 4 stop 6 stop"

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

【讨论】:

  • ? 谢谢你的详细解释?? 编辑:好吧,显然我不能在评论中说谢谢你已经完成了..所以这里没有办法对答案说谢谢❓
【解决方案4】:

使用数组的选项:作为起点,我建议在String#split 之后使用Enumerable#slice_before

根据您的命令和停用词:

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"]}]

等等。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-10-07
    • 1970-01-01
    • 1970-01-01
    • 2020-04-14
    • 1970-01-01
    相关资源
    最近更新 更多