将字符串拆分为列表,但保留分隔符。

15

目前我正在按照模式将一个字符串拆分,像这样:

outcome_array=the_text.split(pattern_to_split_by)

问题在于我用来分割的模式本身总是会被省略。

我该如何让它包含分割的模式本身?


你想要保存两个字符串,一个是分割后的,一个是未分割的,这是你要找的吗? - Mr A
你想在哪里包含这个模式?在字符串内部吗? - Hunter McMillen
我想要分割模式也返回到字符串内。 - john-jones
1
你能给出一个输入和输出的例子吗? - ry.
@Hermann:你为什么会从“这里和”中得到'split it '这个分割出来的部分呢? - Andrew Grimm
输入 = "split it here and here okay",分隔符 = 'here'。将产生输出 = ['split it ', 'here and ', 'here okay'] - john-jones
3个回答

29

感谢 Mark Wilkins 的启发,但以下是一个更短的代码片段:

irb(main):015:0> s = "split on the word on okay?"
=> "split on the word on okay?"
irb(main):016:0> b=[]; s.split(/(on)/).each_slice(2) { |s| b << s.join }; b
=> ["split on", " the word on", " okay?"]
或者:
s.split(/(on)/).each_slice(2).map(&:join)

请往下滑动查看解释。


这是它的工作原理。首先,我们在"on"上进行分割,但将其用括号括起来以使其成为匹配组。当正则表达式中传递了一个匹配组给split函数时,Ruby会将该组包含在输出结果中:

s.split(/(on)/)
# => ["split", "on", "the word", "on", "okay?"

现在我们想要将每个“on”的实例与其前面的字符串连接起来。each_slice(2)可以通过一次传递两个元素到其块中来帮助完成此操作。让我们只是调用each_slice(2)以查看结果。由于each_slice在没有块被调用时会返回一个枚举器,因此我们将应用to_a来枚举枚举器的内容:

s.split(/(on)/).each_slice(2).to_a
# => [["split", "on"], ["the word", "on"], ["okay?"]]
我们已经接近成功了。现在我们需要将这些单词连接起来,这样就得到了上面的完整解决方案。我会将其展开成单独的行以便于跟踪:
b = []
s.split(/(on)/).each_slice(2) do |s|
  b << s.join
end
b
# => ["split on", "the word on" "okay?"]

但有一种巧妙的方法可以消除临时的b并大大缩短代码:

s.split(/(on)/).each_slice(2).map do |a|
  a.join
end

map将输入数组的每个元素传递给块;块的结果成为输出数组中该位置的新元素。在MRI >= 1.8.7中,您甚至可以缩短到等效:

s.split(/(on)/).each_slice(2).map(&:join)

2
+1 非常好。我喜欢 Ruby;它具有简洁而易读的语法。 - Mark Wilkins
@WayneConrad 很感激您提供的更为详尽的解释。谢谢。 - Maxim Veksler

8
您可以使用正则表达式断言来定位分割点,而不需要消耗任何输入。下面使用了一个正向后查找断言,在“on”之后进行分割:
s = "split on the word on okay?"
s.split(/(?<=on)/)
=> ["split on", " the word on", " okay?"]

或者使用正向预查,在“on”之前进行拆分:

s = "split on the word on okay?"
s.split(/(?=on)/)
=> ["split ", "on the word ", "on okay?"]

如果您使用这样的方法,您可能希望确保“on”不是更大单词(例如“assertion”)的一部分,并删除分割处的空格:

"don't split on assertion".split(/(?<=\bon\b)\s*/)
=> ["don't split on", "assertion"]

你可以使用这些的组合来在中间进行分割,例如:"this><is><it".split(/(?<=>)(?=<)/) => ["this>", "<is>", "<it"] - android.weasel

4
如果您使用带有组的模式,则它也会在结果中返回该模式:
irb(main):007:0> "split it here and here okay".split(/ (here) /)
=> ["split it", "here", "and", "here", "okay"]
编辑:附加信息表明,目标是将被拆分的项之一与其一半包含在一起。我认为有一种简单的方法可以做到这一点,但我不知道它是什么,并且今天没有时间去尝试。因此,在没有巧妙的解决方案的情况下,以下是一种强制性的方法。使用上面描述的split方法将拆分项包含在数组中。然后遍历数组并将每个第二个条目(根据定义是拆分值)与前一个条目组合在一起。
s = "split on the word on and include on with previous"
a = s.split(/(on)/)

# iterate through and combine adjacent items together and store
# results in a second array
b = []
a.each_index{ |i|
   b << a[i] if i.even?
   b[b.length - 1] += a[i] if i.odd?
   }

print b

转化为以下结果:

["split on", " the word on", " and include on", " with previous"]

那个 'irb(main):007:0>' 是什么东西? - john-jones
在这种情况下,我希望将“here”作为它正在分割的字符串的一部分。 - john-jones
@Hermann:这是交互式Ruby shell。在提示符处键入“irb”,即可即时输入Ruby命令。 - Mark Wilkins
@Hermann:我现在不知道获取它的语法。如果您有具体的示例,使用适当的正则表达式可能是可能的。但是,手动迭代字符串并手动拆分可能更简单。等我有时间再仔细考虑一下。这是一个有趣的问题。 - Mark Wilkins

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接