我现在正在编写一个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
循环,但有没有一种更有效和本地的方式将其拆分,我没有看到?
答案 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>
应该注意的是,除非我们处理真正大量的数据,否则不会真正感受到这些速度差异,因此应首先考虑代码可读性。
[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