根据大写字母或数字字符拆分UTF8字符串

5
作为这个问题的回答,我可以像这样分割包含大写字母的字符串:
function splitAtUpperCase($string){
    return preg_replace('/([a-z0-9])?([A-Z])/','$1 $2',$string);
}

$string = 'setIfUnmodifiedSince';
echo splitAtUpperCase($string);

输出为“如果未修改,则设置”。但我需要进行一些修改:
- 当字符串中存在这些字符ÇÖĞŞÜİ时,该代码片段无法处理。我不想转换这些字符。否则就会失去单词的意义。我需要使用一些UTF字符。该代码使“HereÇonThen”变成“HereÇon Then”。 - 我也不想拆分大写缩写。如果单词是“IKnowYouWillComeASAPHere”,我需要将其转换为“I Know You Will Come ASAP Here”。 - 如果所有字母都是大写字母,请不要分隔。比如“DONTCOMEHERE”。 - 也要分隔数字值。“Before2013ends” 变成“Before 2013 ends”。 - 如果第一个字符是哈希键(#),请分隔。
案例和期望结果。
  1. "comeHEREtomorrow" => "come HERE tomorrow"
  2. "KissYouTODAY" => "kiss you TODAY"
  3. "comeÜndeHere" => "come Ünde Here"
  4. "NEVERSAYIT" => "NEVERSAYIT"
  5. "2013willCome" => "2013 will Come"
  6. "Before2013ends" => "Before 2013 ends"
  7. "IKnowThat" => "I Know That"
  8. "#whatiknow" => "# whatiknow"

对于这些情况,我使用了连续的str_replace操作。 我寻找一种简短的解决方案,不需要太多循环来检查单词。 如果可能的话,最好将其作为preg_replace等。

编辑:任何人都可以通过更改此PHP fiddle中的convert函数来尝试自己的解决方案:http://ideone.com/9gajZ8


1
如何使用正则表达式知道 IIKnow 中是一个不同的单词?这是不可能的。对于 ASAPHere 也是一样。 - Shiplu Mokaddim
如果后面有两个大写字母和一个小写字母,则拆分它,如果有超过两个大写字母,则存在缩写,因此在不获取下一个单词的第一个字母的情况下进行拆分。 - trante
你想把 JScript 分割成 J Script 吗? - Shiplu Mokaddim
顺便问一下,除了那个来自另一个问题的代码片段,你做了什么? - Shiplu Mokaddim
2
什么逻辑可以将 HEREtomorrow 拆分为 HERE tomorrow,并将 IKnow 拆分为 I Know 而不是 IK now - Shiplu Mokaddim
显示剩余2条评论
4个回答

2

一些注意事项:

  • 使用Unicode属性搜索大写字母和小写字母(甚至是标题大小写,例如Dž Lj Nj Dz
  • comeHEREtomorrowIKnowThat无法使用一种方法处理,除非您使用一些词典来查找确切的单词。

    因为如果您想将comeHEREtomorrow翻译为come HERE tomorrow,则IKnowThat将成为IK now That(甚至是IK now T hat);

    如果您想将IKnowThat翻译为I Know That,则comeHEREtomorrow将成为come H E R E tomorrow

我的解决方案:http://ideone.com/oALyTo(排除非字母和非数字字符)


谢谢你的回答,看起来非常不错。我不打算使用非字母数字字符。我没有使用字典的选项。所以也许“IKnowThat”可以忽略。 - trante
如果哈希符号(#)是第一个字符,是否无法分割? - trante
否则,我将把 $wResult = str_replace("#","# ",$wResult); 添加到结果字符串中。 - trante
如果你只想处理以井号(#)开头的情况,你可以在我的 preg_replace 的第一个参数中添加一个 /^(#)(.)/ 元素。否则,一些 /([^\\p{N}\\p{L}])([\\p{N}\\p{L}])/u/([\\p{N}\\p{L}])([^\\p{N}\\p{L}])/u 将处理所有非字母数字到字母数字(反之亦然)的情况。 - pozs
我不记得投票反对了?它应该得到一个赞,也许我错过了?无论如何,除非被编辑,否则SO不会让我改变我的投票。 - FrankieTheKneeMan

2

/([[:lower:][:digit:]])?([[:upper:]]+)/u应该可以解决问题。

这里使用了/u来处理Unicode字符,([[:upper:]]+)用于匹配一串大写字母。

注意:字母的大小写取决于所使用的字符集。


1

好的,我匹配了所有你的测试用例, 但我仍然认为这不是一个好的解决方案。(测试驱动设计中的少数缺陷之一)。

我采取了稍微不同的方法。而不是试图编写一个正则表达式来描述单词之间的位置应该是什么样子的,我编写了一个正则表达式来查找显然是单词的所有内容,然后将它们合并成一个字符串。

function convert($keyword) {
   $wResult = preg_match_all('/(^I|[[:upper:]]{2,}|[[:upper:]][[:lower:]]*|[[:lower:]]+|\d+|#)/u', $keyword, $matches);
   return implode(' ',$matches[0]);
}

正如您所看到的,这就是我认为合格的单词:

^I                 A capital I at the beginning of the string.  Break point: Icons.
[[:upper:]]{2,}    Consecutive capitals.  Break Point:  WellIKnowThat
[[:upper:]][[:lower:]]*    A single Capital followed by some lower case letters
[[:lower:]]+       A string of lower case letters
\d+                A string of digits
#                  A literal #

它并不完美 - 仍有许多断点。您可以继续完善这些单词定义,但坦率地说,总会有一些您无法捕捉的边缘情况。然后您会逐渐扩展这个正则表达式,直到完全无法管理。您可以尝试使用字典,但最终也会崩溃。对于“whirlwind”或“ITan”,您该怎么办?那是“IT an”还是“I Tan”?就拿这个为例吧。在这里是我试图捕捉一些错误后的结果。它变得如此庞大,而且仍然很容易找到它无法处理的字符串。这个函数涉及程度 - 花费多少时间来教授算法所有世界语言中所有有趣的点?

编辑:经过一些工作,并决定只有在紧随一个大写字母和一个小写字母之后时才能将And作为其自己的单词,我已更新我的回答尝试。

function convert($keyword, $debug = false) {
   $wResult = preg_match_all('/I(?=[[:upper:]][[:lower:]])|[[:upper:]]{2,}|[[:upper:]][[:lower:]]*|[[:lower:]]+|\d+|#/u', $keyword, $matches);
   if($debug){
       var_dump($matches);
       var_dump($matches[0]);
       var_dump(implode(' ',$matches[0]));
   }
   return implode(' ',$matches[0]);
}

我也添加了一些新的测试用例:

 convert("Icons") = "Icons"
 convert("WellIKnowThat") == "Well I Know That"
 convert("ITan") == "I Tan"
 convert("whirlwind") == "whirlwind"

我认为这是今天能达到的最佳水平了。以下是“单词定义”的最终设定,按优先级排序:

  1. 如果大写字母I后面紧跟一个大写字母和一个小写字母,则使用大写字母I:I(?=[[:upper:]][[:lower:]])
  2. 两个或更多连续的大写字母:[[:upper:]]{2,}
  3. 单个大写字母,后面跟尽可能多的小写字母:[[:upper:]][[:lower:]]*
  4. 一个或多个连续的小写字母:[[:lower:]]+
  5. 一个或多个连续数字:\d+
  6. 一个英镑符号:#

我添加了另一个单词定义,一个测试用例,并完善了测试代码。新的单词定义匹配大写字母I的规则,但使用英语中唯一的其他一个字母A。


0
你需要使用Unicode正则表达式: \p{Lu}表示大写字母\p{Li}表示小写字母

因此,你的用法应该像这样: /([\p{Ll}0-9])?([\p{Lu}])/


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