pyparsing - 用千位分隔符解析数字
pyparsing - Parse numbers with thousand separators
所以我正在制作一个解析器,我发现了一个问题。事实上,为了解析数字,我有:
from pyparsing import Word, nums
n = Word(nums)
这适用于没有千位分隔符的数字。例如,n.parseString("1000", parseAll=True)
returns (['1000'], {})
因此有效。
但是,当我添加千位分隔符时它不起作用。事实上,n.parseString("1,000", parseAll=True)
提高了 pyparsing.ParseException: Expected end of text, found ',' (at char 1), (line:1, col:2)
.
如何解析带有千位分隔符的数字?我不只是想忽略逗号(例如,n.parseString("1,00", parseAll=True)
应该 return 一个错误,因为它不是数字)。
当您首先处理字符串时,您可以很好地对其使用正则表达式以确保它确实是一个数字(包括 thousand sep)。如果是,替换每个逗号并将其提供给解析器:
import re
from pyparsing import Word, nums
n = Word(nums)
def is_number(number):
rx = re.compile(r'^-?\d+(?:,\d{3})*$')
if rx.match(number):
return number.replace(",", "")
raise ValueError
try:
number = is_number("10,000,000")
print(n.parseString(number, parseAll=True))
except ValueError:
print("Not a number")
有了这个,例如1,00
将导致 Not a number
,请参阅 regex101.com.
上的表达式演示
我不太明白你所说的“数字有千位分隔符”是什么意思。
无论如何,使用 pyparsing 你应该定义你想要解析的模式。
在第一个示例中,pyparse 运行良好只是因为您将 n 定义为一个数字,所以:
n = Word(nums)
print(n.parseString("1000", parseAll=True))
['1000']
因此,如果要解析“1,000”或“1,00”,则应将 n 定义为:
n = Word(nums) + ',' + Word(nums)
print(n.parseString("1,000", parseAll=True))
['1', ',', '000']
print(n.parseString("1,00", parseAll=True))
['1', ',', '00']
我也想出了一个正则表达式解决方案,有点晚了:
from pyparsing import Word, nums
import re
n = Word(nums)
def parseNumber(x):
parseable = re.sub('[,][0-9]{3}', lambda y: y.group()[1:], x)
return n.parseString(parseable, parseAll=True)
print(parseNumber("1,000,123"))
纯 pyparsing 方法将使用 Combine
来包装一系列代表您在正则表达式中看到的不同字段的 pyparsing 表达式:
import pyparsing as pp
int_with_thousands_separators = pp.Combine(pp.Optional("-")
+ pp.Word(pp.nums, max=3)
+ ("," + pp.Word(pp.nums, exact=3))[...])
我发现像这样构建数值表达式会导致解析时间慢得多,因为所有这些单独的部分都是独立解析的,具有多个内部函数和方法调用(这是 [=32= 中真正的性能杀手) ]).因此,您可以使用 Regex
:
将其替换为表达式
# more efficient parsing with a Regex
int_with_thousands_separators = pp.Regex(r"-?\d{1,3}(,\d{3})*")
您也可以使用 Jan 发布的代码,并将编译后的正则表达式传递给正则表达式构造函数。
要将解析时转换为 int,请添加去除逗号的解析操作。
# add parse action to convert to int, after stripping ','s
int_with_thousands_separators.addParseAction(
lambda t: int(t[0].replace(",", "")))
我喜欢使用 runTests
来检查像这样的小表达式 - 编写一系列测试字符串很容易,输出显示解析结果或带有解析失败位置的注释输入字符串。 ("1,00"
作为故意错误包括在内,以证明 runTests
输出的错误。)
int_with_thousands_separators.runTests("""\
1
# invalid value
1,00
1,000
-3,000,100
""")
如果要解析实数,请添加部分以表示尾随小数点和后面的数字。
real_with_thousands_separators = pp.Combine(pp.Optional("-")
+ pp.Word(pp.nums, max=3)
+ ("," + pp.Word(pp.nums, exact=3))[...]
+ "." + pp.Word(pp.nums))
# more efficient parsing with a Regex
real_with_thousands_separators = pp.Regex(r"-?\d{1,3}(,\d{3})*\.\d+")
# add parse action to convert to float, after stripping ','s
real_with_thousands_separators.addParseAction(
lambda t: float(t[0].replace(",", "")))
real_with_thousands_separators.runTests("""\
# invalid values
1
1,00
1,000
-3,000,100
1.
# valid values
1.732
-273.15
""")
所以我正在制作一个解析器,我发现了一个问题。事实上,为了解析数字,我有:
from pyparsing import Word, nums
n = Word(nums)
这适用于没有千位分隔符的数字。例如,n.parseString("1000", parseAll=True)
returns (['1000'], {})
因此有效。
但是,当我添加千位分隔符时它不起作用。事实上,n.parseString("1,000", parseAll=True)
提高了 pyparsing.ParseException: Expected end of text, found ',' (at char 1), (line:1, col:2)
.
如何解析带有千位分隔符的数字?我不只是想忽略逗号(例如,n.parseString("1,00", parseAll=True)
应该 return 一个错误,因为它不是数字)。
当您首先处理字符串时,您可以很好地对其使用正则表达式以确保它确实是一个数字(包括 thousand sep)。如果是,替换每个逗号并将其提供给解析器:
import re
from pyparsing import Word, nums
n = Word(nums)
def is_number(number):
rx = re.compile(r'^-?\d+(?:,\d{3})*$')
if rx.match(number):
return number.replace(",", "")
raise ValueError
try:
number = is_number("10,000,000")
print(n.parseString(number, parseAll=True))
except ValueError:
print("Not a number")
有了这个,例如1,00
将导致 Not a number
,请参阅 regex101.com.
我不太明白你所说的“数字有千位分隔符”是什么意思。
无论如何,使用 pyparsing 你应该定义你想要解析的模式。
在第一个示例中,pyparse 运行良好只是因为您将 n 定义为一个数字,所以:
n = Word(nums)
print(n.parseString("1000", parseAll=True))
['1000']
因此,如果要解析“1,000”或“1,00”,则应将 n 定义为:
n = Word(nums) + ',' + Word(nums)
print(n.parseString("1,000", parseAll=True))
['1', ',', '000']
print(n.parseString("1,00", parseAll=True))
['1', ',', '00']
我也想出了一个正则表达式解决方案,有点晚了:
from pyparsing import Word, nums
import re
n = Word(nums)
def parseNumber(x):
parseable = re.sub('[,][0-9]{3}', lambda y: y.group()[1:], x)
return n.parseString(parseable, parseAll=True)
print(parseNumber("1,000,123"))
纯 pyparsing 方法将使用 Combine
来包装一系列代表您在正则表达式中看到的不同字段的 pyparsing 表达式:
import pyparsing as pp
int_with_thousands_separators = pp.Combine(pp.Optional("-")
+ pp.Word(pp.nums, max=3)
+ ("," + pp.Word(pp.nums, exact=3))[...])
我发现像这样构建数值表达式会导致解析时间慢得多,因为所有这些单独的部分都是独立解析的,具有多个内部函数和方法调用(这是 [=32= 中真正的性能杀手) ]).因此,您可以使用 Regex
:
# more efficient parsing with a Regex
int_with_thousands_separators = pp.Regex(r"-?\d{1,3}(,\d{3})*")
您也可以使用 Jan 发布的代码,并将编译后的正则表达式传递给正则表达式构造函数。
要将解析时转换为 int,请添加去除逗号的解析操作。
# add parse action to convert to int, after stripping ','s
int_with_thousands_separators.addParseAction(
lambda t: int(t[0].replace(",", "")))
我喜欢使用 runTests
来检查像这样的小表达式 - 编写一系列测试字符串很容易,输出显示解析结果或带有解析失败位置的注释输入字符串。 ("1,00"
作为故意错误包括在内,以证明 runTests
输出的错误。)
int_with_thousands_separators.runTests("""\
1
# invalid value
1,00
1,000
-3,000,100
""")
如果要解析实数,请添加部分以表示尾随小数点和后面的数字。
real_with_thousands_separators = pp.Combine(pp.Optional("-")
+ pp.Word(pp.nums, max=3)
+ ("," + pp.Word(pp.nums, exact=3))[...]
+ "." + pp.Word(pp.nums))
# more efficient parsing with a Regex
real_with_thousands_separators = pp.Regex(r"-?\d{1,3}(,\d{3})*\.\d+")
# add parse action to convert to float, after stripping ','s
real_with_thousands_separators.addParseAction(
lambda t: float(t[0].replace(",", "")))
real_with_thousands_separators.runTests("""\
# invalid values
1
1,00
1,000
-3,000,100
1.
# valid values
1.732
-273.15
""")