正在用 Python 解析半非结构化文本文件。保留一些行并旋转其他行
Parsing semi-unstructured text file with Python. Keeping some rows and pivot others
我有一个结构合理的文件,但后续事件(一个或多个)的日期只打印一次。在下一个日期出现之前,我不知道如何读取文件、识别日期并将它们映射到随后的每个游戏结果。
数据如下所示:
Sa 19.11.2016
FC Tuggen
FC Basel 1893 II
1
3
SC Cham
FC Zürich II
0
1
SC Kriens
FC Köniz
3
1
Sa 26.11.2016
FC Bavois
SC Brühl
1
4
Mi 30.11.2016
FC Zürich II
FC Basel 1893 II
2
2
每个日期可以应用于一个或多个游戏结果。我试过通读文件并查找日期
keys = []
for line in d:
if line[0:2] in ('Sa','So','Mo','Di','Mi','Do','Fr'):
keys.append(line[2:-1].strip())
但是在下一个日期到来之前,我不知道如何为接下来的比赛指定相同的日期。为此,我尝试了 enumerate()、xrange() 等的各种组合。enumerate() 没有像我尝试的那样工作,因为我只能在每个日期之后添加第一个游戏。
我想要的输出如下所示,或者一个 defaultdict(list),键作为日期,数组元素作为小词典:
Sa 19.11.2016,FC Tuggen,FC Basel 1893 II,1,3
Sa 19.11.2016,SC Cham,FC Zürich II,0,1
Sa 19.11.2016,SC Kriens,FC Köniz,3,1
Sa 26.11.2016,FC Bavois,SC Brühl,1,4
Mi 30.11.2016,FC Zürich II,FC Basel 1893 II,2,2
# vim: set fileencoding=utf-8 :
def parse(it):
record, date = [], ""
for line in it:
line = line.strip()
if not line:
if len(record) > 1:
yield ",".join(record)
if date:
record = [date]
continue
if line.startswith(('Sa', 'So', 'Mo', 'Di', 'Mi', 'Do', 'Fr')):
if len(record) > 1:
yield ",".join(record)
date = line
record = [date]
continue
record.append(line)
if len(record) > 1:
yield ",".join(record)
if __name__ == "__main__":
input = """
Sa 19.11.2016
FC Tuggen
FC Basel 1893 II
1
3
SC Cham
FC Zürich II
0
1
SC Kriens
FC Köniz
3
1
Sa 26.11.2016
FC Bavois
SC Brühl
1
4
Mi 30.11.2016
FC Zürich II
FC Basel 1893 II
2
2
"""
output = """Sa 19.11.2016,FC Tuggen,FC Basel 1893 II,1,3
Sa 19.11.2016,SC Cham,FC Zürich II,0,1
Sa 19.11.2016,SC Kriens,FC Köniz,3,1
Sa 26.11.2016,FC Bavois,SC Brühl,1,4
Mi 30.11.2016,FC Zürich II,FC Basel 1893 II,2,2
"""
for record, expected in zip(parse(input.splitlines()), output.splitlines()):
assert record == expected
编辑:好的,误读了问题 earlier.This 一个可以处理所有提供的数据,我只是不想在 SO 的代码中占用所有 space。
首先,我按行拆分文本。通过每一行,我做了几件事。第一,我跟踪起始行,即第一行和空行之后的任何一行。空行还将 needDate 标志设置为 true。
对于非空行,我使用正则表达式来搜索日期。如果我找到一个,则将其设置为(当前)日期。否则,我检查是否设置了 needDate(由于前一行为空),如果是,则将日期附加到该行。
然后我将这些行分组,每个起始行使用我的 subList 函数开始一个组,然后我用逗号连接分组的行。
tf = """Sa 19.11.2016
FC Tuggen
FC Basel 1893 II
1
3
"""
import re
i=0
sl=[0] #start lines
gnu = True
dlt = False # date last turn
lines = tf.split('\n')
for line in lines:
line = line.rstrip()
if not line == '':
regex = re.search('[A-Za-z]+ [0-9]+\.[0-9]+\.[0-9]+', line)
if regex:
date = regex.group(0)
dlt = True
elif needDate:
lines[i] = date + ',' + line
needDate= False
else:
sl.append(i+1)
needDate = True
i += 1
def subList(lzt, inds):
tups = []
for i in range(len(inds)):
if i < len(inds)-1:
tups.append( (inds[i], inds[i+1]))
else:
tups.append( (inds[i], len(lzt)))
return [lzt[s:e] for s,e in tups]
ans = []
for x in subList(lines, sl):
ans.append(",".join(x[:-1]))
for line in ans:
print(line)
假设输入文件的格式与您显示的格式类似,像下面这样简单的方法可能会奏效。使用变量跟踪上次查看日期。
lastseendate = None
gameinfo = []
for line in f:
if line[0:2] in ('Sa','So','Mo','Di','Mi','Do','Fr'): # date row
lastseendate = line.strip()
elif len(line.strip()) == 0: # empty line
print(lastseendate + ',' + ','.join(gameinfo)) # print out the row for game just read before
gameinfo = [] # ready to read the next game info
else:
gameinfo.append(line.strip())
如果日期之前的前两个字符太多而无法硬编码,那么您可以使用如下所示的正则表达式。
import re
pat = re.compile("[A-Za-z] \d{2}\.\d{2}\.\d{4}")
然后将 # date row
行替换为
if pat.match(line):
编辑
- 除非文件末尾有一个空行,否则这段代码不会打印文件中最后一场比赛的信息。要解决此问题,请在文件末尾添加一个空行或在循环结束后重复打印语句。
- 删除了打印语句中的
\n
(不必要,因为 print
已经打印了新行)。
无论末尾是否有空行,这个选项都可以解决。基本思想是发送一个额外的标记表示文件结束,这可能是多余的。
class Token:
def __call__ (self, token):
if len(token) == 0 or token=='-':
print (','. join(self.content))
if token[0:2] in ('Sa','So','Mo','Di','Mi','Do','Fr'):
self.content = [token]
else:
self.content.append(token)
token = Token()
with open('football.txt') as football:
for line in football.readlines():
token(line.strip())
line = football.readline()
token('-')
结果:
Sa 19.11.2016,FC Tuggen,FC Basel 1893 II,1,3
Sa 19.11.2016,FC Tuggen,FC Basel 1893 II,1,3,,SC Cham,FC Zürich II,0,1
Sa 19.11.2016,FC Tuggen,FC Basel 1893 II,1,3,,SC Cham,FC Zürich II,0,1,,SC Kriens,FC Köniz,3,1
Sa 26.11.2016,FC Bavois,SC Brühl,1,4
Mi 30.11.2016,FC Zürich II,FC Basel 1893 II,2,2
我有一个结构合理的文件,但后续事件(一个或多个)的日期只打印一次。在下一个日期出现之前,我不知道如何读取文件、识别日期并将它们映射到随后的每个游戏结果。
数据如下所示:
Sa 19.11.2016
FC Tuggen
FC Basel 1893 II
1
3
SC Cham
FC Zürich II
0
1
SC Kriens
FC Köniz
3
1
Sa 26.11.2016
FC Bavois
SC Brühl
1
4
Mi 30.11.2016
FC Zürich II
FC Basel 1893 II
2
2
每个日期可以应用于一个或多个游戏结果。我试过通读文件并查找日期
keys = []
for line in d:
if line[0:2] in ('Sa','So','Mo','Di','Mi','Do','Fr'):
keys.append(line[2:-1].strip())
但是在下一个日期到来之前,我不知道如何为接下来的比赛指定相同的日期。为此,我尝试了 enumerate()、xrange() 等的各种组合。enumerate() 没有像我尝试的那样工作,因为我只能在每个日期之后添加第一个游戏。
我想要的输出如下所示,或者一个 defaultdict(list),键作为日期,数组元素作为小词典:
Sa 19.11.2016,FC Tuggen,FC Basel 1893 II,1,3
Sa 19.11.2016,SC Cham,FC Zürich II,0,1
Sa 19.11.2016,SC Kriens,FC Köniz,3,1
Sa 26.11.2016,FC Bavois,SC Brühl,1,4
Mi 30.11.2016,FC Zürich II,FC Basel 1893 II,2,2
# vim: set fileencoding=utf-8 :
def parse(it):
record, date = [], ""
for line in it:
line = line.strip()
if not line:
if len(record) > 1:
yield ",".join(record)
if date:
record = [date]
continue
if line.startswith(('Sa', 'So', 'Mo', 'Di', 'Mi', 'Do', 'Fr')):
if len(record) > 1:
yield ",".join(record)
date = line
record = [date]
continue
record.append(line)
if len(record) > 1:
yield ",".join(record)
if __name__ == "__main__":
input = """
Sa 19.11.2016
FC Tuggen
FC Basel 1893 II
1
3
SC Cham
FC Zürich II
0
1
SC Kriens
FC Köniz
3
1
Sa 26.11.2016
FC Bavois
SC Brühl
1
4
Mi 30.11.2016
FC Zürich II
FC Basel 1893 II
2
2
"""
output = """Sa 19.11.2016,FC Tuggen,FC Basel 1893 II,1,3
Sa 19.11.2016,SC Cham,FC Zürich II,0,1
Sa 19.11.2016,SC Kriens,FC Köniz,3,1
Sa 26.11.2016,FC Bavois,SC Brühl,1,4
Mi 30.11.2016,FC Zürich II,FC Basel 1893 II,2,2
"""
for record, expected in zip(parse(input.splitlines()), output.splitlines()):
assert record == expected
编辑:好的,误读了问题 earlier.This 一个可以处理所有提供的数据,我只是不想在 SO 的代码中占用所有 space。
首先,我按行拆分文本。通过每一行,我做了几件事。第一,我跟踪起始行,即第一行和空行之后的任何一行。空行还将 needDate 标志设置为 true。
对于非空行,我使用正则表达式来搜索日期。如果我找到一个,则将其设置为(当前)日期。否则,我检查是否设置了 needDate(由于前一行为空),如果是,则将日期附加到该行。
然后我将这些行分组,每个起始行使用我的 subList 函数开始一个组,然后我用逗号连接分组的行。
tf = """Sa 19.11.2016
FC Tuggen
FC Basel 1893 II
1
3
"""
import re
i=0
sl=[0] #start lines
gnu = True
dlt = False # date last turn
lines = tf.split('\n')
for line in lines:
line = line.rstrip()
if not line == '':
regex = re.search('[A-Za-z]+ [0-9]+\.[0-9]+\.[0-9]+', line)
if regex:
date = regex.group(0)
dlt = True
elif needDate:
lines[i] = date + ',' + line
needDate= False
else:
sl.append(i+1)
needDate = True
i += 1
def subList(lzt, inds):
tups = []
for i in range(len(inds)):
if i < len(inds)-1:
tups.append( (inds[i], inds[i+1]))
else:
tups.append( (inds[i], len(lzt)))
return [lzt[s:e] for s,e in tups]
ans = []
for x in subList(lines, sl):
ans.append(",".join(x[:-1]))
for line in ans:
print(line)
假设输入文件的格式与您显示的格式类似,像下面这样简单的方法可能会奏效。使用变量跟踪上次查看日期。
lastseendate = None
gameinfo = []
for line in f:
if line[0:2] in ('Sa','So','Mo','Di','Mi','Do','Fr'): # date row
lastseendate = line.strip()
elif len(line.strip()) == 0: # empty line
print(lastseendate + ',' + ','.join(gameinfo)) # print out the row for game just read before
gameinfo = [] # ready to read the next game info
else:
gameinfo.append(line.strip())
如果日期之前的前两个字符太多而无法硬编码,那么您可以使用如下所示的正则表达式。
import re
pat = re.compile("[A-Za-z] \d{2}\.\d{2}\.\d{4}")
然后将 # date row
行替换为
if pat.match(line):
编辑
- 除非文件末尾有一个空行,否则这段代码不会打印文件中最后一场比赛的信息。要解决此问题,请在文件末尾添加一个空行或在循环结束后重复打印语句。
- 删除了打印语句中的
\n
(不必要,因为print
已经打印了新行)。
无论末尾是否有空行,这个选项都可以解决。基本思想是发送一个额外的标记表示文件结束,这可能是多余的。
class Token:
def __call__ (self, token):
if len(token) == 0 or token=='-':
print (','. join(self.content))
if token[0:2] in ('Sa','So','Mo','Di','Mi','Do','Fr'):
self.content = [token]
else:
self.content.append(token)
token = Token()
with open('football.txt') as football:
for line in football.readlines():
token(line.strip())
line = football.readline()
token('-')
结果:
Sa 19.11.2016,FC Tuggen,FC Basel 1893 II,1,3
Sa 19.11.2016,FC Tuggen,FC Basel 1893 II,1,3,,SC Cham,FC Zürich II,0,1
Sa 19.11.2016,FC Tuggen,FC Basel 1893 II,1,3,,SC Cham,FC Zürich II,0,1,,SC Kriens,FC Köniz,3,1
Sa 26.11.2016,FC Bavois,SC Brühl,1,4
Mi 30.11.2016,FC Zürich II,FC Basel 1893 II,2,2