如何在Ruby中将CamelCase字符串拆分为其子字符串?

35

我有一个像 ImageWideNice 或者 ImageNarrowUgly 这样的驼峰字符串。现在我想将这个字符串分解成子字符串,如 ImageWideNarrowNiceUgly

我认为可以通过以下方式简单地解决:

camelCaseString =~ /(Image)((Wide)|(Narrow))((Nice)|(Ugly))/

但奇怪的是,这样只会填充$1$2,而不是$3

你有更好的方法来拆分这个字符串吗?


1
你想对 ThisIsANarrowImageOfHIV 做什么?与 n 进行连接,还是将 HIV 分割? - Andrew Grimm
8个回答

67
s = 'nowIsTheTime'

s.split /(?=[A-Z])/

=> ["now", "Is", "The", "Time"]

?=pattern 是正向先行断言的一个例子。它会匹配字符串中 pattern 之前的位置,但不会消耗这些字符,也就是说,它并不包括 pattern 在匹配结果中。下面是另一个例子:

    irb> 'streets'.sub /t(?=s)/, '-'
=> "stree-s"

在这种情况下,s被匹配(只有第二个t匹配),但没有被替换。感谢@Bryce和他的regexp doc link。 Bryce Anderson提供了以下解释:

()匹配组的开头使用?=被称为正向预查,这只是一种说法,即虽然正则表达式在确定是否匹配时正在查看字符,但它们不是匹配的一部分。 split()通常会忽略中间的字符,但在这种情况下,匹配本身是空的,因此没有任何东西。


抱歉@DigitalRoss,我忘记提到我是用RegexBuddy进行测试的。但我想知道为什么在Ruby中这是有效的?Java也会为第一个数组元素返回一个空字符串:"NowIsTheTime".split("(?=[A-Z])") - splash
你可以随时添加 reject {|x| x.empty? } - Ollie Saunders
不需要,在Ruby中它运行良好。Splash在Java和RegexBuddy中遇到了麻烦。 - DigitalRoss
7
回答中缺少解释为什么它能够工作,对于仍在尝试理解正则表达式的人来说可能很有用:() 匹配组开头的 ?= 被称为“正向前瞻”,这只是一种说法,即虽然正则表达式正在查看字符以确定是否匹配,但它不会将其作为匹配的一部分。split() 通常会吞掉中间的字符,但在这种情况下,匹配本身为空,因此没有任何“中间”的内容。正则表达式文档 - Bryce Anderson
要处理像S.M.Mousavi提出的缩写词这样的驼峰式命名,请参见我的替代答案 - EFC
显示剩余2条评论

37

我知道这个回答可能有些过时了,但对于其他人可能仍然有用。在Rails中,你可以这样做:"NowIsTheTime".underscore.humanize


14

DigitalRoss的答案是正确的,因为它处理了一般情况,即您不知道它是严格的驼峰命名(第一个字符小写)还是帕斯卡命名(第一个字母大写)。

如果您知道字符串以哪种形式存在,或者您想强制使用其中之一,Inflector可以实现。

对于帕斯卡命名:

"NowIsTheTime".titleize

对于驼峰命名法:

"nowIsTheTime".titleize.camelize :lower

1
需要注意的是,#titleize#camelize是严格属于Rails方法,而不是核心Ruby。 - onebree

2
I/p:- "ImageWideNice".scan(/[A-Z][a-z]+/).join(",")

O/p:- "Image,Wide,Nice"

2

虽然这是一个关于Ruby正则表达式的问题,而且DigitalRoss的答案正确且简单易懂,但我想再添加一个Java的答案:

// this regex doesn't work perfect with Java and other regex engines
"NowIsTheTime".split("(?=[A-Z])"); // ["", "Now", "Is", "The", "Time"]

// this regex works with first uppercase or lowercase characters
"NowIsTheTime".split("(?!(^|[a-z]|$))"); // ["Now", "Is", "The", "Time"]
"nowIsTheTime".split("(?!(^|[a-z]|$))"); // ["now", "Is", "The", "Time"]

2
你试过了吗?
camelCaseString =~ /(Image)(Wide|Narrow)(Nice|Ugly)/

?


1

DigitalRoss的答案不会识别嵌入在驼峰命名法中的缩写词。例如,它将把"MyHTMLTricks"分成"My H T M L Tricks"而不是"My HTML Tricks"。

这里有另一个选项,基于PmWiki中的AsSpaced()函数,它非常敏感于这种情况:

"MyHTMLTricks" \
.gsub(/([[:lower:]\\d])([[:upper:]])/, '\1 \2') \
.gsub(/([^-\\d])(\\d[-\\d]*( |$))/,'\1 \2') \
.gsub(/([[:upper:]])([[:upper:]][[:lower:]\\d])/, '\1 \2')

=> "My HTML Tricks"

我喜欢这种方法的另一点是它保持了字符串的本质,而不是将其转化为数组。如果你真的需要一个数组,那么只需在最后添加一个分割即可。
"MyHTMLTricks" \
.gsub(/([[:lower:]\\d])([[:upper:]])/, '\1 \2') \
.gsub(/([^-\\d])(\\d[-\\d]*( |$))/,'\1 \2') \
.gsub(/([[:upper:]])([[:upper:]][[:lower:]\\d])/, '\1 \2') \
.split

=> ["My", "HTML", "Tricks"]

记录一下,这是来自PmWiki的原始PHP代码。

function AsSpaced($text) {
    $text = preg_replace("/([[:lower:]\\d])([[:upper:]])/", '$1 $2', $text);
    $text = preg_replace('/([^-\\d])(\\d[-\\d]*( |$))/', '$1 $2', $text);
    return preg_replace("/([[:upper:]])([[:upper:]][[:lower:]\\d])/", '$1 $2', $text);
}

0
def solution(string)
  final_str = []
  string.chars.each do |x|
    final_str << " "  if x.upcase == x 
    final_str << x
  end
  final_str.join
end

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