【问题标题】:Python - Parse text file and create lists based on some criteriaPython - 解析文本文件并根据某些标准创建列表
【发布时间】:2014-08-18 10:19: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”或文件结尾等)之间取每一行并将其放入列表中。对于此示例,它将产生两个列表。

我尝试了各种方法,但它们都需要事先了解有关文件的信息。我希望能够自动化它。非常感谢!

【问题讨论】:

  • 到目前为止你做了什么?请贴出你写的代码。
  • 如果您不想事先要求有关该文件的任何信息,那么告诉您已进入新部分的规则是什么?
  • @abarnert 我在最初的帖子中说错了,我知道我在寻找哪些部分(即节点或元素),而不是每个部分之间的行数。
  • 感谢大家的不同选择。 Dawg 的解决方案看起来最有可能在大局中完成我需要做的事情。

标签: python parsing


【解决方案1】:

使用该示例数据,并假设标签遵循示例中的模式,您可以使用正则表达式:

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. 我们使用mmapapply a regex to a file
  2. 我们假设标签是^\w+$的形式(即,由字母和数字组成的标签在一行上)
  3. 然后捕获其后的所有数字和空格
  4. 创建一个以标签为键的字典,解析后的数字为浮点数列表。

完成!

【讨论】:

  • @dawg,我真的很喜欢这个解决方案,因为我认为它将提供未来的灵活性。也就是说,我有很多关于 mmap 和 regex 的知识要学习。关于正则表达式的一个直接问题。我也有部分标题,看起来像“*Section,Name = Section1”。介意提供有关如何使用正则表达式处理此问题的更多信息吗?谢谢!
  • 对于那个确切的例子,使用^(\w+,\s+\w+\s*=\s*\w+)$ 作为正则表达式的部分(未测试...)
  • 再次感谢@dawg!最后一个问题,我保证(这与最初的问题相去甚远)。我似乎无法让您的示例正则表达式工作。我认为这与字符串“*Section”开头的星号有关。我如何捕获那个星号?再次感谢!
  • 试试^(\*\w+,\s+\w+\s*=\s*\w+)$
  • 此解决方案的唯一问题(通常根本不是问题,只要您确保它不相关)是mmap 无法处理 32- 上的大文件位平台。它不必将整个文件读入内存,但它确实必须为整个文件分配页面空间,并且在 32 位域中,只有 2-4GB 的页面空间。
【解决方案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}

【讨论】:

    【解决方案3】:
    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
    

    可能是关于你正在寻找的东西有点恶心......我相信你可以做得更好

    【讨论】:

      【解决方案4】:

      对于更新后的问题中更简单的问题,您真的不需要正则表达式、groupby 或复杂的状态机,或者任何超出新手应该能够轻松理解的东西。

      您需要做的就是将行累积到一个列表中,直到找到行'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)
      

      【讨论】:

      • 谢谢@abarnert!这是一种非常简单、干净的方法。这似乎也是一个可行的选择。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-04-29
      • 1970-01-01
      相关资源
      最近更新 更多