【问题标题】:How can I split a string in Ruby based on the number of characters between it?如何根据字符串之间的字符数在 Ruby 中拆分字符串?
【发布时间】:2014-10-19 02:03:26
【问题描述】:

我现在正在编写一个 Ruby gem,但我遇到了一些困难,因为我正在尝试一种有效的方法来执行以下操作:

P<GBRLAST<<FIRST<MIDDLE<<<<<<<<<<<<<<<<<<<<<

从中我想得到:GBR、LAST、FIRST MIDDLE 作为输出

我知道我可以使用类似的东西:

 string[2...5]

输出“GBR”,但我将如何获得“LAST”和“FIRST MIDDLE”作为其他输出?

在 LAST 和 FIRST 之间总会有 &lt;&lt;,在 FIRST 和 MIDDLE 之间总会有 &lt;,但 LAST、FIRST 和 MIDDLE 可以是任意长度(它们是示例名称),并且可能不仅仅是 FIRST 和中间带有&lt; 分隔符。例如:

 P<GBRLAST<<FIRST<MIDDLE<LION<<<<<<<<<<<<<<<<

我能看到这样做的唯一方法是通过渐进式 if 循环,但有没有更有效和更原生的方法来拆分它?

【问题讨论】:

  • 看看正则表达式和标题。如果你知道 First 总是会有一个
  • 这是来自护照机读区的字符串吗?
  • 是的,我刚刚完成了一个可以为您解析它们的 ruby​​ gem!在这里查看:github.com/rubyisilluminati/Verified

标签: ruby string substring


【解决方案1】:
a = "P<GBRLAST<<FIRST<MIDDLE<LION<<<<<<<<<<<<<<<<"

parts = a.gsub(/<+/, '<').split('<')
# => ["P", "GBRLAST", "FIRST", "MIDDLE", "LION"] 

这会将所有 '

first = parts[1][0..2]
# => "GBR" 

second = parts[1][3..-1]
# => "LAST" 

the_rest = parts[2..-1]
# => ["FIRST", "MIDDLE", "LION"] 

随心所欲。

这假定“第一”总是 3 个字符长,但我看不到任何其他拆分方式,除非您有更多规则。

编辑:

评论者建议的一些出色的优化。

@7stud 建议:

parts = a.gsub(/<+/, '<').split('<')

可以改写为:

parts = a.split(/<+/)

这在处理器周期方面也更有效。

Benchmark.measure { 10000.times { a.split(/<+/) }}
# => #<Benchmark::Tms:0x007fc0320b84a8 @label="", @real=0.053515, @cstime=0.0, @cutime=0.0, @stime=0.0, @utime=0.04999999999999999, @total=0.04999999999999999> 
Benchmark.measure { 10000.times { a.gsub(/<+/, '<').split('<') }}
# => #<Benchmark::Tms:0x007fc0328fe3d8 @label="", @real=0.081377, @cstime=0.0, @cutime=0.0, @stime=0.0, @utime=0.07999999999999996, @total=0.07999999999999996> 

@Shadwell 指出:

我们可以只拆分一个“

a.split("<").select { |s| !s.empty? }

避免正则表达式是一个很好的目标 - 正则表达式通常效率低下,它是一种适用于字符串操作的通用语言,而不是一种优化的、有针对性的操作。它们也不透明、容易出错、容易出现极端情况且难以维护。

然而,在这种情况下,使用 select 比在正则表达式上拆分效率要低一些。此外,正则表达式很简单,不用担心太多。

Benchmark.measure { 10000.times { a.split(/<+/) }}
# => #<Benchmark::Tms:0x007fc0320b84a8 @label="", @real=0.053515, @cstime=0.0, @cutime=0.0, @stime=0.0, @utime=0.04999999999999999, @total=0.04999999999999999>  
Benchmark.measure { 10000.times { a.split("<").select { |s| !s.empty? } }}
# => #<Benchmark::Tms:0x007fc032039ea0 @label="", @real=0.061219, @cstime=0.0, @cutime=0.0, @stime=0.0, @utime=0.06, @total=0.06> 

这里应该注意的是,除非我们处理真正大量的数据,否则不会真正感受到这些速度差异,因此代码可读性应该是第一位的。

@careyswoveland 评论了我最喜欢的:

[a[2..4]].concat(a[5..-1].split(/<+/))

它返回一个包含所有值的漂亮数组并处理任意数量的额外字符串。需要一点精神上的解包来理解发生了什么,但它是 Ruby 的强大和简洁的一个真正美丽的例子。

【讨论】:

  • 这比我和其他发帖人想出的正则表达式好得多,而且简单得多。
  • 不敢相信我没有看到这个。非常聪明的答案,谢谢。
  • 您也可以拆分 "&lt;" 并删除任何空白条目:a.split("&lt;").select { |s| !s.empty? } 以避免执行 gsub
  • 或者,一个轻微的变体,[a[2..4]].concat(a[5..-1].split(/&lt;+/)) =&gt; ["GBR", "LAST", "FIRST", "MIDDLE", "LION"]
  • 这会将所有 '
【解决方案2】:

听起来像是正则表达式的工作:

PATTERN = /P<(GBR)([^<]*)<<((?:[^<]+<)+)<+/
def parse(str)
  match_data = PATTERN.match(str)

  gbr  = match_data[1]
  last = match_data[2]
  rest = match_data[3].split('<')

  [gbr, last, *rest]
end

puts parse('P<GBRLAST<<FIRST<MIDDLE<LION<<<<<<<<<<<<<<<<').inspect

打印:

["GBR", "LAST", "FIRST", "MIDDLE", "LION"]

根据您的具体要求,您可能需要稍微调整正则表达式以获得您想要的。

有关正则表达式的更多信息,您可能会发现 regular-expressions.info 是一个有用的网站,可用于教程等。您可能还会发现 regex101.com 是一个宝贵的资源,可用于测试和调试您可能编写的任何正则表达式 (example)。

【讨论】:

  • 很确定您必须在 + 之前将最后一次捕获用括号括起来,因为可以有多个名字/中间名。
  • 你也可以这样写:a = str.scan(PATTERN).flatten; a[0..-2] + a[-1].split('&lt;') #=&gt; ["GBR", "LAST", "FIRST", "MIDDLE", "LION"].
  • @CarySwoveland 我想演示 MatchData 的使用,因为 OP 的实际用例可能不是“返回数组”。但更复杂的事情。
【解决方案3】:
str = 'P<GBRLAST<<FIRST<MIDDLE<<<<<<<<<<<<<<<<<<<<<'

puts str[2..4]

str[5..-1].scan(/[^<]+/xm) do |match|
  puts match
end

--output:--
GBR
LAST
FIRST
MIDDLE

获取所有文字并不难:

str = 'P<GBRLAST<<FIRST<MIDDLE<<<<<<<<<<<<<<<<<<<<<'

character_groups = str.scan(/[^<]+/)
p character_groups  

--output:--
["P", "GBRLAST", "FIRST", "MIDDLE"]

但是接下来有两个问题:

1) 目标文本是否总是第 2、3、4 组?

2) GBR 总是完全是 GBR 吗?或者任何三个字母序列?

puts character_groups[1][0..2]   #GBR
puts character_groups[1][3..-1]  #LAST, i.e. the rest of the string

puts character_groups[2..4]      #FIRST
                                 #MIDDLE

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-11-16
    • 1970-01-01
    • 1970-01-01
    • 2011-04-29
    • 2021-10-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多