【问题标题】:How to parse data in a variable length delimited file?如何解析可变长度分隔文件中的数据?
【发布时间】:2010-08-12 23:43:41
【问题描述】:

我有一个不符合标准的文本文件。所以我知道每个列值的(结束,开始)位置。

示例文本文件:

#     #   #   #
Techy Inn Val NJ

使用这段代码找到#的位置:

  1 f = open('sample.txt', 'r')
  2 i = 0
  3 positions = []
  4 for line in f:
  5     if line.find('#') > 0:
  6         print line
  7         for each in line:
  8             i += 1
  9             if each == '#':
 10                 positions.append(i)

1 7 11 15 => 职位

到目前为止,一切都很好!现在,如何根据获取的位置从每一行获取值?我正在尝试构建一个有效的循环,但非常感谢任何指针!谢谢(:

【问题讨论】:

  • 请不要使用上面的代码来寻找职位。它检查文件中的所有行,并且在第一行之后发现的任何# 字符(可能是数据的一部分)都会导致将一个可能是虚假的条目附加到positions

标签: python file text


【解决方案1】:

这是一种使用正则表达式读取固定宽度字段的方法

>>> import re
>>> s="Techy Inn Val NJ"
>>> var1,var2,var3,var4 = re.match("(.{5}) (.{3}) (.{3}) (.{2})",s).groups()
>>> var1
'Techy'
>>> var2
'Inn'
>>> var3
'Val'
>>> var4
'NJ'
>>> 

【讨论】:

  • 超级优雅!你能否解决这个问题: var1,var2 = re.match("(.{%d}) (.{%d})",line2).groups() % (positions[1],positions[2]) AttributeError : 'NoneType' 对象没有属性 'groups'
  • 正则表达式与该行不匹配。另外,我很好奇为什么你认为正则表达式比切片更优雅。
  • 你需要像这样移动字符串旁边的参数`re.match("(.{%d}) (.{%d})"% (positions[1],positions[ 2]),line2).groups() `
【解决方案2】:

在我的头顶:

f = open(.......)
header = f.next() # get first line
posns = [i for i, c in enumerate(header + "#") if c = '#']
for line in f:
    fields = [line[posns[k]:posns[k+1]] for k in xrange(len(posns) - 1)]

更新经过测试的固定代码:

import sys
f = open(sys.argv[1])
header = f.next() # get first line
print repr(header)
posns = [i for i, c in enumerate(header) if c == '#'] + [-1]
print posns
for line in f:
    posns[-1] = len(line)
    fields = [line[posns[k]:posns[k+1]].rstrip() for k in xrange(len(posns) - 1)]
    print fields

输入文件:

#      #  #
Foo    BarBaz
123456789abcd

调试输出:

'#      #  #\n'
[0, 7, 10, -1]
['Foo', 'Bar', 'Baz']
['1234567', '89a', 'bcd']

鲁棒性说明:

  1. 此解决方案适用于标题行中最后一个 # 之后的任何旧垃圾(或什么都没有);它不需要用空格或其他任何内容填充标题行。
  2. 如果header的第一个字符不是#,OP需要考虑是否出错。
  3. 每个字段都去除了尾随空格;这会自动从最右边的字段中删除一个尾随换行符(如果最后一行没有被换行符终止,则不会运行异常)。

Final(?) 更新:跨越@gnibbler 使用slice() 的建议:在循环之前设置一次切片。

import sys
f = open(sys.argv[1])
header = f.next() # get first line
print repr(header)
posns = [i for i, c in enumerate(header) if c == '#']
print posns
slices = [slice(lo, hi) for lo, hi in zip(posns, posns[1:] + [None])]
print slices
for line in f:
    fields = [line[sl].rstrip() for sl in slices]
    print fields

【讨论】:

  • fields = [line[slice(*x)] for x in zip(posns, posns[1:])]怎么样
  • 这个答案是正确的先生!工作精美。另一个挑战,如果分隔符是 3 个字母并且并非全部相同,例如: 我知道它要求太多了!智能地解析分隔符会很棒。再次感谢...
  • 哇!我稍微调整了我的程序,使用我的代码来查找分隔符并将位置传递给您的代码和繁荣,它工作!非常感谢!
  • @gnibbler:感谢使用切片的建议。
  • @AnonymousDriveByDownVoter:愿意给出理由,以便我/我们可以从您的智慧中受益吗?
【解决方案3】:

改编自 John Machin 的回答

>>> header = "#     #   #   #"
>>> row = "Techy Inn Val NJ"
>>> posns = [i for i, c in enumerate(header) if c == '#']
>>> [row[slice(*x)] for x in zip(posns, posns[1:]+[None])]
['Techy ', 'Inn ', 'Val ', 'NJ']

最后一行也可以这样写

>>> [row[i:j] for i,j in zip(posns, posns[1:]+[None])]

对于你在 cmets 中给出的另一个例子,你只需要有正确的标题

>>> header = "#       #     #     #"
>>> row    = "Techiyi Iniin Viial NiiJ"
>>> posns = [i for i, c in enumerate(header) if c == '#']
>>> [row[slice(*x)] for x in zip(posns, posns[1:]+[None])]
['Techiyi ', 'Iniin ', 'Viial ', 'NiiJ']
>>> 

【讨论】:

  • row = "Techiyi Iniin Viial NiiJ" 失败。我真的很感谢你的回答。试图破译代码,但很整洁!
  • @ThinkCode,你是否更新了 header 的值以匹配行?
【解决方案4】:

好的,为了有点不同并给出 cmets 中的通用解决方案,我使用标题行而不是 slice 和生成器函数。此外,我允许通过不将字段名称放在第一列中并使用多字符字段名称而不是仅使用“#”来对第一列进行注释。

缺点是一个字符字段不可能有标题名称,而只能在标题行中包含“#”(在以前的解决方案中总是将其视为字段的开头,即使在标题中的字母之后也是如此)

sample="""
            HOTEL     CAT ST DEP ##
Test line   Techy Inn Val NJ FT  FT
"""
data=sample.splitlines()[1:]

def fields(header,line):
    previndex=0
    prevchar=''
    for index,char in enumerate(header):
        if char == '#' or (prevchar != char and prevchar == ' '):
            if previndex or header[0] != ' ':
                yield line[previndex:index]
            previndex=index
        prevchar = char
    yield line[previndex:]

header,dataline = data
print list(fields(header,dataline))

输出

['Techy Inn ', 'Val ', 'NJ ', 'FT  ', 'F', 'T']

此方法的一个实际用途是在不知道长度的情况下解析固定字段长度数据,只需将数据行的副本放入所有字段且不存在注释并将空格替换为其他内容(例如“_”)并替换单个字符字段值由#.

示例行的标题:

'            Techy_Inn Val NJ FT  ##'

【讨论】:

    【解决方案5】:
    def parse(your_file):
        first_line = your_file.next().rstrip()
        slices = []
        start = None
        for e, c in enumerate(first_line):
            if c != '#':
                continue
    
            if start is None:
                start = e
                continue
            slices.append(slice(start, e))
            start = e
        if start is not None:
            slices.append(slice(start, None))
    
        for line in your_file:
            parsed = [line[s] for s in slices]
            yield parsed
    

    【讨论】:

      【解决方案6】:
      f = open('sample.txt', 'r')
      pos = [m.span() for m in re.finditer('#\s*', f.next())]
      pos[-1] = (pos[-1][0], None)
      for line in f:
         print [line[i:j].strip() for i, j in pos]
      f.close()
      

      【讨论】:

        【解决方案7】:

        这个怎么样?

        with open('somefile','r') as source:
            line= source.next()
            sizes= map( len, line.split("#") )[1:]
            positions = [ (sum(sizes[:x]),sum(sizes[:x+1])) for x in xrange(len(sizes)) ] 
            for line in source:
                fields = [ line[start,end] for start,end in positions ]
        

        这就是你要找的吗?

        【讨论】:

          猜你喜欢
          • 2015-07-18
          • 2014-01-09
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-03-11
          • 1970-01-01
          相关资源
          最近更新 更多