【问题标题】:Regex: Robust way to handle string numbers正则表达式:处理字符串数字的稳健方法
【发布时间】:2018-07-11 01:59:44
【问题描述】:

编辑:

我不知道这是否可以在正则表达式中重新创建,@Paul Crovella 指出这可能不适合解决问题,但只是为了好玩,我想做这样的事情:

  1. 从右到左开始。匹配点或逗号的第一个字符/(?<seperator>[.,])\d+$/
  2. 重置指针并递归捕获每个数字直到十进制字符(不捕获除数字以外的任何内容)/(?<number>(?:\d+[^\1])+\d+)/
  3. 获取小数位/(?<decimal)\d+(?<=\1)/

附加规则

  • 如果只有一个 [.,] 则为小数点
  • 如果只有其中一个 [.,\h] 则为百/千分隔符
  • 如果多次找到第一个捕获的非数字字符,则为百/千分隔符
  • 百/千总是相同的,所以应该可以编写一个递归的前瞻,它总是在字符处停止并用数字“填充”一个组

原文:

我正在用 PHP 构建一个在单位之间转换的类。到目前为止,我已经完成了所有工作,现在我正在尝试创建一种强大的方法来将输入字符串转换为浮点数。

这是我的班级应该处理的一些测试字符串:

123456789
1234567.89
1234567,89
1,234,567.89
1.234.567,89
123 456 789
1 234 567.89
1 234 567,89

为了使这个可行,我必须做出一些假设:

  • 字符串可以是整数
  • 字符串可以包含由[.,] 分隔的小数位
  • 字符串可以分组(按数百/千),以[.,\h] 分隔
  • 分隔符是一致的,但彼此不同

我认为最好的“做一次,做对”的方法是用正则表达式来解决这个问题。

首先你必须收集第一个分隔符

/^\d+(?<s>[.,\h])/

然后你必须重置指针并反向引用符号

/^(?<b>(\d+)${s}(\d+))/

我不想在实际组中使用分隔符,但我不知道如何实现。

下一步是为小数组匹配[^${s}](?&lt;d&gt;\d+)

最后将两个数相加

return (float) $matches['b'] . '.' . $matches['d'];

我想出了一些解决方案,但没有一个是完全正确的。我希望社区提供一些意见。请描述每个区块的作用,以便我向您学习。

最好的问候。

附:添加解析这些的可能性的奖励积分

123^2
123^-2
123 ^2
123^ 2
123²
123³

前四个我可以做到,但对于后两个,我正在寻找一种将上标替换为数字的方法(我也可以使用 str_replace 做到这一点,但我知道这在正则表达式本身中应该是可能的)。

【问题讨论】:

  • 那么你想如何解决像123,456这样的模糊字符串,可以解释为123456.0123.456?还是所有输入字符串的小数点都不超过 2 位?
  • 假设小数始终是小数点后 2 位,我认为这可行。 3v4l.org/nclkR
  • php.net/manual/en/numberformatter.parse.php是这个问题比较合适的解决方案
  • 这是个好问题!我认为最安全的选择是,不传递额外的函数参数,只是假设如果只有一个点或逗号,它应该是小数点。
  • 加入假设和模棱两可是“稳健”解决方案的反面。

标签: php regex pcre


【解决方案1】:

^(?|(\d{1,3}(?=([.,\h])?)(?:\2\d{3})*)(?:(?!\2)[.,](\d*))|(\d+)()(?:[.,](\d*))?|()()[.,](\d+))$

https://regex101.com/r/ZMJEmb/1

整数在第 1 组。
小数在第 3 组。

在比赛结束后在第 1 组全局替换 \D,以去除数千个分隔符。

 ^                             # BOS
 (?|                           # Branch Reset

                                    # Form D,DDD,DDD.dd
      (                             # (1 start), Whole number
           \d{1,3} 
           (?=
                ( [.,\h] )?                   # (2), Thousands seperator
           )
           (?:
                \2 
                \d{3} 
           )*
      )                             # (1 end)
      (?:
           (?! \2 )
           [.,]                          # This form requires at least a fractional separator
           ( \d* )                       # (3), Fractional number, optional
      )
   |  

      ( \d+ )                       # (1), Whole number
      ( )                           # (2), Thousands seperator N/A
      (?:
           [.,] 
           ( \d* )                       # (3), Fractional number, optional
      )?
   |                              # or, Form .dd
      ( )                           # (1), Whole number N/A
      ( )                           # (2), Thousands seperator N/A
      [.,] 
      ( \d+ )                       # (3), Fractional number
 )
 $                             # EOS

【讨论】:

  • 嘿,谢谢你的努力。有没有办法在没有分隔符的情况下获得数字?也许有递归函数?替换\D是我想避免的额外步骤。
  • 这个正则表达式似乎不匹配 "1,234,567" 应该匹配吗? @user3462116
  • @user3462116 - 可以使用 Capture Collections 在 Dot-net 引擎中完成。但在任何其他引擎中都没有。至于\D 的额外步骤,这里的正则表达式在一个正则表达式中有 12 个步骤,我认为它不会受到伤害。
  • @Julio - 当然,这可以解释。但是,如果您查看注释为# This form requires at least a fractional separator 的行,则将其放在那里以区分千位分隔符123,456 和小数分隔符123,456。一切皆有可能……
  • @Julio '1,234,567' 应解释为 123456789。我会尝试根据我目前收到的意见提出一个新的解决方案,如果你们有兴趣,我会在这里发布。谢谢你的工作@sln
【解决方案2】:

如果您想将“333.333”视为小数分隔符,请使用:

^(\d{0,3}(?=([.,](?!\d+$)| |))(?:\2\d{3})*)(?:[,.](\d*))?$

https://regex101.com/r/TOrxA0/4/

^
  (
    \d{0,3} # Match up to 3 digits so we can...
    (?=([.,](?!\d+$)| |)) # get first separator. Will be used down here:
    (?:\2\d{3})* # get group of 3 digits with previous separator, greedy
  ) # first block
  (?:
      [,.](\d*) # decimal separator + digits
  )? # last block
$

如果您想将“333.333”视为数字分隔符,请使用:

^(\d{0,3}(?=([ .,]|))(?:\2\d{3})*)(?:[,.](\d*))?$

见:https://regex101.com/r/BsaARo/3/

^
 (
  \d{0,3} # Match up to 3 digits so we can...
  (?=([ .,]|)) # get first separator. Will be used down here:
    (?!\d+$)   # Optional: is just one separator is present, it will be a decimal point
  (?:\2\d{3})* # get group of 3 digits with previous separator, greedy
 ) # First block
 (?:
   [,.](\d*) # decimal separator + digits
 )? # Last block
$ 

编辑:将一些 \d\d​​\d 替换为 \d{3}

【讨论】:

  • 嘿,谢谢你的努力。有没有办法在没有分隔符的情况下获得数字?也许有递归函数?替换\D是我想避免的额外步骤。
  • 对不起,我不能因为这个:(\2\d{3})* 该块重复(分隔符 + 3 个数字)多次。如果我想捕获所有这些数字,捕获组必须在星之外,这将获取分隔符。虽然正则表达式功能强大,但如果避免使正则表达式过于复杂,使用几个步骤也不是一个坏主意。它有助于维护。
  • @user3462116 On 可能会回答,第一个正则表达式将匹配 1,234,567 作为整数和 123,456 作为您似乎需要的小数。第二个正则表达式将匹配 1,234,567 和 123,456 作为整数。无论哪种情况,您都可能需要在第一部分删除 \D。
  • 您的第一个正则表达式似乎完全符合我的要求。谢谢你!只是一些想法:-(?=([.,](?!\d+$)| |)) 匹配 [.,][ ](更喜欢 [\h])或者什么都不匹配。为什么不在第一个字符列表中包含空格并添加量词,这样您就不必匹配任何内容? - \d+$ 感觉很奇怪。也许只有我一个人,但是在一个组中包含一个字符串结尾字符是一种好习惯吗? - \d{3} 它们被 2 分隔的系统呢? - (?:[,.](\d*)) 可以是 ^\2 而不是 [,.]
  • 没问题,(?=([.,](?!\d+$)| |)) 你可以用\h 代替" ",这似乎是个好主意。在那里,您需要找到nothing,因为稍后您将使用该匹配组(重复多次)作为正则表达式的其余部分。因此,为了匹配没有分隔符的数字,如 123456789,您需要查找 (123nothing) 以便稍后匹配 456nothing 和 789nothing。此外,此(?!\d+$) 用于将 123,456 视为十进制数。您基本上不想在第一部分找到最多 3 位数字 + [.,] + 一些数字,因为如果是这样,它将是整数。您稍后将它们匹配为小数
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-04-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多