选择性合并两个或多个数据文件

时间:2009-11-08 22:42:36

标签: python parsing text-files bison flex-lexer

我有一个可执行文件,其输入包含在ASCII文件中,格式为:

$ GENERAL INPUTS
$ PARAM1 = 123.456
PARAM2=456,789,101112
PARAM3(1)=123,456,789
PARAM4       =
1234,5678,91011E2
PARAM5(1,2)='STRING','STRING2'
$ NEW INSTANCE
NEW(1)=.TRUE.
PAR1=123
[More data here]
$ NEW INSTANCE
NEW(2)=.TRUE.
[etcetera]

换句话说,一些常规输入,以及一些新实例的一些参数值。参数声明不规范;有些数字用逗号分隔,其他数字用科学记数法表示,其他数字用引号括起来,间距不等,等等。

对某些场景的评估要求我输入一个“主”数据文件,并将例如实例2到6的参数数据复制到另一个可能已包含所述实例数据的数据文件中(在这种情况下)数据应该被覆盖)和其他可能的数据(数据应保持不变)。

我写了一个Flex lexer和一个Bison解析器;他们可以一起吃数据文件并将参数存储在内存中。如果我使用它们来打开两个文件(主文件和“方案”),那么选择性地向第三个新文件写入所需参数(如"general input from 'scenario'; instances 1 though 5 from 'master'; instances 6 through 9 from 'scenario'; ..."),保存它并删除原始方案文件。

其他信息:(1)文件高度敏感,用户完全不受修改主文件的影响非常重要; (2)文件大小可控(从500K到10M)。

我已经了解到我可以用十行代码做些什么,这里的一些人可以做两行。你会如何解决这个问题? Pythonic的回答会让我哭泣。严重。

1 个答案:

答案 0 :(得分:1)

如果你已经能够解析这种格式(我已经尝试过使用pyParsing,但是如果你已经有了一个有效的flexx / bison解决方案,那就没关系了),并且解析后的数据非常适合内存那你基本上就在那里。您可以将从每个文件中读取的内容表示为一个简单的对象,其中包含“常规输入”的dict和一个dicts列表,每个实例一个(或者可能更好的实例dict,其中键是实例数,可能是给你一点灵活性)。然后,如您所述,您只需选择性地“更新”(添加或覆盖)从主服务器复制到方案中的一些实例,编写新方案文件,用它替换旧方案文件。

要在Python中使用flexx / bison代码,您有几个选项 - 将其变为DLL / so并使用ctypes访问它,或者从cython编码的扩展,SWIG包装器,Python C-API调用它扩展,或SIP,Boost等等。

假设您有这样或那样的解析器原语(例如)接受输入文件名,读取并解析该文件,并返回2个字符串元组的列表,每个元组都是以下任意一个:

  • (paramname,paramvalue)
  • ('$$$$','General Inputs')
  • ('$$$$','新实例')

只使用'$$$$'作为一种任意标记。然后,对于代表您从文件中读取的所有内容的对象,您可以:

import re

instidre = re.compile(r'NEW\((\d+)\)')

class Afile(object):

  def __init__(self, filename):
    self.filename = filename
    self.geninput = dict()
    self.instances = dict()

  def feed_data(self, listoftuples):
    it = iter(listoftuples)
    assert next(it) == ('$$$$', 'General Inputs')
    for name, value in it:
      if name == '$$$$': break
      self.geninput[name] = value
    else:  # no instances at all!
      return
    currinst = dict()
    for name, value in it:
      if name == '$$$$':
        self.finish_inst(currinst)
        currinst = dict()
        continue
      mo = instidre.match(name)
      if mo:
        assert value == '.TRUE.'
        name = '$$$INSTID$$$'
        value = mo.group(1)
      currinst[name] = value
    self.finish_inst(currinst)

  def finish_inst(self, adict):
    instid = dict.pop('$$$INSTID$$$')
    assert instid not in self.instances
    self.instances[instid] = adict

可以稍微改善一下整体检查,更准确地诊断异常,但是我认为这大概是你想要的错误情况。

合并只需要foo.instances[instid] = bar.instances[instid]执行instid所需的值,其中foo是方案文件的Afile实例,bar是一个用于主文件 - 将根据需要覆盖或添加。

我假设要写出新更改的方案文件,您不需要重复特定输入可能具有的所有格式化问题(如果这样做,那么在解析名称时需要记录这些怪癖)和值),所以简单地循环sorted(foo.instances)并按排序顺序写出每个(在按顺序编写一般内容后,使用适当的$ this and that标记行,并正确翻译{ {1}}条目等)应该足够了。