【问题标题】:Read a line store it in a variable and then read another line and come back to the first line. Python 2读取一行将其存储在一个变量中,然后读取另一行并返回第一行。蟒蛇2
【发布时间】:2015-04-10 01:15:15
【问题描述】:

这是一个棘手的问题,我已经阅读了很多关于它的帖子,但我无法让它发挥作用。

我有一个大文件。我需要逐行阅读它,一旦我到达"Total is: (any decimal number)" 形式的行,就取这个字符串并将数字保存在一个变量中。如果数字大于 40.0,那么我需要找到 Total 行上方的第四行(例如,如果 Total 行是第 39 行,则此行将是第 35 行)。该行的格式为"(number).(space)(substring)"。最后,我需要将这个子字符串解析出来并对其进行进一步处理。

这是一个输入文件的示例:

many lines that we don't care about
many lines that we don't care about
...
1. Hi45
People: bla bla bla bla bla bla
whitespace
bla bla bla bla bla
Total is: (*here there will be a decimal number*)
bla bla
white space
...
more lines we don't care about
and then more lines and then
again we get
2. How144
People: bla bla bla bla bla bla
whitespace
bla bla bla bla bla
Total is: (*here there will be a decimal number*)
bla bla
white space

我尝试了很多东西,包括使用re.search() 方法从我需要关注的每一行中捕获我需要的内容。

这是我从另一个 stackoverflow 问答中修改的代码:

import re
import linecache
number = ""
higher_line = ""
found_line = ""

with open("filename_with_many_lines.txt") as aFile:
    for num, line in enumerate(aFile, 1):
        searchObj = re.search(r'(\bTotal\b)(\s)(\w+)(\:)(\s)(\d+.\d+)', line)
        if searchObj:
            print "this is on line", line
            print "this is the line number:", num
            var1 = searchObj.group(6)
            print var1
            if float(var1) > 40.0:
                number = num
                higher_line = number - 4
                print number
                print higher_line

                found_line = linecache.getline("filename_with_many_lines.txt", higher_line)
                print "found the", found_line

预期的输出是:

this is on line Total is: 45.5
this is the line number: 14857
14857
14853
found the 1. Hi145
this is on line Total is: 62.1
this is the line number: 14985
14985
14981
found the 2.How144

【问题讨论】:

  • 提供一个示例以及预期的输出。
  • 数量不大于40怎么办?
  • “我的一个尝试是部分成功的,因为它给了我所有“Total is”的行,但它并没有得到它们全部。它得到了他们所有,但它没有得到他们所有?你能更具体地说明它是如何分解的吗?
  • 顺便说一句,如果文件是在with 语句的标题中打开的,则不需要对文件调用close()

标签: python regex file line python-2.x


【解决方案1】:

如果您需要的行总是在Total is: 行上方四行,您可以将前面的行保留在有界deque 中。

from collections import deque

with open(filename, 'r') as file:
    previous_lines = deque(maxlen=4)
    for line in file:
        if line.startswith('Total is: '):
            try:
                higher_line = previous_lines[-4]
                # store higher_line, do calculations, whatever
                break  # if you only want to do this once; else let it keep going
            except IndexError:
                # we don't have four previous lines yet
                # I've elected to simply skip this total line in that case
                pass
        previous_lines.append(line)

有界deque(具有最大长度的)如果添加新项目会导致其超过其最大长度,则会丢弃另一侧的项目。在这种情况下,我们将字符串附加到deque 的右侧,因此一旦deque 的长度达到4,我们附加到右侧的每个新字符串都会导致它丢弃一个字符串左侧。因此,在for 循环的开头,deque 将包含当前行之前的四行,最旧的行位于最左侧(索引0)。

事实上,the documentation on collections.deque 提到的用例与我们的非常相似:

有限长度双端队列提供类似于 Unix 中的 tail 过滤器的功能。它们还可用于跟踪仅关注最近活动的交易和其他数据池。

【讨论】:

  • 赞成。我喜欢这个解决方案,但在解释其背后的想法时可能会更明确一些,无需新的数据结构,只需多行几行即可实现。
  • @HR123r 想一想您将如何在线操作以获得小数点!你能把绳子切开吗?你能把它分开吗?最坏的情况,你能应用正则表达式吗?
  • @Ben 我不认为deque 是一个新的数据结构;它在标准库中。你有什么建议?
  • @BlacklightShining - 我同意这是完全公平的竞争,这是解决问题的有效方法。我的意思是,如果未来的读者不能断定他们一次只能跟踪四行,那么他们可能不知道deque 的开头是什么。因此,答案可能会受益于一些说明。公平的?编辑:太棒了。 :D
  • @AvinashRaj 我在这里看到的问题是“如果我正在迭代一个文件,我如何到达当前文件之前的第 4 行?”从一行中解析值的问题是单独的,应该是一个新问题(尽管我确信它已经在这里得到了回答)。
【解决方案2】:

这会将以数字和点开头的行存储到名为prevline 的变量中。仅当re.search 返回匹配对象时,我们才打印prevline

import re
with open("file") as aFile:
    prevline = ""
    for num, line in enumerate(aFile,1):
        m = re.match(r'\d+\.\s*.*', line)                                # stores the match object of the line which starts with a number and a dot
        if m:                                              
            prevline += re.match(r'\d+\.\s*(.*)', line).group()         # If there is any match found then this would append the whole line to the variable prevline. You could also write this line as prevline += m.group()

        searchObj = re.search(r'(\bTotal\b\s+\w+:\s+(\d+\.\d+))', line)  # Search for the line which contains the string Total plus a word plus a colon and a float number
        if searchObj:                                                   # if there is any
            score = float(searchObj.group(2))                           # then the float number is assigned to the variable called score
            if score > 40.0:                                            # Do all the below operations only if the float number we fetched was greater than 40.0
                print "this is the line number: ", num
                print "this is the line", searchObj.group(1)
                print num
                print num-4
                print "found the", prevline
                prevline = ""

输出:

this is on line Total is: 45.5
this is the line number:  8
8
4
found the 1. Hi45
this is on line Total is: 62.1
this is the line number:  20
20
16
found the 2. How144

【讨论】:

  • 您好,谢谢。它有效,但不完全有效,因为它还打印小于 40.0 的分数。我知道 float(searchObj.group(2)) 允许它捕获十进制数,因为我尝试将 1.0 添加到它并且添加工作。但我不明白为什么它也会打印分数小于 40 的行。谢谢
  • @HR123r 你说你需要与当前行一起的“更高的行”将是当前行之前的四行。这个答案使用了与正则表达式匹配的最新行——一个完全不同的条件。如果这是您想要的,请考虑编辑您的问题。
  • @BlacklightShining 是的,它使用最新的行。我假设每个块都有一个 number.Total is: 行。所以它不会产生任何问题。
  • @HR123r 只需将条件if float(searchObj.group(2)) > 40.0 放在顶部即可。
  • @HR123r - 如果您 1. 可以假设您的文件格式正确并且 2. 当您不需要时不要依赖正则表达式来捕获您的文本,则可以完全避免这个问题。该文件似乎是由计算机生成的(仅按行数)。使用deque 解决方案,higherline.split()[1] 获取第一个数字,line.strip('Total is: ').strip('\n') 获取第二个数字。
【解决方案3】:

我建议对 Blacklight Shining 的帖子进行编辑,该帖子基于其 deque 解决方案,但被拒绝,建议改为将其作为答案。下面,我将展示 Blacklight 的解决方案如何解决您的问题,如果您只是盯着它看一会儿。

with open(filename, 'r') as file:
    # Clear: we don't care about checking the first 4 lines for totals.
    # Instead, we just store them for later.
    previousLines = []
    previousLines.append(file.readline())
    previousLines.append(file.readline())
    previousLines.append(file.readline())
    previousLines.append(file.readline())

    # The earliest we should expect a total is at line 5.
    for lineNum, line in enumerate(file, 5):
        if line.startswith('Total is: '):
            prevLine = previousLines[0]
            high_num = prevLine.split()[1] # A
            score = float(line.strip("Total_is: ").strip("\n").strip()) # B

            if score > 40.0:
                # That's all! We've now got everything we need.
                # Display results as shown in example code.
                print "this is the line number : ", lineNum
                print "this is the line ", line.strip('\n')
                print lineNum
                print (lineNum - 4)
                print "found the ", prevLine

        # Critical - remove old line & push current line onto deque.
        previousLines = previousLines[1:] + [line]

我没有利用deque,但我的代码命令式地完成了同样的事情。我认为这不一定是比其他任何一个更好的答案。我发布它是为了展示如何使用非常简单的算法和简单的工具来解决您尝试解决的问题。 (将 Avinash 巧妙的 17 线解决方案与我简化的 18 线解决方案进行比较。)

这种简化的方法不会让任何阅读您的代码的人看起来像个向导,但它也不会意外匹配中间行中的任何内容。如果您对使用正则表达式不满意,那么只需修改 A 和 B 行。通用解决方案仍然有效。

重点是,记住第 4 行后面的内容的一种简单方法是将最后四行存储在内存中。

【讨论】:

  • +1 进行解释。我确实认为 list 在这里是一个不太合适的容器——最后一行相当不雅,每个附加(包括那个)都需要 O(n) 时间——尽管公平地说,只有四个项目,这并不重要.顺便说一句,函数的前四行可以替换为理解 (previous_lines = [file.readline() for _ in range(3)])。感谢您指出我的回答中的一个重要缺陷。
  • @BlacklightShining - 谢谢!我同意 - deque 在实践中更合适。我尝试将输出添加到您的解决方案中,但编辑被拒绝,理由是它改变了您的答案的功能。因此,我想我会展示解决方案背后最基本的想法,而不是复制您的代码并做出大部分多余的答案。 (对于你建议的理解,我的感觉几乎相同。它很简洁,但我试图尽可能地不聪明。)
  • 啊——回顾我的通知队列,我看到了that suggested edit。我也可以理解它被拒绝的原因——虽然它肯定不是“故意破坏”,也没有“破坏帖子”(?!),但它大大改变了我的代码,使其反映这个问题更多。本着为提问者提供完成工作的工具的精神,我打算简单地演示如何在这种情况下使用deque,并将其适合特定情况的任务留给读者作为练习.
猜你喜欢
  • 2017-11-05
  • 1970-01-01
  • 2013-03-05
  • 1970-01-01
  • 2015-04-11
  • 2020-11-30
  • 2022-12-08
  • 2018-02-07
  • 1970-01-01
相关资源
最近更新 更多