【问题标题】:Better regex implementation than for looping whole file?比循环整个文件更好的正则表达式实现?
【发布时间】:2019-02-08 11:06:52
【问题描述】:

我的文件看起来像这样:

#     BJD     K2SC-Flux EAPFlux   Err  Flag Spline
2457217.463564 5848.004 5846.670 6.764 0 0.998291
2457217.483996 6195.018 6193.685 6.781 1 0.998291
2457217.504428 6396.612 6395.278 6.790 0 0.998292
2457217.524861 6220.890 6219.556 6.782 0 0.998292
2457217.545293 5891.856 5890.523 6.766 1 0.998292
2457217.565725 5581.000 5579.667 6.749 1 0.998292
2457217.586158 5230.566 5229.232 6.733 1 0.998292
2457217.606590 4901.128 4899.795 6.718 0 0.998293
2457217.627023 4604.127 4602.793 6.700 0 0.998293

我需要找到并计算Flag = 1的行。 (第 5 列。)这就是我的做法:

foundlines=[]
c=0
import re
with open('examplefile') as f:
    for index, line in enumerate(f):
        try:
            found = re.findall(r' 1 ', line)[0]
            foundlines.append(index)
            print(line)
            c+=1
        except:
            pass
print(c)

在 Shell 中,我只会使用grep " 1 " examplefile | wc -l,它比上面的 Python 脚本要短得多。 python 脚本可以工作,但我对是否有比上面的脚本更短、更紧凑的方法来完成任务感兴趣?我更喜欢 Shell 的简短性,所以我希望在 Python 中有类似的东西。

【问题讨论】:

  • 由于代码有效,您应该考虑将其发布到Code Review。但是,很明显,您不需要正则表达式即可在空格之间找到1,请使用if ' 1 ' in line
  • Python 中的大部分内容都可以放在一行中,但这会严重损害可读性。你确定尺寸对你来说是唯一重要的事情吗?
  • 如果你喜欢它简短,坚持shell。
  • 是的,如果可读性严重降低,我不介意它很长。好的,我会重新考虑 shell 的实现!

标签: python regex python-3.x shell text


【解决方案1】:

最短代码

这是在某些特定前提下的一个非常短的版本:

  • 您只想计算 grep 调用之类的出现次数
  • 保证每行只有一个" 1 "
  • " 1 " 只能出现在所需的列中
  • 您的文件很容易放入内存中

请注意,如果不满足这些先决条件,可能会导致内存问题或返回误报。

print(open("examplefile").read().count(" 1 "))

简单通用,稍长

当然,如果你以后有兴趣用这些行做点什么,我推荐 Pandas:

df = pandas.read_table('test.txt', delimiter=" ",
                       comment="#",
                       names=['BJD', 'K2SC-Flux', 'EAPFlux', 'Err', 'Flag', 'Spline'])

获取 Flag 为 1 的所有行:

flaggedrows = df[df.Flag == 1]

返回:

            BJD  K2SC-Flux   EAPFlux    Err  Flag    Spline
1  2.457217e+06   6195.018  6193.685  6.781     1  0.998291
4  2.457218e+06   5891.856  5890.523  6.766     1  0.998292
5  2.457218e+06   5581.000  5579.667  6.749     1  0.998292
6  2.457218e+06   5230.566  5229.232  6.733     1  0.998292

计算它们:

print(len(flaggedrows))

返回 4

【讨论】:

【解决方案2】:

你的 shell 实现可以更短,grep-c 选项可以让你计数,不需要匿名管道和wc

grep -c " 1 " examplefile

您的 shell 代码只是为您获取找到模式 1 的行数,但您的 Python 代码还保留了与该模式匹配的行的索引列表。

仅获取行数,您可以使用sum 和 genexp/list comprehension,也不需要Regex;简单的字符串__contains__ 检查会做,因为字符串是可迭代的:

with open('examplefile') as f:
    count = sum(1 for line in f if ' 1 ' in line)
    print(count)  

如果你也想保留索引,你可以坚持你的想法,只用str test 替换re test:

count = 0
indexes = []
with open('examplefile') as f:
    for idx, line in enumerate(f):
        if ' 1 ' in line:
            count += 1
            indexes.append(idx)

此外,做一个简单的except 几乎总是一个坏主意(至少你应该使用except Exception 来忽略SystemExitKeyboardInterrupt 之类的异常),只捕获你知道可能会引发的异常。

此外,在解析结构化数据时,您应该使用特定工具,例如这里csv.reader 以空格作为分隔符(line.split(' ') 在这种情况下也应该这样做)并且检查 index-4 将是最安全的(请参阅Tomalak's answer)。使用' 1 ' in line 测试,如果任何其他列包含1,则会产生误导性结果。

考虑到上述情况,下面是使用awk 匹配第5 个字段的shell 方式:

awk '$5 == "1" {count+=1}; END{print count}' examplefile

【讨论】:

  • ... if ' 1 ' in line 是奸诈的。
  • @Tomalak 我承认 ;) 实际上是文字示例。
  • 那么你至少应该警告误报的风险。
【解决方案3】:

你有CSV数据,你可以使用csv模块:

import csv

with open('your file', 'r', newline='', encoding='utf8') as fp:
    rows = csv.reader(fp, delimiter=' ')

    # generator comprehension
    errors = (row for row in rows if row[4] == '1')

for error in errors:
    print(error)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-03-31
    • 2014-01-19
    • 1970-01-01
    • 2019-09-07
    • 1970-01-01
    • 2012-10-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多