我有两个对象列表。每个列表已经按日期时间类型的对象的属性排序。我想将两个列表合并为一个排序的列表。最好的方法是仅执行排序,还是在Python中有更聪明的方法来执行此操作?
我有两个对象列表。每个列表已经按日期时间类型的对象的属性排序。我想将两个列表合并为一个排序的列表。最好的方法是仅执行排序,还是在Python中有更聪明的方法来执行此操作?
Answers:
人们似乎对此过于复杂了。只需组合两个列表,然后对它们进行排序:
>>> l1 = [1, 3, 4, 7]
>>> l2 = [0, 2, 5, 6, 8, 9]
>>> l1.extend(l2)
>>> sorted(l1)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
..或更短(且无需修改l1
):
>>> sorted(l1 + l2)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
..简单!另外,它仅使用两个内置函数,因此,假设列表的大小合理,则比在循环中实现排序/合并要快。更重要的是,以上代码少得多,而且可读性强。
如果您的列表很大(我猜可能超过几十万),则使用替代/自定义排序方法可能会更快,但是可能首先要进行其他优化(例如,不存储数百万个datetime
对象)
使用timeit.Timer().repeat()
(将功能重复1000000次),我根据ghoseb的解决方案对它进行了宽松的基准测试,并且sorted(l1+l2)
速度更快:
merge_sorted_lists
拿..
[9.7439379692077637, 9.8844599723815918, 9.552299976348877]
sorted(l1+l2)
拿..
[2.860386848449707, 2.7589840888977051, 2.7682540416717529]
有没有更聪明的方式在Python中做到这一点
这没有被提及,所以我继续-python 2.6+的heapq模块中有一个合并stdlib函数。如果您只是想把事情做好,那么这可能是一个更好的主意。当然,如果您想实现自己的方法,则合并合并排序是一种方法。
>>> list1 = [1, 5, 8, 10, 50]
>>> list2 = [3, 4, 29, 41, 45, 49]
>>> from heapq import merge
>>> list(merge(list1, list2))
[1, 3, 4, 5, 8, 10, 29, 41, 45, 49, 50]
这是文档。
merge()
实现为纯python函数,因此很容易将其移植到较早的Python版本。
sorted(l1+l2)
解决方案慢一个数量级。
list.sort
(根据sorted
实现)使用TimSort,后者经过优化以利用基础序列中的现有排序(或反向排序),因此,即使从理论上讲O(n log n)
,在这种情况下,它也更接近O(n)
执行排序。除此之外,CPythonlist.sort
是用C实现的(避免了解释器的开销),而heapq.merge
大多数是用Python实现的,并且以“许多可迭代”情况优化的方式进行了优化,从而减慢了“两个可迭代”情况。
heapq.merge
是它不需要输入或输出list
; 它可以消耗迭代器/生成器并生成一个生成器,因此可以合并大量输入/输出(不立即存储在RAM中)而不会发生交换颠簸。它还可以以比预期低的开销来处理任意数量的输入可迭代项的合并(它使用堆来协调合并,因此开销与可迭代项数的对数成比例地扩展,而不是线性地缩放,但是正如所指出的那样,与“两个可迭代”情况无关)。
长话短说,除非len(l1 + l2) ~ 1000000
使用:
L = l1 + l2
L.sort()
该图的说明和源代码可在此处找到。
该图是通过以下命令生成的:
$ python make-figures.py --nsublists 2 --maxn=0x100000 -s merge_funcs.merge_26 -s merge_funcs.sort_builtin
merge()
为O(n),空间为O(1),并且排序为O( (n log n)时间,整个算法在空间中为O(n)。¶比较现在仅具有历史价值。
heapq.merge
,您正在将其sort
与某人的代码高尔夫球提交进行比较。
merge_26()
来自Python 2.6 heapq模块。
这只是合并。将每个列表视为一个堆栈,并连续弹出两个堆栈头中较小的一个,将该项添加到结果列表中,直到其中一个堆栈为空。然后将所有剩余的项目添加到结果列表中。
ghoseb解决方案中存在一点缺陷,使其为O(n ** 2),而不是O(n)。
问题是这正在执行:
item = l1.pop(0)
使用链接列表或双端队列,这将是O(1)操作,因此不会影响复杂性,但是由于python列表是作为矢量实现的,因此将l1的其余元素复制了一个空格,即O(n)操作。由于每次遍历列表都会完成此操作,因此会将O(n)算法转换为O(n ** 2)算法。可以使用不更改源列表但仅跟踪当前位置的方法来纠正此问题。
我已经尝试过测试dbr建议的校正算法与简单sorted(l1 + l2)的比较
def merge(l1,l2):
if not l1: return list(l2)
if not l2: return list(l1)
# l2 will contain last element.
if l1[-1] > l2[-1]:
l1,l2 = l2,l1
it = iter(l2)
y = it.next()
result = []
for x in l1:
while y < x:
result.append(y)
y = it.next()
result.append(x)
result.append(y)
result.extend(it)
return result
我已经用生成的列表测试了这些
l1 = sorted([random.random() for i in range(NITEMS)])
l2 = sorted([random.random() for i in range(NITEMS)])
对于各种大小的列表,我得到以下计时(重复100次):
# items: 1000 10000 100000 1000000
merge : 0.079 0.798 9.763 109.044
sort : 0.020 0.217 5.948 106.882
因此,实际上,看起来dbr是正确的,除非您期望有非常大的列表,否则最好使用sorted(),尽管它确实具有更差的算法复杂度。收支平衡点在每个来源列表中大约有100万个项目(总计200万个)。
合并方法的一个优点是,将其重写为生成器非常简单,它将使用更少的内存(不需要中间列表)。
[编辑]
我在更接近问题的情况下重试了此方法-使用包含字段“ date
”(日期时间对象)的对象列表。更改了上述算法以进行比较.date
,并将sort方法更改为:
return sorted(l1 + l2, key=operator.attrgetter('date'))
这确实改变了一点。比较起来比较昂贵,这意味着相对于实现的恒定速度,我们执行的数量变得更加重要。这意味着合并弥补了损失,在100,000个项目上超过了sort()方法。基于更复杂的对象(例如较大的字符串或列表)进行比较可能会使这种平衡发生更多的变化。
# items: 1000 10000 100000 1000000[1]
merge : 0.161 2.034 23.370 253.68
sort : 0.111 1.523 25.223 313.20
[1]:注意:对于1,000,000个项目,我实际上只重复了10次,并且由于速度很慢而相应地按比例放大。
这是两个排序列表的简单合并。看一下下面的示例代码,其中合并了两个排序的整数列表。
#!/usr/bin/env python
## merge.py -- Merge two sorted lists -*- Python -*-
## Time-stamp: "2009-01-21 14:02:57 ghoseb"
l1 = [1, 3, 4, 7]
l2 = [0, 2, 5, 6, 8, 9]
def merge_sorted_lists(l1, l2):
"""Merge sort two sorted lists
Arguments:
- `l1`: First sorted list
- `l2`: Second sorted list
"""
sorted_list = []
# Copy both the args to make sure the original lists are not
# modified
l1 = l1[:]
l2 = l2[:]
while (l1 and l2):
if (l1[0] <= l2[0]): # Compare both heads
item = l1.pop(0) # Pop from the head
sorted_list.append(item)
else:
item = l2.pop(0)
sorted_list.append(item)
# Add the remaining of the lists
sorted_list.extend(l1 if l1 else l2)
return sorted_list
if __name__ == '__main__':
print merge_sorted_lists(l1, l2)
这对于datetime对象应该可以正常工作。希望这可以帮助。
head, tail = l[0], l[1:]
也将具有O(n ** 2)复杂度吗?
collections.deque
,它也可以通过创建解决l1
和l2
以相反的顺序(l1 = l1[::-1]
,l2 = l2[::-1]
),然后从右手侧,而不是左侧工作,替换if l1[0] <= l2[0]:
用if l1[-1] <= l2[-1]:
,替换pop(0)
用 pop()
并改变sorted_list.extend(l1 if l1 else l2)
到sorted_list.extend(reversed(l1 if l1 else l2))
from datetime import datetime
from itertools import chain
from operator import attrgetter
class DT:
def __init__(self, dt):
self.dt = dt
list1 = [DT(datetime(2008, 12, 5, 2)),
DT(datetime(2009, 1, 1, 13)),
DT(datetime(2009, 1, 3, 5))]
list2 = [DT(datetime(2008, 12, 31, 23)),
DT(datetime(2009, 1, 2, 12)),
DT(datetime(2009, 1, 4, 15))]
list3 = sorted(chain(list1, list2), key=attrgetter('dt'))
for item in list3:
print item.dt
输出:
2008-12-05 02:00:00
2008-12-31 23:00:00
2009-01-01 13:00:00
2009-01-02 12:00:00
2009-01-03 05:00:00
2009-01-04 15:00:00
我敢打赌,这比任何花哨的纯Python合并算法都要快,即使对于大数据也是如此。Python 2.6完全heapq.merge
是另外一回事了。
Python的排序实现“ timsort”专门针对包含有序部分的列表进行了优化。另外,它是用C编写的。
http://bugs.python.org/file4451/timsort.txt
http://en.wikipedia.org/wiki/Timsort
正如人们所提到的,它可能会以某个常数因子多次调用比较函数(但在许多情况下可能会在较短的时间内多次调用比较函数!)。
但是,我永远不会依靠它。–丹尼尔·纳达斯(Daniel Nadasi)
我相信Python开发人员致力于保持最小排序,或者在这种情况下至少保持O(n)排序。
不能在串行计算机上以小于O(n log n)的方式进行广义排序(即将基数排序与有限值域分开)。–巴里·凯利
是的,一般情况下排序不能比这快。但是,由于O()是一个上限,因此在sorted(L1)+ sorted(L2)的情况下,任意输入上的timsort为O(n log n)并不矛盾。
合并排序中合并步骤的实现,该步骤遍历两个列表:
def merge_lists(L1, L2):
"""
L1, L2: sorted lists of numbers, one of them could be empty.
returns a merged and sorted list of L1 and L2.
"""
# When one of them is an empty list, returns the other list
if not L1:
return L2
elif not L2:
return L1
result = []
i = 0
j = 0
for k in range(len(L1) + len(L2)):
if L1[i] <= L2[j]:
result.append(L1[i])
if i < len(L1) - 1:
i += 1
else:
result += L2[j:] # When the last element in L1 is reached,
break # append the rest of L2 to result.
else:
result.append(L2[j])
if j < len(L2) - 1:
j += 1
else:
result += L1[i:] # When the last element in L2 is reached,
break # append the rest of L1 to result.
return result
L1 = [1, 3, 5]
L2 = [2, 4, 6, 8]
merge_lists(L1, L2) # Should return [1, 2, 3, 4, 5, 6, 8]
merge_lists([], L1) # Should return [1, 3, 5]
我仍在学习算法,请告诉我代码是否可以在任何方面进行改进,感谢您的反馈,谢谢!
使用合并排序的“合并”步骤,它在O(n)时间内运行。
来自维基百科(伪代码):
function merge(left,right)
var list result
while length(left) > 0 and length(right) > 0
if first(left) ≤ first(right)
append first(left) to result
left = rest(left)
else
append first(right) to result
right = rest(right)
end while
while length(left) > 0
append left to result
while length(right) > 0
append right to result
return result
递归实现如下。平均性能为O(n)。
def merge_sorted_lists(A, B, sorted_list = None):
if sorted_list == None:
sorted_list = []
slice_index = 0
for element in A:
if element <= B[0]:
sorted_list.append(element)
slice_index += 1
else:
return merge_sorted_lists(B, A[slice_index:], sorted_list)
return sorted_list + B
或具有更高空间复杂度的发电机:
def merge_sorted_lists_as_generator(A, B):
slice_index = 0
for element in A:
if element <= B[0]:
slice_index += 1
yield element
else:
for sorted_element in merge_sorted_lists_as_generator(B, A[slice_index:]):
yield sorted_element
return
for element in B:
yield element
好吧,天真的方法(将2个列表组合成一个大的列表并进行排序)将是O(N * log(N))复杂性。另一方面,如果您手动实现合并(我不知道为此在python库中有任何现成的代码,但我不是专家),复杂度将为O(N),这显然更快。巴里·凯利(Barry Kelly)在帖子中很好地描述了这个想法。
如果您想以与学习迭代过程更一致的方式进行操作,请尝试以下操作
def merge_arrays(a, b):
l= []
while len(a) > 0 and len(b)>0:
if a[0] < b[0]: l.append(a.pop(0))
else:l.append(b.pop(0))
l.extend(a+b)
print( l )
import random
n=int(input("Enter size of table 1")); #size of list 1
m=int(input("Enter size of table 2")); # size of list 2
tb1=[random.randrange(1,101,1) for _ in range(n)] # filling the list with random
tb2=[random.randrange(1,101,1) for _ in range(m)] # numbers between 1 and 100
tb1.sort(); #sort the list 1
tb2.sort(); # sort the list 2
fus=[]; # creat an empty list
print(tb1); # print the list 1
print('------------------------------------');
print(tb2); # print the list 2
print('------------------------------------');
i=0;j=0; # varialbles to cross the list
while(i<n and j<m):
if(tb1[i]<tb2[j]):
fus.append(tb1[i]);
i+=1;
else:
fus.append(tb2[j]);
j+=1;
if(i<n):
fus+=tb1[i:n];
if(j<m):
fus+=tb2[j:m];
print(fus);
# this code is used to merge two sorted lists in one sorted list (FUS) without
#sorting the (FUS)
使用了合并排序的合并步骤。但是我用过发电机。 时间复杂度 O(n)
def merge(lst1,lst2):
len1=len(lst1)
len2=len(lst2)
i,j=0,0
while(i<len1 and j<len2):
if(lst1[i]<lst2[j]):
yield lst1[i]
i+=1
else:
yield lst2[j]
j+=1
if(i==len1):
while(j<len2):
yield lst2[j]
j+=1
elif(j==len2):
while(i<len1):
yield lst1[i]
i+=1
l1=[1,3,5,7]
l2=[2,4,6,8,9]
mergelst=(val for val in merge(l1,l2))
print(*mergelst)
def compareDate(obj1, obj2):
if obj1.getDate() < obj2.getDate():
return -1
elif obj1.getDate() > obj2.getDate():
return 1
else:
return 0
list = list1 + list2
list.sort(compareDate)
将列表排序到位。定义您自己的用于比较两个对象的函数,然后将该函数传递给内置的sort函数。
不要使用冒泡排序,它具有可怕的性能。
该代码具有时间复杂度O(n),并且可以将任何数据类型的列表合并,并以量化函数为参数func
。它产生一个新的合并列表,并且不修改作为参数传递的两个列表。
def merge_sorted_lists(listA,listB,func):
merged = list()
iA = 0
iB = 0
while True:
hasA = iA < len(listA)
hasB = iB < len(listB)
if not hasA and not hasB:
break
valA = None if not hasA else listA[iA]
valB = None if not hasB else listB[iB]
a = None if not hasA else func(valA)
b = None if not hasB else func(valB)
if (not hasB or a<b) and hasA:
merged.append(valA)
iA += 1
elif hasB:
merged.append(valB)
iB += 1
return merged
希望这可以帮助。非常简单直接:
l1 = [1、3、4、7]
l2 = [0,2,5,6,8,9]
l3 = l1 + l2
l3.sort()
列印(l3)
[0、1、2、3、4、5、6、7、8、9]