就像我之前的许多不幸的程序员一样,我目前正在处理一种过时的文件格式,它不会消亡。我说的是1970年的过时格式规范。如果完全由我决定,我们会放弃文件格式和任何知道如何处理它的工具,从头开始。我可以做梦,但不幸的是,这并不能解决我的问题。
格式:定义相当松散,因为多年的无意义修订几乎破坏了它曾经拥有的所有背部兼容性。基本上,唯一不变的是有章节标题,关于这些行之前或之后的内容几乎没有规则。标题是按顺序排列的(例如,HEADING1、HEADING2、HEADING3…),但没有编号,也不是必需的(例如HEADING1,HEADING3、HEADING7)。值得庆幸的是,所有可能的标题排列都是已知的。这里有一个假的例子:
# Bunch of comments
SHOES # First heading
# bunch text and numbers here
HATS # Second heading
# bunch of text here
SUNGLASSES # Third heading
...
我的问题:我需要用这些节标题连接多个这样的文件。我有一个perl脚本可以很好地做到这一点:
while(my $l=<>) {
if($l=~/^SHOES/i) { $r=$shoes; name($r);}
elsif($l=~/^HATS/i) { $r=$hats; name($r);}
elsif($l=~/^SUNGLASSES/i) { $r=$sung; name($r);}
elsif($l=~/^DRESS/i || $l=~/^SKIRT/i ) { $r=$dress; name($r);}
...
...
elsif($l=~/^END/i) { $r=$end; name($r);}
else {
$$r .= $l;
}
print STDERR "Finished processing $ARGVn" if eof;
}
正如您所看到的,使用perl脚本,我基本上只是在获得某个模式匹配时更改引用指向的位置,并将文件的每一行连接到其各自的字符串,直到获得下一个模式匹配。这些文件稍后会作为一个大的凹入文件打印出来。
我会也可以坚持使用perl,但我的需求每天都在变得越来越复杂,我真的很想看看如何用python优雅地解决这个问题(是吗?)。到目前为止,我在python中的方法基本上是将整个文件加载为字符串,搜索标题位置,然后根据标题索引拆分字符串并连接字符串。对于在另一种语言中看起来如此简单的东西,这需要大量的正则表达式if语句和变量。
这似乎真的归结为一个根本的语言问题。我发现了一个非常好的SO讨论,与其他通过引用调用的语言相比,python的"按对象调用"风格。如何通过引用传递变量?然而,我仍然想不出一种优雅的方法来在python中做到这一点。如果有人能帮助我把大脑踢向正确的方向,我将不胜感激。
这甚至不是一个优雅的Perl。
my @headers = qw( shoes hats sunglasses dress );
my $header_pat = join "|", map quotemeta, @headers;
my $header_re = qr/$header_pat/i;
my ( $section, %sections );
while (<>) {
if (/($header_re)/) { name( $section = $sections{$1 } ); }
elsif (/skirt/i) { name( $section = $sections{'dress'} ); }
else { $$section .= $_; }
print STDERR "Finished processing $ARGVn" if eof;
}
或者,如果你有很多例外:
my @headers = qw( shoes hats sunglasses dress );
my %aliases = ( 'skirt' => 'dress' );
my $header_pat = join "|", map quotemeta, @headers, keys(%aliases);
my $header_re = qr/$header_pat/i;
my ( $section, %sections );
while (<>) {
if (/($header_re)/) {
name( $section = $sections{ $aliases{$1} // $1 } );
} else {
$$section .= $_;
}
print STDERR "Finished processing $ARGVn" if eof;
}
使用散列可以保存您没有显示的无数my
声明。
为了提高可读性,您也可以使用$header_name = $1; name($sections{$header_name});
和$sections{$header_name} .= $_
。
我不确定我是否理解你的整个问题,但这似乎可以满足你的所有需求:
import sys
headers = [None, 'SHOES', 'HATS', 'SUNGLASSES']
sections = [[] for header in headers]
for arg in sys.argv[1:]:
section_index = 0
with open(arg) as f:
for line in f:
if line.startswith(headers[section_index + 1]):
section_index = section_index + 1
else:
sections[section_index].append(line)
显然,您可以将其更改为读取或mmap
整个文件,然后re.search
或仅buf.find
用于下一个标头。类似这样的东西(未经测试的伪代码):
import sys
headers = [None, 'SHOES', 'HATS', 'SUNGLASSES']
sections = defaultdict(list)
for arg in sys.argv[1:]:
with open(arg) as f:
buf = f.read()
section = None
start = 0
for header in headers[1:]:
idx = buf.find('n'+header, start)
if idx != -1:
sections[section].append(buf[start:idx])
section = header
start = buf.find('n', idx+1)
if start == -1:
break
else:
sections[section].append(buf[start:])
还有很多其他的选择。
但关键是,我看不出在任何一个解决方案中,你需要在哪里通过引用传递变量,所以我不确定你在哪里遇到了你选择的任何一个。
那么,如果你想把两个不同的标题视为同一节呢?
简单:创建一个dict
,将标题映射到节。例如,对于第二个版本:
headers_to_sections = {None: None, 'SHOES': 'SHOES', 'HATS': 'HATS',
'DRESSES': 'DRESSES', 'SKIRTS': 'DRESSES'}
现在,在执行sections[section]
的代码中,只执行sections[headers_to_sections[section]]
。
首先,只需将其作为从字符串到索引的映射,而不是从字符串到字符串的映射,或者将sections
替换为dict
。或者只需使用collections.OrderedDict
来压平这两个集合。
我深表同情!
以下是一些代码(请原谅轻微的语法错误)
def foundSectionHeader(l, secHdrs):
for s in secHdrs:
if s in l:
return True
return False
def main():
fileList = ['file1.txt', 'file2.txt', ...]
sectionHeaders = ['SHOES', 'HATS', ...]
sectionContents = dict()
for section in sectionHeaders:
sectionContents[section] = []
for file in fileList:
fp = open(file)
lines = fp.readlines()
idx = 0
while idx < len(lines):
sec = foundSectionHeader(lines[idx]):
if sec:
idx += 1
while not foundSectionHeader(lines[idx], sectionHeaders):
sectionContents[sec].append(lines[idx])
idx += 1
这假设您没有看起来像"鞋子"/"帽子"等的内容行。
假设您正在从stdin读取,就像在perl脚本中一样,这应该可以做到:
import sys
import collections
headings = {'SHOES':'SHOES','HATS':'HATS','DRESS':'DRESS','SKIRT':'DRESS'} # etc...
sections = collections.defaultdict(str)
key = None
for line in sys.stdin:
sline = line.strip()
if sline not in headings:
sections[headings.get(key)].append(sline)
else:
key = sline
你最终会得到一本这样的字典:
{
None: <all lines as a single string before any heading>
'HATS' : <all lines as a single string below HATS heading and before next heading> ],
etc...
}
headings
列表不必按照某些顺序定义,因为标题出现在输入中。