2

像我之前的许多不幸的程序员一样,我目前正在处理一种拒绝死亡的古老文件格式。我说的是~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 $ARGV\n" if eof;
}

正如您所看到的,使用 perl 脚本,我基本上只是在获得某个模式匹配时更改引用指向的位置,并将文件的每一行连接到其各自的字符串,直到获得下一个模式匹配。然后将这些作为一个大的连接文件打印出来。

我会并且可以坚持使用 perl,但我的需求每天都变得越来越复杂,我真的很想看看如何用 python 优雅地解决这个问题(可以吗?)。到目前为止,我在 python 中的方法基本上是将整个文件加载为字符串,搜索标题位置,然后根据标题索引拆分字符串并连接字符串。这需要大量的正则表达式、if 语句和变量来处理在另一种语言中看起来如此简单的东西。

看来这真的归结为一个基本的语言问题。与其他按引用调用的语言相比,我发现了一个关于 python 的“按对象调用”样式的非常好的 SO 讨论。 如何通过引用传递变量? 然而,我仍然想不出一种优雅的方式在 python 中做到这一点。如果有人可以帮助我朝着正确的方向前进,将不胜感激。

4

4 回答 4

2

这甚至不是优雅的 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 $ARGV\n" 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 $ARGV\n" if eof;
}

使用散列可以保存my您未显示的无数声明。

你也可以这样做$header_name = $1; name(\$sections{$header_name});$sections{$header_name} .= $_以获得更多的可读性。

于 2013-02-18T23:47:34.260 回答
1

我不确定我是否理解您的整个问题,但这似乎可以满足您的所有需求:

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]].

首先,只需将其设为从字符串到索引而不是字符串到字符串的映射,或者替换sectionsdict. 或者只是使用collections.OrderedDict.

于 2013-02-18T23:36:19.147 回答
0

我最深切的同情!

这是一些代码(请原谅小的语法错误)

  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

这假设您没有看起来像“SHOES”/“HATS”等的内容行。

于 2013-02-18T23:47:02.797 回答
0

假设您正在从标准输入读取,就像在 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列表不必以某种顺序定义,因为标题出现在输入中。

于 2013-02-18T23:37:00.007 回答