用正则表达式匹配数字——只有数字和逗号
Posted
技术标签:
【中文标题】用正则表达式匹配数字——只有数字和逗号【英文标题】:Matching numbers with regular expressions — only digits and commas 【发布时间】:2011-05-13 20:43:21 【问题描述】:我不知道如何为示例值构建正则表达式:
123,456,789
-12,34
1234
-8
你能帮帮我吗?
【问题讨论】:
“-12,34
”是否有效?在日常使用中,它不会对生成的正则表达式产生巨大影响。
',' == 坚果欧洲'.'
@user278618,如果您编辑您的个人资料,您可以将您的姓名从 user278618
更改为您喜欢的任何名称。
@user278618 请查看我的更新答案。我已经解释了我建议的所有神秘模式,引用了我从中获得所有信息的源材料,然后展示了几个实质性改进,首先是空白和 cmets,然后是命名组,最后是具有完整的语法模式。看看吧!
Jason:不是每个人都使用.
来分隔整数和小数部分...
【参考方案1】:
什么是数字?
我对你的“简单”问题有一个简单的问题:“数字”到底是什么意思?
−0
是数字吗?
你觉得√−1
怎么样?
⅝
或 ⅔
是数字吗?
186,282.42±0.02
miles/second 是一个数字 - 还是两个或三个?
6.02e23
是一个数字吗?
3.141_592_653_589
是一个数字吗? π
或 ℯ
怎么样?还有−2π⁻³ ͥ
?
0.083̄
中有多少个数字?
128.0.0.1
中有多少个数字?
⚄
持有什么号码? ⚂⚃
怎么样?
10,5 mm
中是否有一个数字 - 还是有两个?
∛8³
是一个数字,还是三个?
ↀↀⅮⅭⅭⅬⅫ AUC
代表什么数字,2762 还是 2009?
४५६७
和 ৭৮৯৮
是数字吗?
0377
、0xDEADBEEF
和 0b111101101
呢?
Inf
是一个数字吗?是NaN
?
④②
是数字吗? ⓰
呢?
你觉得㊅
怎么样?
ℵ₀
和 ℵ₁
与数字有什么关系?还是ℝ
、ℚ
和ℂ
?
建议的模式
另外,你熟悉这些模式吗?你能解释一下每种方法的优缺点吗?
/\D/
/^\d+$/
/^\pNd+$/
/^\pN+$/
/^\pNumeric_Value:10$/
/^\PNumeric_Value:NaN+$/
/^-?\d+$/
/^[+-]?\d+$/
/^-?\d+\.?\d*$/
/^-?(?:\d+(?:\.\d*)?|\.\d+)$/
/^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/
/^((\d)(?(?=(\d))|$)(?(?ord$3==1+ord$2)(?1)|$))$/
/^(?:(?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]1,2)[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]1,2)[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]1,2)[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]1,2))$/
/^(?:(?:[0-9a-fA-F]1,2):(?:[0-9a-fA-F]1,2):(?:[0-9a-fA-F]1,2):(?:[0-9a-fA-F]1,2):(?:[0-9a-fA-F]1,2):(?:[0-9a-fA-F]1,2))$/
/^(?:(?:[+-]?)(?:[0123456789]+))$/
/(([+-]?)([0123456789]1,3(?:,?[0123456789]3)*))/
/^(?:(?:[+-]?)(?:[0123456789]1,3(?:,?[0123456789]3)*))$/
/^(?:(?i)(?:[+-]?)(?:(?=[0123456789]|[.])(?:[0123456789]*)(?:(?:[.])(?:[0123456789]0,))?)(?:(?:[E])(?:(?:[+-]?)(?:[0123456789]+))|))$/
/^(?:(?i)(?:[+-]?)(?:(?=[01]|[.])(?:[01]1,3(?:(?:[,])[01]3)*)(?:(?:[.])(?:[01]0,))?)(?:(?:[E])(?:(?:[+-]?)(?:[01]+))|))$/
/^(?:(?i)(?:[+-]?)(?:(?=[0123456789ABCDEF]|[.])(?:[0123456789ABCDEF]1,3(?:(?:[,])[0123456789ABCDEF]3)*)(?:(?:[.])(?:[0123456789ABCDEF]0,))?)(?:(?:[G])(?:(?:[+-]?)(?:[0123456789ABCDEF]+))|))$/
/((?i)([+-]?)((?=[0123456789]|[.])([0123456789]1,3(?:(?:[_,]?)[0123456789]3)*)(?:([.])([0123456789]0,))?)(?:([E])(([+-]?)([0123456789]+))|))/
我怀疑上面的一些模式可能满足您的需求。但我不能告诉你是哪一个或几个——或者,如果没有,提供给你另一个——因为你还没有说出你所说的“数字”是什么意思。
如您所见,有大量的数字可能性:实际上,很可能有 ℵ₁ 的价值。 ☺
建议模式的关键
下面列出的每个编号说明都描述了上面列出的相应编号模式的模式。
-
如果字符串中的任何位置有任何非数字,则匹配,包括换行符之类的空格。
仅当字符串只包含数字时才匹配,可能的例外是尾随换行符。请注意,数字定义为具有通用类别十进制数属性,可用作
\pNd
、\pDecimal_Number
或\pGeneral_Category=Decimal_Number
。这实际上只是那些数字类型类别为十进制的代码点的反映,可以通过\pNumeric_Type=Decimal
获得。
这与大多数正则表达式语言中的 2 相同。 Java 在这里是一个例外,因为它不会将简单的 charclass 转义(如 \w
和 \W
、\d
和 \D
、\s
和 \S
以及 \b
或 \B
映射到适当的 Unicode 属性。这意味着您不能对 Java 中的任何 Unicode 数据使用这八个单字符转义中的任何一个,因为它们仅适用于 ASCII,即使 Java 始终在内部使用 Unicode 字符。
这与 3 略有不同,它不限于十进制数,而是可以是任何数字;那是,
具有\pN
、\pNumber
或\pGeneral_Category=Number
属性的任何字符。其中包括用于罗马数字之类的 \pNl
或 \pLetter_Number
以及用于下标和下标数字、分数和带圆圈的数字的 \pNo
或 \pOther_Number
- 其中包括计数棒。
这仅匹配那些完全由十进制值为 10 的数字组成的字符串,例如 Ⅹ
罗马数字 10 和 ⑩
、⑽
、⒑
、⓾
、❿
、 ➉
和 ➓
。
仅那些包含缺少数值 NaN 的字符的字符串;换句话说,所有字符都必须有一些数值。
仅匹配十进制数字,可以选择与前导连字符减号匹配。
与 7 相同,但现在如果符号是加号而不是减号也可以使用。
查找十进制数字,后面带有可选的 HYPHEN MINUS 和可选的 FULL STOP 加上零个或多个十进制数字。
与 9 相同,但如果点后面有数字,则不需要数字。
针对 C 和许多其他语言的标准浮点表示法,允许使用科学计数法。
以降序查找仅由任何脚本的两位或多位小数组成的数字,例如 987 或 54321。此递归正则表达式包括对 Perl 代码的标注,用于检查前瞻数字是否具有作为当前数字;也就是说,它的序数值大一。可以在 PCRE 中使用 C 函数作为标注来执行此操作。
这会查找有效范围内具有四个十进制数字的有效 IPv4 地址,例如 128.0.0.1 或 255.255.255.240,但不是 999.999.999.999。
这会查找有效的 MAC 地址,即六个冒号分隔的两个 ASCII 十六进制数字对。
这会查找 ASCII 范围内带有可选前导符号的整数。这是匹配 ASCII 整数的正常模式。
这类似于 15,只是它需要一个逗号来分隔三个一组。
这类似于 15,只是用于分隔组的逗号现在是可选的。
这是在 ASCII 中匹配 C 样式浮点数的常规模式。
这类似于 18,但需要逗号来分隔 3 组,并且以 2 为底,而不是以 10 为底。
这类似于 19,但是是十六进制的。请注意,可选指数现在由 G 而不是 E 表示,因为 E 是有效的十六进制数字。
这将检查字符串是否包含 C 样式的浮点数,但在它们之间每三位逗号或下划线 (LOW LINE) 有一个可选的分组分隔符。它还会将该字符串存储到\1
捕获组中,在匹配成功后以$1
的形式提供。
来源和可维护性
模式编号 1、2、7–11 来自问题“如何验证输入?”中的 Perl 常见问题列表的前一个版本。该部分已替换为使用Regexp::Common 模块的建议,由Abigail 和Damian Conway 编写。原始模式仍然可以在 Perl Cookbook 的配方 2.1 中找到,“检查字符串是否为有效数字”,可以为令人眼花缭乱的多种语言找到解决方案,包括 ada、common lisp、groovy、guile 、haskell、java、merd、ocaml、php、pike、python、rexx、ruby 和 tcl 在the PLEAC project。
模式 12 可以更清晰地重写
m
^
(
( \d )
(?(?= ( \d ) ) | $ )
(?(? ord $3 == 1 + ord $2 ) (?1) | $ )
)
$
x
它使用regex recursion,它在许多模式引擎中都可以找到,包括 Perl 和所有 PCRE 派生的语言。但它也使用嵌入式代码标注作为第二个条件模式的测试;据我所知,代码标注仅在 Perl 和 PCRE 中可用。
模式 13-21 源自前面提到的 Regexp::Common 模块。请注意,为简洁起见,这些都是在没有您在生产代码中肯定需要的空格和 cmets 的情况下编写的。这是/x
模式下的样子:
$real_rx = qr ( # start $1 to hold entire pattern
( [+-]? ) # optional leading sign, captured into $2
( # start $3
(?= # look ahead for what next char *will* be
[0123456789] # EITHER: an ASCII digit
| [.] # OR ELSE: a dot
) # end look ahead
( # start $4
[0123456789]1,3 # 1-3 ASCII digits to start the number
(?: # then optionally followed by
(?: [_,]? ) # an optional grouping separator of comma or underscore
[0123456789]3 # followed by exactly three ASCII digits
) * # repeated any number of times
) # end $4
(?: # begin optional cluster
( [.] ) # required literal dot in $5
( [0123456789]0, ) # then optional ASCII digits in $6
) ? # end optional cluster
) # end $3
(?: # begin cluster group
( [E] ) # base-10 exponent into $7
( # exponent number into $8
( [+-] ? ) # optional sign for exponent into $9
( [0123456789] + ) # one or more ASCII digits into $10
) # end $8
| # or else nothing at all
) # end cluster group
) xi; # end $1 and whole pattern, enabling /x and /i modes
从软件工程的角度来看,上面/x
模式版本中使用的样式仍然存在几个问题。首先,有大量的代码重复,你看到相同的[0123456789]
;如果其中一个序列不小心遗漏了一个数字,会发生什么?其次,您依赖于必须计算的位置参数。这意味着您可能会写如下内容:
(
$real_number, # $1
$real_number_sign, # $2
$pre_exponent_part, # $3
$pre_decimal_point, # $4
$decimal_point, # $5
$post_decimal_point, # $6
$exponent_indicator, # $7
$exponent_number, # $8
$exponent_sign, # $9
$exponent_digits, # $10
) = ($string =~ /$real_rx/);
坦率地说,这很可恶!很容易弄错编号,很难记住符号名称在哪里,而且写起来很乏味,特别是如果你不需要所有这些部分。将其重写为使用命名组,而不仅仅是编号组。同样,我将对变量使用 Perl 语法,但 Pattern 的内容应该适用于任何支持命名组的地方。
use 5.010; # Perl got named patterns in 5.10
$real_rx = qr
(?<real_number>
# optional leading sign
(?<real_number_sign> [+-]? )
(?<pre_exponent_part>
(?= # look ahead for what next char *will* be
[0123456789] # EITHER: an ASCII digit
| [.] # OR ELSE: a dot
) # end look ahead
(?<pre_decimal_point>
[0123456789]1,3 # 1-3 ASCII digits to start the number
(?: # then optionally followed by
(?: [_,]? ) # an optional grouping separator of comma or underscore
[0123456789]3 # followed by exactly three ASCII digits
) * # repeated any number of times
) # end <pre_decimal_part>
(?: # begin optional anon cluster
(?<decimal_point> [.] ) # required literal dot
(?<post_decimal_point>
[0123456789]0, )
) ? # end optional anon cluster
) # end <pre_exponent_part>
# begin anon cluster group:
(?:
(?<exponent_indicator> [E] ) # base-10 exponent
(?<exponent_number> # exponent number
(?<exponent_sign> [+-] ? )
(?<exponent_digits> [0123456789] + )
) # end <exponent_number>
| # or else nothing at all
) # end anon cluster group
) # end <real_number>
xi;
现在抽象被命名了,这很有帮助。您可以按名称将组拉出,并且只需要您关心的组。例如:
if ($string =~ /$real_rx/)
($pre_exponent, $exponent_number) =
@+ qw< pre_exponent exponent_number > ;
要使这种模式更易于维护,还有一件事要做。问题是仍然有太多的重复,这意味着它在一个地方很容易改变,而在另一个地方却没有。如果你在做 McCabe 分析,你会说它的复杂度指标太高了。我们大多数人只会说它太缩进了。这让人很难跟上。为了解决所有这些问题,我们需要一种“语法模式”,它具有定义块来创建命名抽象,然后我们将其视为稍后在匹配中的子例程调用。
use 5.010; # Perl first got regex subs in v5.10
$real__rx = qr
^ # anchor to front
(?&real_number) # call &real_number regex sub
$ # either at end or before final newline
##################################################
# the rest is definition only; think of ##
# each named buffer as declaring a subroutine ##
# by that name ##
##################################################
(?(DEFINE)
(?<real_number>
(?&mantissa)
(?&abscissa) ?
)
(?<abscissa>
(?&exponent_indicator)
(?&exponent)
)
(?<exponent>
(&?sign) ?
(?&a_digit) +
)
(?<mantissa>
# expecting either of these....
(?= (?&a_digit)
| (?&point)
)
(?&a_digit) 1,3
(?: (?&digit_separator) ?
(?&a_digit) 3
) *
(?: (?&point)
(?&a_digit) *
) ?
)
(?<point> [.] )
(?<sign> [+-] )
(?<digit_separator> [_,] )
(?<exponent_indicator> [Ee] )
(?<a_digit> [0-9] )
) # end DEFINE block
x;
看看语法模式比原来的行嘈杂模式好得多?获得正确的语法也容易得多:我输入了它,甚至没有一个需要更正的正则表达式语法错误。 (好吧,我输入了所有其他的也没有任何语法错误,但我已经这样做了一段时间。:)
语法模式看起来更像是 BNF,而不是人们讨厌的丑陋的旧正则表达式。它们更容易阅读、编写和维护。所以我们不要再有丑陋的模式了,好吗?
【讨论】:
@ThiefMaster,我希望现在有了详细的解释来回答我原来的非修辞问题,你不只是微笑,而是笑得合不拢嘴! :) @ʞɔɐɯɹoↃɔW sǝɯɐſ:不知道如何使用 Microsoft 来完成,但在最新的 Perl 版本中完全是微不足道的:perl -Mv5.14 -MUnicode::UCD=num -CSA -E 'say "$_ is ",num($_) for @ARGV' "४५६७" "໓໑໔໑໕໙"
打印出 ४५६७ is 4567 ໓໑໔໑໕໙ is 314159
。这意味着您需要为数字字符串做的所有事情就是使用/(\d+)/
获取它们并在其上调用Unicode::UCD::num 函数。我不知道微软的国际支持有多好,但它必须比 Java 的更好。
很好的答案——但你有 21 种模式和 20 种解释。 :)
我有一个绝妙的主意,让我们来辩论一下是否有 ℵ₁ 个有意义的数字正则表达式或只有 ℵ₀ 个。
从一粒沙中看宇宙 - 正则表达式、可读性、框架问题、稳健性、清晰性、启发性 - 一小时内永恒 - 这个答案压缩了多少智慧?【参考方案2】:
试试这个:
boxValue = boxValue.replace(/[^0-9\.\,]/g, "");
此 RegEx 将仅匹配数字、点和逗号。
【讨论】:
你为什么要发布这个?它与已经存在的高度赞成的答案没有什么不同。【参考方案3】:^[-+]?(\d1,3)(,?(?1))*$
Debuggex Demo
那是什么?!
^
标记字符串的开始
[-+]?
允许在字符串的开头紧跟 minus 或 plus
(\d1,3)
连续匹配至少一个和最多三个 (1,3
) 数字(\d
- 通常为 [0-9]
)并将它们分组(括号 (...)
构建组)作为第一组
(,?(?1))*
好的...让我们分解一下
(...)
建立另一个小组(不那么重要)
,?
在第一个数字序列之后匹配逗号(如果存在)
(?1)
再次匹配第一组的模式(记住(\d1,3)
);用文字表示:此时,表达式匹配一个符号(加/减/无),后跟一个数字序列,可能后跟一个逗号,再跟另一个数字序列。
(,?(?1))*
,*
尽可能频繁地重复第二部分(逗号和序列)
$
终于匹配到了字符串的结尾
这样的表达式的优点是,避免在你的表达式中一次又一次地定义相同的模式......好吧,缺点有时是复杂性:-/
【讨论】:
这不处理浮点数 - 无论如何都不用点作为小数。 是的,这不是目标……它与请求的输入相匹配 它也匹配未被请求的1,2,3,4,5
。 regex101.com/r/T3m0iQ/1 您的模式在用作千位分隔符的逗号和用作十进制字符的逗号之间没有区别。验证不是很强。【参考方案4】:
举例:
^(-)?([,0-9])+$
它应该工作。用你想要的任何语言实现它。
【讨论】:
【参考方案5】:由于这个问题已在四年后重新提出,我想提出不同的看法。由于有人花费大量时间使用正则表达式,我的观点是:
A.如果可能,不要使用正则表达式来验证数字
如果可能,请使用您的语言。可能有一些函数可以帮助您确定字符串包含的值是否为有效数字。话虽如此,如果您接受多种格式(逗号等),您可能别无选择。
B.不要手动编写正则表达式来验证数字范围
编写正则表达式来匹配给定范围内的数字是很困难的。即使将正则表达式写入match a number between 1 and 10,您也可能会出错。 一旦有了数字范围的正则表达式,就很难调试。首先,看起来很糟糕。其次,你怎么能确定它匹配你想要的所有值,而不匹配任何你不想要的值?坦率地说,如果你自己一个人,没有同行在你的肩膀上看,你不能。最好的调试技术是以编程方式输出整个范围的数字,并根据正则表达式检查它们。 幸运的是,有一些工具可以自动为数字范围生成正则表达式。C.明智地使用正则表达式能量:使用工具
在给定范围内匹配数字是一个已经解决的问题。您无需尝试重新发明***。这是一个可以通过程序机械地解决的问题,并且可以保证没有错误。好好利用这次搭便车吧。 解决一个数字范围的正则表达式对于学习目的可能会很有趣几次。除此之外,如果您有精力投资于提高您的正则表达式技能,请将其花在一些有用的事情上,例如加深您对regex greed 的理解,阅读Unicode regex,玩零宽度匹配或递归,阅读@ 987654324@ 并发现巧妙的技巧,例如如何 exclude certain patterns from a regex match... 或阅读经典著作,例如 Matering Regular Expressions, 3rd Ed 或 The Regular Expressions Cookbook, 2nd Ed 。对于工具,你可以使用:
在线:Regex_for_range 离线:我知道的唯一一个是RegexMagic
(不是免费的),作者是正则表达式大师 Jan Goyvaerts。这是他的初学者正则表达式产品,我记得它有很多选项可以在给定范围内生成数字,以及其他功能。
如果条件太复杂,自动生成两个范围...然后用交替运算符|
连接它们
D.练习:为问题中的规范构建正则表达式
这些规格相当广泛...但不一定含糊不清。让我们再看看样本值:
123,456,789
-12,34
1234
-8
前两个值如何关联?在第一个中,逗号匹配三的幂组。在第二种情况下,它可能与欧陆式数字格式中的小数点匹配。这并不意味着我们应该在任何地方都允许数字,例如1,2,3,44
。同样的道理,我们不应该有限制性。例如,接受答案中的正则表达式将不符合要求之一,123,456,789
(请参阅demo)。
我们如何构建正则表达式以匹配规范?
让我们在^
和 $
之间锚定表达式以避免子匹配
让我们允许一个可选的减号:-?
让我们在交替(?:this|that)
的两侧匹配两种类型的数字:
左侧是欧式数字,小数部分可选逗号:[1-9][0-9]*(?:,[0-9]+)?
在右侧,带有千位分隔符的数字:[1-9][0-9]1,2(?:,[0-9]3)+
完整的正则表达式:
^-?(?:[1-9][0-9]*(?:,[0-9]+)?|[1-9][0-9]1,2(?:,[0-9]3)+)$
见demo。
此正则表达式不允许以0
开头的欧式数字,例如0,12
。这是一个功能,而不是一个错误。为了匹配这些,稍微调整一下就可以了:
^-?(?:(?:0|[1-9][0-9]*)(?:,[0-9]+)?|[1-9][0-9]1,2(?:,[0-9]3)+)$
见demo。
【讨论】:
【参考方案6】:在 java 中,您可以使用 java.util.Scanner
及其 useLocale
方法
Scanner myScanner = new Scanner(input).useLocale( myLocale)
isADouble = myScanner.hasNextDouble()
【讨论】:
【参考方案7】:^-? # start of line, optional -
(\d+ # any number of digits
|(\d1,3(,\d3)*)) # or digits followed by , and three digits
((,|\.)\d+)? # optional comma or period decimal point and more digits
$ # end of line
【讨论】:
为什么不将(,|\.)
减少到[,.]
?【参考方案8】:
试试这个:
^-?\d1,3(,\d3)*(\.\d\d)?$|^\.\d\d$
允许:
1
12
.99
12.34
-18.34
12,345.67
999,999,999,999,999.99
【讨论】:
【参考方案9】:如果您只想允许数字和逗号,^[-,0-9]+$
是您的正则表达式。如果您还想允许空格,请使用^[-,0-9 ]+$
。
但是,如果您想允许正确的数字,最好使用以下内容:
^([-+] ?)?[0-9]+(,[0-9]+)?$
或者干脆使用.net's number parser(对于各种NumberStyles,请参阅MSDN):
try
double.Parse(yourString, NumberStyle.Number);
catch(FormatException ex)
/* Number is not in an accepted format */
【讨论】:
是的,看看我的补充 - 他的问题没有提到他是否想允许“仅允许数字和逗号”或者他是否想检查有效数字。 这太恶心了。我认为它甚至不能回答他的问题。再说一次,@user278618 提出的问题与@user278618 提供的示例不一致。我在自己的回答中给出了许多解决方案,没有一个有你的很多很多问题中的任何一个。我的/^(?:(?:[+-]?)(?:[0123456789]1,3(?:,?[0123456789]3)*))$/
可能会满足他的需求,但由于措辞不精确和冲突,无法判断。但我的肯定比你的要好得多!
我只能猜到他到底想要什么,因为他的问题很不清楚。【参考方案10】:
试试这个:
^-?[\d\,]+$
它将允许可选的-
作为第一个字符,然后是逗号和数字的任意组合。
【讨论】:
正如 Paul 在之前的评论中指出的那样,这是一个明显较弱的验证。以上是关于用正则表达式匹配数字——只有数字和逗号的主要内容,如果未能解决你的问题,请参考以下文章