如何用括号和缺失值之间的逗号解析 CSV
How to parse a CSV with commas between parenthesis and missing values
我尝试使用 pyparsing
来解析 CSV:
- 括号(或括号等)之间的逗号: "a(1,2),b" 应该 return 列表 ["a(1,2)","b" ]
- 缺失值: "a,b,,c," 应该 return 列表 ['a','b','','c','']
我找到了解决方案,但似乎 "dirty"。主要是, Optional
里面只有一个可能的原子。我认为可选的应该独立于原子。也就是说,我觉得它应该放在其他地方,例如 delimitedList
可选参数,但在我的反复试验中,这是唯一有效且有意义的地方。它可能在任何可能的原子中,所以我选择了第一个。
另外,我不完全明白 originalTextFor
在做什么,但如果我删除它,它就会停止工作。
工作示例:
import pyparsing as pp
# Function that parses a line of columns separated by commas and returns a list of the columns
def fromLineToRow(line):
sqbrackets_col = pp.Word(pp.printables, excludeChars="[],") | pp.nestedExpr(opener="[",closer="]") # matches "a[1,2]"
parens_col = pp.Word(pp.printables, excludeChars="(),") | pp.nestedExpr(opener="(",closer=")") # matches "a(1,2)"
# In the following line:
# * The "^" means "choose the longest option"
# * The "pp.Optional" can be in any of the expressions separated by "^". I put it only on the first. It's used for when there are missing values
atomic = pp.originalTextFor(pp.Optional(pp.OneOrMore(parens_col))) ^ pp.originalTextFor(pp.OneOrMore(sqbrackets_col))
grammar = pp.delimitedList(atomic)
row = grammar.parseString(line).asList()
return row
file_str = \
"""YEAR,a(2,3),b[3,4]
1960,2.8,3
1961,4,
1962,,1
1963,1.27,3"""
for line in file_str.splitlines():
row = fromLineToRow(line)
print(row)
打印:
['YEAR', 'a(2,3)', 'b[3,4]']
['1960', '2.8', '3']
['1961', '4', '']
['1962', '', '1']
['1963', '1.27', '3']
这样做正确吗?是否有 "cleaner" 方法在第一个原子中使用 Optional
?
您可以使用正则表达式 re
,例如:
>>> import re
>>> re.split(r',\s*(?![^()]*\))', line1)
['a(1,2)', 'b']
>>> re.split(r',\s*(?![^()]*\))', line2)
['a', 'b', '', 'c', '']
import re
with open('44289614.csv') as f:
for line in map(str.strip, f):
l = re.split(',\s*(?![^()[]]*[\)\]])', line)
print(len(l), l)
输出:
3 ['YEAR', 'a(2,3)', 'b[3,4]']
3 ['1960', '2.8', '3']
3 ['1961', '4', '']
3 ['1962', '', '1']
3 ['1963', '1.27', '3']
修改自 this answer。
我也喜欢 csv
模块的 this answer, which suggests modifying the input slightly and using quotechar。
由内而外的工作,我明白了:
# chars not in ()'s or []'s - also disallow ','
non_grouped = pp.Word(pp.printables, excludeChars="[](),")
# grouped expressions in ()'s or []'s
grouped = pp.nestedExpr(opener="[",closer="]") | pp.nestedExpr(opener="(",closer=")")
# use OneOrMore to allow non_grouped and grouped together
atomic = pp.originalTextFor(pp.OneOrMore(non_grouped | grouped))
# or based on your examples, you *could* tighten this up to:
# atomic = pp.originalTextFor(non_grouped + pp.Optional(grouped))
originalTextFor
在匹配表达式的前导和尾随边界内重新组合原始输入文本,returns 一个字符串。如果你忽略它,那么你将得到嵌套字符串列表中的所有子表达式,如 ['a',['2,3']]
。您 可以 通过重复调用 ''.join
来重新加入它们,但这会破坏空格(或使用 ' '.join
,但这有可能引入空格的相反问题) .
要可选化列表的元素,只需在定界列表的定义中这样说:
grammar = pp.delimitedList(pp.Optional(atomic, default=''))
一定要添加默认值,否则空槽将被丢弃。
通过这些更改,我得到:
['YEAR', 'a(2,3)', 'b[3,4]']
['1960', '2.8', '3']
['1961', '4', '']
['1962', '', '1']
['1963', '1.27', '3']
我尝试使用 pyparsing
来解析 CSV:
- 括号(或括号等)之间的逗号: "a(1,2),b" 应该 return 列表 ["a(1,2)","b" ]
- 缺失值: "a,b,,c," 应该 return 列表 ['a','b','','c','']
我找到了解决方案,但似乎 "dirty"。主要是, Optional
里面只有一个可能的原子。我认为可选的应该独立于原子。也就是说,我觉得它应该放在其他地方,例如 delimitedList
可选参数,但在我的反复试验中,这是唯一有效且有意义的地方。它可能在任何可能的原子中,所以我选择了第一个。
另外,我不完全明白 originalTextFor
在做什么,但如果我删除它,它就会停止工作。
工作示例:
import pyparsing as pp
# Function that parses a line of columns separated by commas and returns a list of the columns
def fromLineToRow(line):
sqbrackets_col = pp.Word(pp.printables, excludeChars="[],") | pp.nestedExpr(opener="[",closer="]") # matches "a[1,2]"
parens_col = pp.Word(pp.printables, excludeChars="(),") | pp.nestedExpr(opener="(",closer=")") # matches "a(1,2)"
# In the following line:
# * The "^" means "choose the longest option"
# * The "pp.Optional" can be in any of the expressions separated by "^". I put it only on the first. It's used for when there are missing values
atomic = pp.originalTextFor(pp.Optional(pp.OneOrMore(parens_col))) ^ pp.originalTextFor(pp.OneOrMore(sqbrackets_col))
grammar = pp.delimitedList(atomic)
row = grammar.parseString(line).asList()
return row
file_str = \
"""YEAR,a(2,3),b[3,4]
1960,2.8,3
1961,4,
1962,,1
1963,1.27,3"""
for line in file_str.splitlines():
row = fromLineToRow(line)
print(row)
打印:
['YEAR', 'a(2,3)', 'b[3,4]']
['1960', '2.8', '3']
['1961', '4', '']
['1962', '', '1']
['1963', '1.27', '3']
这样做正确吗?是否有 "cleaner" 方法在第一个原子中使用 Optional
?
您可以使用正则表达式 re
,例如:
>>> import re
>>> re.split(r',\s*(?![^()]*\))', line1)
['a(1,2)', 'b']
>>> re.split(r',\s*(?![^()]*\))', line2)
['a', 'b', '', 'c', '']
import re
with open('44289614.csv') as f:
for line in map(str.strip, f):
l = re.split(',\s*(?![^()[]]*[\)\]])', line)
print(len(l), l)
输出:
3 ['YEAR', 'a(2,3)', 'b[3,4]']
3 ['1960', '2.8', '3']
3 ['1961', '4', '']
3 ['1962', '', '1']
3 ['1963', '1.27', '3']
修改自 this answer。
我也喜欢 csv
模块的 this answer, which suggests modifying the input slightly and using quotechar。
由内而外的工作,我明白了:
# chars not in ()'s or []'s - also disallow ','
non_grouped = pp.Word(pp.printables, excludeChars="[](),")
# grouped expressions in ()'s or []'s
grouped = pp.nestedExpr(opener="[",closer="]") | pp.nestedExpr(opener="(",closer=")")
# use OneOrMore to allow non_grouped and grouped together
atomic = pp.originalTextFor(pp.OneOrMore(non_grouped | grouped))
# or based on your examples, you *could* tighten this up to:
# atomic = pp.originalTextFor(non_grouped + pp.Optional(grouped))
originalTextFor
在匹配表达式的前导和尾随边界内重新组合原始输入文本,returns 一个字符串。如果你忽略它,那么你将得到嵌套字符串列表中的所有子表达式,如 ['a',['2,3']]
。您 可以 通过重复调用 ''.join
来重新加入它们,但这会破坏空格(或使用 ' '.join
,但这有可能引入空格的相反问题) .
要可选化列表的元素,只需在定界列表的定义中这样说:
grammar = pp.delimitedList(pp.Optional(atomic, default=''))
一定要添加默认值,否则空槽将被丢弃。
通过这些更改,我得到:
['YEAR', 'a(2,3)', 'b[3,4]']
['1960', '2.8', '3']
['1961', '4', '']
['1962', '', '1']
['1963', '1.27', '3']