如何根据Ruby之间的字符数在Ruby中拆分字符串?

时间:2014-08-25 20:42:45

标签: ruby string substring

我现在正在编写一个Ruby宝石,但是当我试图想出一个有效的方法来执行以下操作时,我会遇到一些问题:

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

从那里我想得到:GBR,最后,第一中间作为输出

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

 string[2...5]

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

在LAST和FIRST之间始终存在<<,在FIRST和MIDDLE之间始终存在<,但LAST,FIRST和MIDDLE可以是任意长度(它们是示例名称)并且可能有更多而不只是FIRST和MIDDLE与<分隔符。例如:

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

我能看到这样做的唯一方法是通过渐进式if循环,但有没有一种更有效和本地的方式将其拆分,我没有看到?

3 个答案:

答案 0 :(得分:5)

a = "P<GBRLAST<<FIRST<MIDDLE<LION<<<<<<<<<<<<<<<<"

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

这会折叠所有'&lt;&lt;&lt;&lt;&lt;&lt;将字符串转换为单个'&lt;'字符,然后使用它作为分隔符拆分字符串。

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

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

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

做那些你想做的事。

这假定'first'总是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指出:

我们可以拆分一个'&lt;'并删除空白条目以避免使用正则表达式。

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的强大和简洁的一个真正美好的例子。

答案 1 :(得分: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"]

根据您的具体要求,您可能需要调整一下Regex以获得您想要的效果。

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

答案 2 :(得分:0)

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