选择性合并两个或多个数据文件
-
18-09-2019 - |
题
我有一个可执行文件,其输入包含在 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)。
我了解到我可以用十行代码完成的事情,这里的一些人可以用两行代码完成。您将如何解决这个问题?Python式的回答会让我哭泣。严重地。
解决方案
如果您已经能够解析这种格式(我已经用 pyParsing 尝试过,但如果您已经有一个有效的 flexx/bison 解决方案,那就太好了),并且解析的数据非常适合内存,那么您基本上都有了您可以将从每个文件中读取的内容表示为一个简单的对象,其中包含一个用于“常规输入”的字典和一个字典列表,每个实例一个(或者可能更好的是实例字典,键是实例编号,这可能是给你更多的灵活性)。然后,正如您所提到的,您只需有选择地“更新”(添加或覆盖)从主服务器复制到场景中的一些实例,编写新的场景文件,用它替换旧的场景文件。
要在 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
健全性检查可能会得到一些改进,更准确地诊断异常,但排除错误情况,我认为这大致就是您想要的。
合并只需要做 foo.instances[instid] = bar.instances[instid]
对于所需的值 instid
, , 在哪里 foo
是个 Afile
场景文件的实例和 bar
是主文件的文件——它将根据需要覆盖或添加。
我假设要写出新更改的场景文件,您不需要重复特定输入可能具有的所有格式怪癖(如果这样做,那么在解析过程中需要将这些怪癖与名称和值一起记录) ,所以简单地循环 sorted(foo.instances)
并按排序顺序写出每个内容(在也按排序顺序写出一般内容之后,并使用适当的 $ this and that
标记线,并正确翻译 '$$$INSTID$$$'
条目等)就足够了。