遍历行时记住前一行

时间:2018-06-26 00:53:25

标签: python csv

假设我有一个很大的CSV文件,其中包含有关在指定日期和时间是否下雨的数据。一个玩具示例如下。

Day, Time, Rain
1, 0800, 1
1, 0818, 0
1, 0842, 1
1, 0900, 0
2, 0800, 0
2, 0822, 1
2, 0845, 1
2, 0900, 1

我希望编写代码来计算一天下雨的时间比例。我的计算方法如下(从当天的第二次观察开始):

  1. 如果在该观测值与上一个观测值之间没有下雨,则整个时期不算为“下雨”。
  2. 如果在该观测值和上一个观测值之间下雨,则整个时期都算作下雨
  3. 如果该观测值和先前的观测值中有一个正下雨,则将一半时间计为下雨。

例如,在第1天,下雨了30分钟(一半为0800至0818,一半为0818至0842,一半为0842和0900)。在第2天,下雨了49分钟(从0800到0822的一半时间,从0822到0900的降雨)。

如何在Python中快速执行此操作?我知道for line in file:遍历每一行,但不存储前一行的内存。

4 个答案:

答案 0 :(得分:4)

我将维护一个变量,保留最后看到的行,并在每次迭代时对其进行更新:

last_line = None
for line in lines:

    # You need to change this to explicitly check against None if line can be empty
    if not last_line:
        # Handle no last line on the first iteration

    # Use line and last_line

    # Update the last line seen
    last_line = line

您还可以在开始时将last_line默认设置为有效值,这样就无需在循环中进行检查了。但是,这并不总是可行的。

可以想象有更复杂的方法,例如使用自己的版本压缩lines,其中每个元素都偏移一个,但这可能要简单得多。

答案 1 :(得分:1)

[注意:如果这是一次性的,那么我建议使用Carcigenicate的答案]

这对于生成器和yield语句很有用。以下函数将接受您的迭代器,并产生2元组的对,可根据需要使用该对。

生成器功能:

def lineandlast(listish, first=True, last=True):
    iterator = iter(listish)
    lastline = next(iterator)

    if first:
      yield lastline, None

    for line in iterator:
        yield line, lastline
        lastline = line

    if last:
        yield None, lastline

示例:

for line, last in lineandlast([1,2,3,4,5]):
    print(line, last)

输出:

1 None
2 1
3 2
4 3
5 4
None 5

注释:
有两个关键字参数firstlast,可用于控制上述输出示例中第一项和最后一项的输出。

答案 2 :(得分:0)

您可以zip()行,知道上一行和当前行。

演示:

with open('data.csv') as in_file:
    # skip headers
    next(in_file)

    # convert _io_TextIOWrapper object to list
    in_file = list(in_file)

    # print out current and previous lines
    for curr, prev in zip(in_file[1:], in_file):
        print("CURRENT: %s, PREVIOUS: %s" % (curr.strip(), prev.strip()))

输出:

CURRENT: 1, 0818, 0, PREVIOUS: 1, 0800, 1
CURRENT: 1, 0842, 1, PREVIOUS: 1, 0818, 0
CURRENT: 1, 0900, 0, PREVIOUS: 1, 0842, 1
CURRENT: 2, 0800, 0, PREVIOUS: 1, 0900, 0
CURRENT: 2, 0822, 1, PREVIOUS: 2, 0800, 0
CURRENT: 2, 0845, 1, PREVIOUS: 2, 0822, 1
CURRENT: 2, 0900, 1, PREVIOUS: 2, 0845, 1

您还可以使用csv库进行csv文件处理。

答案 3 :(得分:0)

您可以使用itertools.groupby

import itertools, csv, typing
from functools import reduce
def calculate_minutes(stats:typing.List[list]) -> int:
  new_row = [(int(a), list(b)) for a, b in itertools.groupby(stats, key=lambda x:x[-1])]
  result = 0
  for i in range(len(new_row)-1):
    [m, _start], [m1, _end] = new_row[i], new_row[i+1]
    if any([m, m1]):
      a, b = int(_start[-1][0]), int(_end[0][0])
      result += abs((((int(str(a)[0])-int(str(b)[0]))*60)+int(str(a)[1:])-int(str(b)[1:]))//2)
      if m and len(_start) > 1:
        a, b = int(_start[-1][0]), int(_start[0][0])
        result += abs(((int(str(a)[0])-int(str(b)[0]))*60)+int(str(a)[1:])-int(str(b)[1:]))
      if m1 and len(_end) > 1:
        a, b = int(_end[-1][0]), int(_end[0][0])
        result += abs(((int(str(a)[0])-int(str(b)[0]))*60)+int(str(a)[1:])-int(str(b)[1:]))
  return result

with open('filename.csv') as f:
  data = list(csv.reader(f))
  new_results = {a:[c for _, *c in b] for a, b in itertools.groupby(data[1:], key=lambda x:x[0])}
  final_results = {a:calculate_minutes(b) for a, b in new_results.items()}
  print(final_results)

输出:

{'1': 30, '2': 49}