【问题标题】:What is the best way to sort a sequence in Python?在 Python 中对序列进行排序的最佳方法是什么?
【发布时间】:2013-07-07 14:44:45
【问题描述】:

我正在尝试根据需要连续发生的某些条件对表格进行排序。 表格的简化版本:

Number  Time
   1    23
   2    45
   3    67
   4    23
   5    11
   6    45
   7    123
   8    34

...

我需要检查时间是否连续 5 次

这是我的代码的一部分:

reader = csv.reader(open(filename))
counter, temp1, temp2, numrow = 0, 0, 0, 0

for row in reader:
    numrow+=1
    if numrow <5:
        col0, col1, col4, col5, col6, col23, col24, col25 = float(row[0]),
            float(row[1]), float(row[4]), float(row[5]),float(row[6]), 
            float(row[23]), float(row[24]), float(row[25])
        if col1 <= 40:
            list1=(col1, col3, col4, col5, col6, col23, col24, col25)
            counter += 1
            if counter == 3:
                print("Cell# %s" %filename[-10:-5])
                print LAYOUT.format(*headers_short)
                print LAYOUT.format(*temp1)
                print LAYOUT.format(*temp2)
                print LAYOUT.format(*list1)
                print ""

            elif counter == 1:
                temp1=list1

            elif counter == 2:
                temp2=list1

        else:
            counter = 0

我实施了 Bakuriu 建议的解决方案,它似乎正在工作。但是,结合众多测试的最佳方式是什么?就像我需要检查几个条件一样。让我们说: v

  • 连续 10 次循环效率低于 40,
  • 连续 5 个循环中小于 40 个的容量
  • 连续 25 个周期少于 40 次
  • 还有其他一些...

现在我只为每次测试打开 csv.reader 并运行该函数。我想这不是最有效的方法,尽管它有效。对不起,我只是个菜鸟。

csvfiles = glob.glob('processed_data/*.stat')
for filename in csvfiles: 

    flag=[]
    flag.append(filename[-12:-5])
    reader = csv.reader(open(filename))
    for a, row_group in enumerate(row_grouper(reader,10)):
        if all(float(row[1]) < 40 for row in row_group):         
            str1= "Efficiency is less than 40 in cycles "+ str(a+1)+'-'+str(a+10)  #i is the index of the first row in the group.
            flag.append(str1)
            break #stop processing other rows.

    reader = csv.reader(open(filename))    
    for b, row_group in enumerate(row_grouper(reader,5)):
        if all(float(row[3]) < 40 for row in row_group):
            str1= "Capacity is less than 40 minutes in cycles "+ str(a+1)+'-'+str(a+5)
            flag.append(str1)
            break #stop processing other rows.

    reader = csv.reader(open(filename))    
    for b, row_group in enumerate(row_grouper(reader,25)):
        if all(float(row[3]) < 40 for row in row_group):
            str1= "Time is less than < 40 in cycles "+ str(a+1)+'-'+str(a+25)
            flag.append(str1)
            break #stop processing other rows.

   if len(flag)>1:

       for i in flag:
            print i
        print '\n'

【问题讨论】:

  • 这是一个非常规的“排序”含义...

标签: python csv conditional


【解决方案1】:

您根本不必对数据进行排序。一个简单的解决方案可能是:

def row_grouper(reader):
    iterrows = iter(reader)
    current = [next(iterrows) for _ in range(5)]
    for next_row in iterrows:
        yield current
        current.pop(0)
        current.append(next_row)


reader = csv.reader(open(filename))

for i, row_group in enumerate(row_grouper(reader)):
    if all(float(row[1]) < 40 for row in row_group):
        print i, i+5  #i is the index of the first row in the group.
        break #stop processing other rows.

row_grouper 函数是一个生成器,可生成由 5 个元素组成的连续行列表。每次它删除组的第一行并在末尾添加新行。


代替普通的list,您可以使用deque 并将row_grouper 中的pop(0) 替换为更有效的popleft() 调用,尽管如果列表只有5 个元素。

或者,您可以使用 martineau 建议并使用 maxlen 关键字参数并避免使用 poping。这大约是使用双端队列的 popleft 的两倍,大约是使用 listpop(0) 的两倍。


编辑:要检查多个条件,您可以修改使用多个row_grouper 并使用itertools.tee 来获取可迭代对象的副本。

例如:

import itertools as it

def check_condition(group, row_index, limit, found):
    if group is None or found:
        return False
    return all(float(row[row_index]) < limit for row in group)


f_iter, s_iter, t_iter = it.tee(iter(reader), 3)

groups = row_grouper(f_iter, 10), row_grouper(s_iter, 5), row_grouper(t_iter, 25)

found_first = found_second = found_third = False

for index, (first, second, third) in enumerate(it.izip_longest(*groups)):
    if check_condition(first, 1, 40, found_first):
        #stuff
        found_first = True
    if check_condition(second, 3, 40, found_second):
        #stuff
        found_second = True
    if check_condition(third, 3, 40, found_third): 
        # stuff
        found_third = True
    if found_first and found_second and found_third:
        #stop the code if we matched all the conditions once.
        break

第一部分只是导入itertools(并分配一个“别名”it 以避免每次都输入itertools)。

我已经定义了check_condition 函数,因为条件越来越复杂,您不想一遍又一遍地重复它们。如您所见,check_condition 的最后一行与之前的条件相同:它检查当前“行组”是否验证了该属性。由于我们计划只对文件进行一次迭代,并且我们不能在只满足一个条件时停止循环(因为我们会错过其他条件),我们必须使用一些标志来告诉我们(例如)时间的条件是否是以前见过与否。在for循环中可以看到,当所有条件都满足时,我们break退出循环。

现在,一行:

f_iter, s_iter, t_iter = it.tee(iter(reader), 3)

reader 的行上创建一个可迭代对象,并复制 3 个副本。 这意味着循环:

for row in f_iter:
    print(row)

将打印文件的所有行,就像 for row in reader 一样。 但请注意,itertools.tee 允许我们获取行的副本 无需多次读取文件。

之后,我们必须将这些行传递给row_grouper 以验证条件:

groups = row_grouper(f_iter, 10), row_grouper(s_iter, 5), row_grouper(t_iter, 25)

最后我们必须遍历“行组”。为了同时做到这一点,我们使用itertools.izip_longest(在python3中重命名为itertools.zip_longest(没有i))。 它就像zip 一样工作,创建元素对(例如zip([1, 2, 3], ["a", "b", "c"]) -&gt; [(1, "a"), (2, "b"), (3, "c")])。不同之处在于izip_longest padsNones 的迭代更短。这确保我们检查所有可能组的条件(这也是为什么check_condition 必须检查group 是否为None)。

为了获取当前行索引,我们将所有内容都包装在enumerate 中,就像以前一样。 在for 中,代码非常简单:您使用check_condition 检查条件,如果满足条件,则执行您必须执行的操作并且您必须为该条件设置标志(因此在以下循环中,条件将始终为False)。

(注意:我必须说我没有测试代码。有时间我会测试它,无论如何我希望我能给你一些想法。并查看itertools的文档。

【讨论】:

  • 本可以使用deque 并在两侧高效弹出/追加。
  • @kroolik 我们正在讨论一个固定(非常小)大小的列表。反正时间是不变的。 (如果您使用 timeit 它,您会发现使用普通的 list 会慢不到 2 倍,考虑到 pop(0) 必须做的转变,这还不错)。
  • 我尝试了这种方法,但脚本在 current = [next(iterrows) for_ in range(5)] 中抛出“无效语法”错误
  • @Pasha 我忘了for_ 之间有一个空格。固定。
  • 好的,知道了。还有另一个错误。只是“row_groupuer”中的一个错字。我现在得到一些输出。该功能似乎正在工作。我还更新了我的帖子,提出了更多关于实现你的功能的问题。如果您能进一步提供帮助,我将不胜感激。谢谢。
【解决方案2】:

您实际上不需要对数据进行排序,只需跟踪您要查找的条件是否已在最后 N 行数据中发生。固定大小的collections.deques 适合这种事情。

import csv
from collections import deque
filename = 'table.csv'
GROUP_SIZE = 5
THRESHOLD = 40
cond_deque = deque(maxlen=GROUP_SIZE)

with open(filename) as datafile:
    reader = csv.reader(datafile) # assume delimiter=','
    reader.next() # skip header row
    for linenum, row in enumerate(reader, start=1):  # process rows of file
        col0, col1, col4, col5, col6, col23, col24, col25 = (
            float(row[i]) for i in (0, 1, 4, 5, 6, 23, 24, 25))
        cond_deque.append(col1 < THRESHOLD)
        if cond_deque.count(True) == GROUP_SIZE:
            print 'lines {}-{} had {} consecutive rows with col1 < {}'.format(
                linenum-GROUP_SIZE+1, linenum, GROUP_SIZE, THRESHOLD)
            break  # found, so stop looking

【讨论】:

  • 很好的答案伙计们。两种方法看起来都不错。我只希望我更精通 Python,这样我至少可以阅读脚本。对 Python 如此陌生是很困难的,而试图在知识很少的情况下完成一个管理项目就更难了。再次感谢。我将尝试在我的脚本中实施您的建议。
  • 这里我也得到一个错误:ValueError: invalid literal for float(): 1,00000005.04428203596,0000041078.87327
  • 看起来您可能需要在csv.reader() 调用上使用delimiter=','(实际上这是默认设置,我使用了tab,因为您的表格的简化版本是如何出现的)。您还可以通过打印row 来查看已读取的内容,以查看正在返回的数据。
  • 对不起,我敢打赌。没有注意分隔符。就我而言,它只是一个逗号。工作得很好。唯一的问题是它打印 4 行而不是 5 行。我想我应该将组大小设为 6,正弦 Python 不包括列表中的最后一项。再次感谢!
  • 不知道你说的只输出 4 行而不是 5 行是什么意思,因为当它找到条件为 True 的 5 个连续行时,唯一的输出是一行。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-09-18
  • 1970-01-01
  • 2010-12-16
  • 2018-11-20
  • 2021-06-09
相关资源
最近更新 更多