【问题标题】:Selective merge of two or more data files选择性合并两个或多个数据文件
【发布时间】:2009-11-08 22:42:36
【问题描述】:

我有一个可执行文件,其输入包含在 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 词法分析器和一个 Bison 解析器;他们可以一起吃一个数据文件并将参数存储在内存中。如果我使用它们打开两个文件(主文件和“场景”),那么有选择地向第三个新文件写入所需参数(如"general input from 'scenario'; instances 1 though 5 from 'master'; instances 6 through 9 from 'scenario'; ..."),保存并删除原始场景应该不会太难文件。

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

我知道我可以用十行代码完成,这里的一些人可以用两行代码完成。你会如何处理这个问题?一个 Pythonic 的答案会让我哭泣。认真的。

【问题讨论】:

  • 这是一个很难回答的问题。您提到您已经构建了一个 flex 和 bison 解决方案,所以我不确定您还需要完成哪些部分?此外,您的条款太模糊,无法掌握。例如,什么标识了一个实例?
  • 我可以对 Flex/Bison 解决方案感到满意,但有些人会发现很难维护 - 很少有人知道或对这些感到满意。这个问题在概念上并不难,我的解决方案,尽管我花了大约三个小时才完成,但似乎是“矫枉过正”。如果我能找到一个 Python 解决方案(@Alex Martelli - 感谢您的输入),人们会对我弄乱这些文件感到更加自在。实例是航天器轨迹中的新“腿”,具有相关参数,但我认为细节并不重要。

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


【解决方案1】:

如果你已经能够解析这种格式(我会用 pyParsing 尝试过,但如果你已经有一个有效的 flexx/bison 解决方案,那就没问题了),并且解析的数据很适合内存,那么你基本上就在那里。您可以将您从每个文件中读取的内容表示为一个简单的对象,其中包含一个用于“一般输入”的字典和一个字典列表,每个实例一个(或者可能更好的是实例字典,键是实例编号,这可能给你更多的灵活性)。然后,正如您所提到的,您只需选择性地“更新”(添加或覆盖)一些从 master 复制到场景中的实例,写入新的场景文件,用它替换旧的。

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

假设,无论哪种方式,您有一个解析器原语(例如)接受输入文件名,读取并解析该文件,并返回一个 2 字符串元组列表,每个元组是以下之一:

  • (参数名,参数值)
  • ('$$$$', '一般输入')
  • ('$$$$', '新实例')

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

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

健全性检查可能会有所改进,可以更准确地诊断异常,但我认为这大致是您想要的。

合并只需要对instid的所需值执行foo.instances[instid] = bar.instances[instid],其中foo是场景文件的Afile实例,bar是主文件的实例——这将覆盖或根据需要添加。

我假设要写出新更改的场景文件,您不需要重复特定输入可能具有的所有格式怪癖(如果这样做,那么在解析过程中需要将这些怪癖与名称一起记录下来和值),所以只需循环 sorted(foo.instances) 并按排序顺序写出每个内容(在编写一般内容后也按排序顺序,并使用适当的 $ this and that 标记行,并正确翻译 '$$$INSTID$$$' 条目,等)就足够了。

【讨论】:

  • 该死!我想看看你的 pyparsing 解决方案!
  • 我喜欢您的错误检查(我没有想到断言实例中的参数对应于 .TRUE. 值)。目前我将坚持使用我的 Flex/Bison 解析器,并将实现这个 Python 部分来编写新文件和进行完整性检查。谢谢!
  • @Arrieta,是的,我确实建议坚持使用 Flex/Bison,因为您可以使用它——只需将其连接到 Python 以完成工作的“逻辑”(而不是解析)部分。
猜你喜欢
  • 2021-09-18
  • 2018-11-08
  • 2010-12-14
  • 1970-01-01
  • 2019-09-26
  • 1970-01-01
  • 1970-01-01
  • 2022-08-04
相关资源
最近更新 更多