Python - 解析文本文件并根据条件创建列表
我一直在寻找解决这个问题的方法,但一直没有找到。我想用Python来读取一个文本文件,并根据文件中的数据创建一些列表(或者叫数组)。下面的例子能更好地说明我的目标。
考虑以下文本:
NODE
1.0, 2.0
2.0, 2.0
3.0, 2.0
4.0, 2.0
ELEMENT
1, 2, 3, 4
5, 6, 7, 8
1, 2, 3, 4
1, 2, 3, 4
1, 2, 3, 4
5, 6, 7, 8
5, 6, 7, 8
5, 6, 7, 8
我想一次性读取整个文件(因为文件可能很大),当我找到“NODE”时,就把“NODE”和“ELEMENT”之间的每一行放到一个列表里。然后,当我到达“ELEMENT”时,再把“ELEMENT”和其他分隔符(可能是另一个“ELEMENT”或者文件结束等)之间的每一行放到另一个列表里。对于这个例子,最终会得到两个列表。
我尝试了很多方法,但它们都需要提前知道文件的一些信息。我希望能够自动化这个过程。非常感谢!
4 个回答
对于更新后的问题中的简单情况,其实你根本不需要用到正则表达式、分组、复杂的状态机,或者任何超出初学者容易理解的东西。
你只需要把行数据放到一个列表里,直到找到那一行 'ELEMENT'
,然后再开始把后面的行数据放到另一个列表里。就像这样:
import csv
result = {'NODES': [], 'ELEMENTS': []}
current = result['NODES']
with open(path) as f:
for row in csv.reader(f):
if row == ['NODE']:
pass
elif row == ['ELEMENT']:
current = result['ELEMENTS']
else:
current.append(row)
def getBlocks(fname):
state = 0
node = []
ele = []
with open(fname) as f:
for line in f:
if "NODE" in line:
if state == 2:
yield (node,ele)
node,ele = [],[]
state = 1
elif state == 1 and "ELEMENT" in line:
state = 2
elif state == 1:
node.append(list(map(float,line.split(","))))
elif state == 2 and re.match("[a-zA-Z]+",line):
yield (node,ele)
node,ele = [],[]
state = 0
elif state == 2:
ele.append(list(map(int,line.split(","))))
yield (node,ele)
for node,ele in getBlocks("somefile.txt"):
print "N:",node
print "E:",ele
这可能是你想要的东西,不过看起来有点恶心……我相信你能做得更好。
如果你想让这个过程完全通用和自动化,你需要想出一个规则来区分章节标题和普通行。我会想一个规则,但这可能不是你想要的,所以我想的代码可能不适合你……不过希望它能给你一些启发,让你知道该怎么做,以及如何开始。
def new_section(row):
return len(row) == 1 and row[0].isalpha() and row[0].isupper()
现在,我们可以通过使用 itertools.groupby
来根据是否是章节标题来分组这些行。如果你打印出每一组,你会得到类似这样的结果:
True, [['NODE']]
False, [['1.0', '2.0'], ['2.0', '2.0'], …, ]
True, [['ELEMENT']]
False, [['1.0', '2.0', '3.0', '4.0'], …, ]
我们不关心每组中的第一个值,所以可以把它丢掉。
接下来,我们想把每一对相邻的组打包成一个(标题,行)的组合,这可以通过将我们的迭代器与自身配对来实现。
然后把它放进一个字典里,字典的样子大概是这样的:
{'NODE': [['1.0', '2.0'], ['2.0', '2.0'], …],
'ELEMENT': [['1.0', '2.0', '3.0', '4.0'], …]}
这就是整个过程:
import csv
import itertools
def new_section(row):
return len(row) == 1 and row[0].isalpha() and row[0].isupper()
with open(path) as f:
rows = csv.reader(f)
grouped = itertools.groupby(rows, new_section)
groups = (group for key, group in grouped)
pairs = zip(groups, groups)
lists = {header[0][0]: rows for header, rows in pairs}
根据这个示例数据,假设标签的格式和你的例子一样,你可以使用正则表达式来处理:
import re, mmap, os
def conv(s):
try:
return float(s)
except ValueError:
return s
data_dict={}
with open(fn, 'r') as fin:
size = os.stat(fn).st_size
data = mmap.mmap(fin.fileno(), size, access=mmap.ACCESS_READ)
for m in re.finditer(r'^(\w+)$([\d\s,.]+)', data, re.M):
data_dict[m.group(1)]=[[conv(e) for e in line.split(',')]
for line in m.group(2).splitlines() if line.strip()]
print data_dict
输出结果:
{'NODE': [[1.0, 2.0], [2.0, 2.0], [3.0, 2.0], [4.0, 2.0]],
'ELEMENT': [[1.0, 2.0, 3.0, 4.0], [5.0, 6.0, 7.0, 8.0], [1.0, 2.0, 3.0, 4.0], [1.0, 2.0, 3.0, 4.0], [1.0, 2.0, 3.0, 4.0], [5.0, 6.0, 7.0, 8.0], [5.0, 6.0, 7.0, 8.0], [5.0, 6.0, 7.0, 8.0]]}
那么,这个过程是怎么进行的呢:
- 我们使用 mmap 来 对文件应用正则表达式。
- 我们假设标签的格式是
^\w+$
(也就是说,标签是由字母和数字组成的一行) - 然后提取出所有跟在后面的数字和空格。
- 最后,创建一个字典,用标签作为键,解析出的数字作为浮点数列表。
完成了!