如何从 CSV 文件中提取目标行、前一行和后行?
How to extract target row, the row before, and the row after from a CSV File?
我一直在尝试弄清楚如何使用 for
循环和 python 中提供的 enumerate
对象来完成此操作。我的时间格式是HH:MM
。我有一个 csv 文件,其中第一列是时间戳,后面是相同格式的时间戳。然后我在文件中搜索匹配时间,然后提取该行以便稍后转换为 XML 文件。但是,我还需要提取目标行之前和之后的行。我尝试了以下代码:
def findRow(timeID, filename):
rows = []
csvFile = csv.reader(open(filename, "rb"), delimiter=",")
for i, row in enumerate(csvFile):
if timeID == timeInRow:
rows.append(i-1)
rows.append(i)
rows.append(i+1)
return rows
但是,我很快意识到这不是正确的方法,因为我提取的是索引而不是值。我需要的是 row[i-1]、row[i]、row[i+1] 之类的东西。换句话说,我需要与该行匹配的元素。
有没有简单的方法来做到这一点?我考虑过使用 range(csvFile)
但老实说我不知道最终会做什么。
我会使用不同的方法:
- 将前一行存储在循环中
- 如果匹配,则使用
next
获取下一行,return 3 行
像这样(我添加了评论,因为 timeInRow
应该从 row
中提取,但您的代码没有显示它):
prev_row = [] # just in case it matches at first row
for row in csvFile:
# something must be done to extract timeInRow from row here!
if timeID == timeInRow:
return [prev_row,row,next(csvFile,[])]
prev_row = row # save current row for next iteration
next
使用默认的空列表值,以防 last 行匹配(避免 StopIteration
异常)
这种线性方法有效,但如果行按时间排序并且您需要执行多次搜索,更好的方法(更快)可能会创建一个行列表,一个时间列表,然后使用 bisect
模块计算时间列表中的插入点,检查时间是否匹配,并使用索引 return 行列表的一部分。
类似于:
list_of_rows = list(csvFile)
list_of_times = [x[3] for x in list_of_rows] # assume that the time is the 4th column here
i = bisect.bisect(list_of_rows,timeInRow)
if i < len(list_of_rows) and list_of_rows[i] == timeInRow:
return list_of_rows[max(i-1,0):min(i+2,len(list_of_rows)]
如果您只需要执行 1 次搜索,这会比较慢,因为无论如何您都必须创建列表,所以 O(n) + O(log(n))
。但是如果你想在同一个列表中执行多次搜索,每次搜索的成本是O(log(n))
。
您可以为此使用 deque。
鉴于:
$ cat /tmp/file.csv
firstName,lastName,email,phoneNumber
John,Doe,john@doe.com,0123456789
Jane,Doe,jane@doe.com,9876543210
James,Bond,james.bond@mi6.co.uk,0612345678
假设您想要带有 Jane
的行以及之前和之后的行。
尝试:
import csv
from collections import deque
dq=deque([[None] for _ in range(3)],maxlen=3)
with open(fn,'r') as f:
for row in csv.reader(f):
dq.append(row)
if dq[-2][0]=='Jane': break # here you can use your custom function
然后:
>>> dq
deque([['John', 'Doe', 'john@doe.com', '0123456789'], ['Jane', 'Doe', 'jane@doe.com', '9876543210'], ['James', 'Bond', 'james.bond@mi6.co.uk', '0612345678']], maxlen=3)
上述方法的替代(功能)方法是使用 zip
或其变体。类似于:
rows = list(csv.reader(f))
for x, y, z in zip(rows, rows[1:], rows[2:]):
# y is the middle row, x is above it, and z below it
pass
如果您想在迭代中包含前两行和后两行
(None, None, rows[0])
(None, rows[0], rows[1])
(rows[-2], rows[-1], None)
(rows[-1], None, None)
那么您将不得不在 rows
列表的两端加上两个 None 的前置和 post-挂起。
并不是说这一定比其他答案更好,但这是我会考虑编写的另一种方法。
[编辑]
根据 Jean-François 的建议使用 itertools.islice:
rows = list(csv.reader(f))
from itertools import islice
for x, y, z in zip(rows, islice(rows, 1, None), islice(rows, 2, None)):
# y is the middle row, x is above it, and z below it
pass
我一直在尝试弄清楚如何使用 for
循环和 python 中提供的 enumerate
对象来完成此操作。我的时间格式是HH:MM
。我有一个 csv 文件,其中第一列是时间戳,后面是相同格式的时间戳。然后我在文件中搜索匹配时间,然后提取该行以便稍后转换为 XML 文件。但是,我还需要提取目标行之前和之后的行。我尝试了以下代码:
def findRow(timeID, filename):
rows = []
csvFile = csv.reader(open(filename, "rb"), delimiter=",")
for i, row in enumerate(csvFile):
if timeID == timeInRow:
rows.append(i-1)
rows.append(i)
rows.append(i+1)
return rows
但是,我很快意识到这不是正确的方法,因为我提取的是索引而不是值。我需要的是 row[i-1]、row[i]、row[i+1] 之类的东西。换句话说,我需要与该行匹配的元素。
有没有简单的方法来做到这一点?我考虑过使用 range(csvFile)
但老实说我不知道最终会做什么。
我会使用不同的方法:
- 将前一行存储在循环中
- 如果匹配,则使用
next
获取下一行,return 3 行
像这样(我添加了评论,因为 timeInRow
应该从 row
中提取,但您的代码没有显示它):
prev_row = [] # just in case it matches at first row
for row in csvFile:
# something must be done to extract timeInRow from row here!
if timeID == timeInRow:
return [prev_row,row,next(csvFile,[])]
prev_row = row # save current row for next iteration
next
使用默认的空列表值,以防 last 行匹配(避免 StopIteration
异常)
这种线性方法有效,但如果行按时间排序并且您需要执行多次搜索,更好的方法(更快)可能会创建一个行列表,一个时间列表,然后使用 bisect
模块计算时间列表中的插入点,检查时间是否匹配,并使用索引 return 行列表的一部分。
类似于:
list_of_rows = list(csvFile)
list_of_times = [x[3] for x in list_of_rows] # assume that the time is the 4th column here
i = bisect.bisect(list_of_rows,timeInRow)
if i < len(list_of_rows) and list_of_rows[i] == timeInRow:
return list_of_rows[max(i-1,0):min(i+2,len(list_of_rows)]
如果您只需要执行 1 次搜索,这会比较慢,因为无论如何您都必须创建列表,所以 O(n) + O(log(n))
。但是如果你想在同一个列表中执行多次搜索,每次搜索的成本是O(log(n))
。
您可以为此使用 deque。
鉴于:
$ cat /tmp/file.csv
firstName,lastName,email,phoneNumber
John,Doe,john@doe.com,0123456789
Jane,Doe,jane@doe.com,9876543210
James,Bond,james.bond@mi6.co.uk,0612345678
假设您想要带有 Jane
的行以及之前和之后的行。
尝试:
import csv
from collections import deque
dq=deque([[None] for _ in range(3)],maxlen=3)
with open(fn,'r') as f:
for row in csv.reader(f):
dq.append(row)
if dq[-2][0]=='Jane': break # here you can use your custom function
然后:
>>> dq
deque([['John', 'Doe', 'john@doe.com', '0123456789'], ['Jane', 'Doe', 'jane@doe.com', '9876543210'], ['James', 'Bond', 'james.bond@mi6.co.uk', '0612345678']], maxlen=3)
上述方法的替代(功能)方法是使用 zip
或其变体。类似于:
rows = list(csv.reader(f))
for x, y, z in zip(rows, rows[1:], rows[2:]):
# y is the middle row, x is above it, and z below it
pass
如果您想在迭代中包含前两行和后两行
(None, None, rows[0])
(None, rows[0], rows[1])
(rows[-2], rows[-1], None)
(rows[-1], None, None)
那么您将不得不在 rows
列表的两端加上两个 None 的前置和 post-挂起。
并不是说这一定比其他答案更好,但这是我会考虑编写的另一种方法。
[编辑]
根据 Jean-François 的建议使用 itertools.islice:
rows = list(csv.reader(f))
from itertools import islice
for x, y, z in zip(rows, islice(rows, 1, None), islice(rows, 2, None)):
# y is the middle row, x is above it, and z below it
pass