Python - 解析文本文件并根据条件创建列表

0 投票
4 回答
1481 浏览
提问于 2025-04-18 17:22

我一直在寻找解决这个问题的方法,但一直没有找到。我想用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 个回答

0

对于更新后的问题中的简单情况,其实你根本不需要用到正则表达式、分组、复杂的状态机,或者任何超出初学者容易理解的东西。

你只需要把行数据放到一个列表里,直到找到那一行 '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)
0
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

这可能是你想要的东西,不过看起来有点恶心……我相信你能做得更好。

2

如果你想让这个过程完全通用和自动化,你需要想出一个规则来区分章节标题和普通行。我会想一个规则,但这可能不是你想要的,所以我想的代码可能不适合你……不过希望它能给你一些启发,让你知道该怎么做,以及如何开始。

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}
4

根据这个示例数据,假设标签的格式和你的例子一样,你可以使用正则表达式来处理:

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]]}

那么,这个过程是怎么进行的呢:

  1. 我们使用 mmap对文件应用正则表达式
  2. 我们假设标签的格式是 ^\w+$(也就是说,标签是由字母和数字组成的一行)
  3. 然后提取出所有跟在后面的数字和空格。
  4. 最后,创建一个字典,用标签作为键,解析出的数字作为浮点数列表。

完成了!

撰写回答