【问题标题】:Ruby: how to split a string on a regex while keeping the delimiters? [duplicate]Ruby:如何在保留分隔符的同时在正则表达式上拆分字符串? [复制]
【发布时间】:2015-04-21 04:35:17
【问题描述】:

这里有been asked multiple times,但从来没有一个通用的答案,所以我们开始吧:

假设你有一个字符串,任何字符串,但让我们使用"oruh43451rohcs56oweuex59869rsr",你想用正则表达式分割它。任何正则表达式,但让我们使用一系列数字:/\d+/。然后你会使用split:

"oruh43451rohcs56oweuex59869rsr".split(/\d+/)
# => ["oruh", "rohcs", "oweuex", "rsr"]

这很可爱,但我想要数字。所以我们有scan:

"oruh43451rohcs56oweuex59869rsr".scan(/\d+/)
# => ["43451", "56", "59869"]

但我想要这一切!有没有,比如说,split_and_scan?没有。

splitscan 然后zip 他们怎么样?让我在这里阻止你。

好的,那怎么办?

【问题讨论】:

  • 您的预期输出是什么?你能解释一下你所说的一般解决方案是什么意思吗?
  • 通用解决方案适用于任何有效的给定输入;在这种情况下,任何字符串和任何正则表达式。
  • @Chris Heald 建议捕获组解决方案。

标签: ruby regex string split


【解决方案1】:

如果split 的模式包含捕获组,则该组将包含在结果数组中。

str = "oruh43451rohcs56oweuex59869rsr"
str.split(/(\d+)/)
# => ["oruh", "43451", "rohcs", "56", "oweuex", "59869", "rsr"]

如果你想把它拉上拉链,

str.split(/(\d+)/).each_slice(2).to_a
# => [["oruh", "43451"], ["rohcs", "56"], ["oweuex", "59869"], ["rsr"]]

【讨论】:

  • 不错。我实际上知道这一点,但完全忘记了。我不确定“zip”解决方案是否适用于所有情况。
  • @kch:我相信它应该,只要你在分隔符中包含一个捕获组。如果字符串开头,则第一组为["", "delim"]。最后一个有点反常,但也有道理——分隔符会读作nil,这没关系,因为它确实不存在。
  • 我认为你是对的。无论如何,您可能想转到我链接的先前问题并发布此答案(我不记得在其中看到它,但也许它被埋没了。)。它比我的自定义实现更简单,可能更快,并且具有多个捕获功能。
  • @AvinashRaj:我的意思是,鉴于分隔符的模式是 \d+,您需要同时拥有 \d+\D+ - 分隔符和分隔符之间的内容很容易定义.如果分隔符更复杂,例如 OP 给出的示例 (/\w\d+\w/),则为交替的另一侧提出一个模式并非易事(即与示例字符串中的 ["oru", "ohc", "weue", "sr"] 匹配的模式) ,使str.scan(/#{delim}|#{notdelim}/) 方法无法概括; split 方法使它变得微不足道 (str.split(/(\w\d+\w)/))。
  • @Amadan 感谢您的解释。现在我明白了,但我认为你仍然可以使用扫描功能string.scan(/\w\d+\w|(?:(?!\w\d+\w).)+/)string.scan(/delim|(?:(?!delim).)+/)
【解决方案2】:

很高兴你问...嗯,String#shatter 来自Facets。我不喜欢它,因为它是使用诡计实现的(看看源代码,这是很聪明的诡计,但如果你的字符串实际上包含 "\1" 怎么办?)。

所以我推出了自己的。这是你得到的:

"oruh43451rohcs56oweuex59869rsr".unjoin(/\d+/)
# => ["oruh", "43451", "rohcs", "56", "oweuex", "59869", "rsr"]

下面是实现:

class Object
  def unfold(&f)
    (m, n = f[self]).nil? ? [] : n.unfold(&f).unshift(m)
  end
end

class String
  def unjoin(rx)
    unfold do |s|
      next if s.empty?
      ix = s =~ rx
      case
      when ix.nil?; [s , ""]
      when ix == 0; [$&, $']
      when ix >  0; [$`, $& + $']
      end
    end
  end
end

(底部更详细的版本)

以下是一些正在处理的极端情况的示例:

"".unjoin(/\d+/)     # => []
"w".unjoin(/\d+/)    # => ["w"]
"1".unjoin(/\d+/)    # => ["1"]
"w1".unjoin(/\d+/)   # => ["w", "1"]
"1w".unjoin(/\d+/)   # => ["1", "w"]
"1w1".unjoin(/\d+/)  # => ["1", "w", "1"]
"w1w".unjoin(/\d+/)  # => ["w", "1", "w"]

就是这样,还有更多……

或者,如果您不喜欢使用内置类...好吧,您可以使用 Refinements...但如果您真的不喜欢它,这里是函数:

def unfold(x, &f)
  (m, n = f[x]).nil? ? [] : unfold(n, &f).unshift(m)
end

def unjoin(s, rx)
  unfold(s) do |s|
    next if s.empty?
    ix = s =~ rx
    case
    when ix.nil?; [s , ""]
    when ix == 0; [$&, $']
    when ix >  0; [$`, $& + $']
    end
  end
end

我还想到,可能并不总是清楚哪些是分隔符,哪些是分隔位,所以这里有一个小补充,可以让您使用#joint? 查询一个字符串以了解它在拆分之前所扮演的角色:

class String

  def joint?
    false
  end

  class Joint < String
    def joint?
      true
    end
  end

  def unjoin(rx)
    unfold do |s|
      next if s.empty?
      ix = s =~ rx
      case
      when ix.nil?; [s, ""]
      when ix == 0; [Joint.new($&), $']
      when ix >  0; [$`, $& + $']
      end
    end
  end
end

这里正在使用:

"oruh43451rohcs56oweuex59869rsr".unjoin(/\d+/)\
  .map { |s| s.joint? ? "(#{s})" : s }.join(" ")
# => "oruh (43451) rohcs (56) oweuex (59869) rsr"

您现在可以轻松地重新实现splitscan

class String

  def split2(rx)
    unjoin(rx).reject(&:joint?)
  end

  def scan2(rx)
    unjoin(rx).select(&:joint?)
  end

end

"oruh43451rohcs56oweuex59869rsr".split2(/\d+/)
# => ["oruh", "rohcs", "oweuex", "rsr"]

"oruh43451rohcs56oweuex59869rsr".scan2(/\d+/)
# => ["43451", "56", "59869"]

如果你讨厌匹配全局变量和一般的简洁性……

class Object
  def unfold(&map_and_next)
    result = map_and_next.call(self)
    return [] if result.nil?
    mapped_value, next_value = result
    [mapped_value] + next_value.unfold(&map_and_next)
  end
end

class String
  def unjoin(regex)
    unfold do |tail_string|
      next if tail_string.empty?
      match = tail_string.match(regex)
      index = match.begin(0)
      case
      when index.nil?; [tail_string, ""]
      when index == 0; [match.to_s, match.post_match]
      when index >  0; [match.pre_match, match.to_s + match.post_match]
      end
    end
  end
end

【讨论】:

  • 嗯……我想如果您想要捕获,这仍然很有用。
猜你喜欢
  • 1970-01-01
  • 2014-11-22
  • 2011-02-24
  • 1970-01-01
  • 2013-07-06
  • 1970-01-01
  • 2013-09-27
  • 2012-08-13
  • 1970-01-01
相关资源
最近更新 更多