给出两个排序的数组,如下所示:
a = array([1,2,4,5,6,8,9])
b = array([3,4,7,10])
我希望输出为:
c = array([1,2,3,4,5,6,7,8,9,10])
或:
c = array([1,2,3,4,4,5,6,7,8,9,10])
我知道我可以做到以下几点:
c = unique(concatenate((a,b))
我只是想知道是否有更快的方法来实现它,因为我正在处理的数组有数百万个元素。
欢迎任何想法。感谢
答案 0 :(得分:23)
既然你使用numpy,我怀疑bisec对你有所帮助......所以我会建议两件小事:
np.sort
,而是使用c.sort()
方法对数组进行排序并避免复制。np.unique
必须使用未到位的np.sort
。因此,不要使用np.unique
手动执行逻辑。 IE浏览器。首先排序(就地)然后手动执行np.unique
方法(也检查其python代码),flag = np.concatenate(([True], ar[1:] != ar[:-1]))
使用unique = ar[flag]
(ar被排序)。为了更好一点,你应该让标志操作本身,即。 flag = np.ones(len(ar), dtype=bool)
然后np.not_equal(ar[1:], ar[:-1], out=flag[1:])
,基本上避免了flag
的完整副本。.sort
有3种不同的算法,因为你的阵列几乎已经排序了,改变排序方法可能会产生速度差异。这样可以使你完全接近你所得到的东西(事先没有做出独特的事情):
def insort(a, b, kind='mergesort'):
# took mergesort as it seemed a tiny bit faster for my sorted large array try.
c = np.concatenate((a, b)) # we still need to do this unfortunatly.
c.sort(kind=kind)
flag = np.ones(len(c), dtype=bool)
np.not_equal(c[1:], c[:-1], out=flag[1:])
return c[flag]
答案 1 :(得分:10)
将元素插入到array
的中间是一个非常低效的操作,因为它们在内存中是平坦的,因此无论何时插入另一个元素,您都需要移动所有内容。因此,您可能不想使用bisect
。这样做的复杂性大约是O(N^2)
。
您当前的方法是O(n*log(n))
,因此效果要好得多,但并不完美。
将所有元素插入哈希表(例如set
)是有道理的。这需要花费O(N)
时间进行统一,但是你需要排序O(n*log(n))
。仍然不是很好。
真正的O(N)
解决方案涉及分配一个数组,然后通过获取输入列表的最小头部,即一次填充一个元素,即。合并。不幸的是,numpy
和Python似乎都没有这样的东西。解决方案可能是在Cython中编写一个。
它看起来模糊如下:
def foo(numpy.ndarray[int, ndim=1] out,
numpy.ndarray[int, ndim=1] in1,
numpy.ndarray[int, ndim=1] in2):
cdef int i = 0
cdef int j = 0
cdef int k = 0
while (i!=len(in1)) or (j!=len(in2)):
# set out[k] to smaller of in[i] or in[j]
# increment k
# increment one of i or j
答案 2 :(得分:6)
当对时间充满好奇时,最好只timeit
。下面,我列出了各种方法及其时间的一部分:
import numpy as np
import timeit
import heapq
def insort(a, x, lo=0, hi=None):
if hi is None: hi = len(a)
while lo < hi:
mid = (lo+hi)//2
if x < a[mid]: hi = mid
else: lo = mid+1
return lo, np.insert(a, lo, [x])
size=10000
a = np.array(range(size))
b = np.array(range(size))
def op(a,b):
return np.unique(np.concatenate((a,b)))
def martijn(a,b):
c = np.copy(a)
lo = 0
for i in b:
lo, c = insort(c, i, lo)
return c
def martijn2(a,b):
c = np.zeros(len(a) + len(b), a.dtype)
for i, v in enumerate(heapq.merge(a, b)):
c[i] = v
def larsmans(a,b):
return np.array(sorted(set(a) | set(b)))
def larsmans_mod(a,b):
return np.array(set.union(set(a),b))
def sebastian(a, b, kind='mergesort'):
# took mergesort as it seemed a tiny bit faster for my sorted large array try.
c = np.concatenate((a, b)) # we still need to do this unfortunatly.
c.sort(kind=kind)
flag = np.ones(len(c), dtype=bool)
np.not_equal(c[1:], c[:-1], out=flag[1:])
return c[flag]
结果:
martijn2 25.1079499722
OP 1.44831800461
larsmans 9.91507601738
larsmans_mod 5.87612199783
sebastian 3.50475311279e-05
我在这里的具体贡献是larsmans_mod
,它可以避免创建2个集合 - 它只创建1个,这样做可以将执行时间减少近一半。
编辑已移除martijn
,因为它太慢而无法参与竞争。还测试了稍微更大的数组(已排序)输入。我还没有测试输出的正确性......
答案 3 :(得分:4)
除了使用bisect.insort
的其他答案之外,如果您对性能不满意,可以尝试将blist
模块与bisect
一起使用。它应该提高性能。
传统list
insertion complexity is O(n)
,而blist
's complexity on insertion is O(log(n))
。
此外,您的数组似乎已排序。如果是这样,您可以使用merge
mudule中的heapq
函数来利用两个数组都预先排序的事实。由于在内存中装入新数组,这种方法会占用开销。这可能是一个选择,因为此解决方案的时间复杂度为O(n+m)
,而具有insort的解决方案的复杂度为O(n*m)
(n个元素* m插入)
import heapq
a = [1,2,4,5,6,8,9]
b = [3,4,7,10]
it = heapq.merge(a,b) #iterator consisting of merged elements of a and b
L = list(it) #list made of it
print(L)
输出:
[1, 2, 3, 4, 4, 5, 6, 7, 8, 9, 10]
如果要删除重复值,可以使用groupby:
import heapq
import itertools
a = [1,2,4,5,6,8,9]
b = [3,4,7,10]
it = heapq.merge(a,b) #iterator consisting of merged elements of a and b
it = (k for k,v in itertools.groupby(it))
L = list(it) #list made of it
print(L)
输出:
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
答案 4 :(得分:1)
您可以使用bisect
module进行此类合并,将第二个python列表合并到第一个。
bisect*
函数适用于numpy数组,但insort*
函数不适用。使用module source code来调整算法很容易,这很基本:
from numpy import array, copy, insert
def insort(a, x, lo=0, hi=None):
if hi is None: hi = len(a)
while lo < hi:
mid = (lo+hi)//2
if x < a[mid]: hi = mid
else: lo = mid+1
return lo, insert(a, lo, [x])
a = array([1,2,4,5,6,8,9])
b = array([3,4,7,10])
c = copy(a)
lo = 0
for i in b:
lo, c = insort(c, i, lo)
自定义insort
并不是真的在这里添加任何内容,默认bisect.bisect
也可以正常使用:
import bisect
c = copy(a)
lo = 0
for i in b:
lo = bisect.bisect(c, i)
c = insert(c, i, lo)
使用此适应性insort
比组合和排序更有效。由于b
也已排序,我们可以跟踪lo
插入点并搜索从那里开始的下一个点,而不是每个循环都考虑整个数组。
如果您不需要保留a
,只需直接在该阵列上操作并自行保存副本。
效率更高:因为两个列表都已排序,我们可以使用heapq.merge
:
from numpy import zeros
import heapq
c = zeros(len(a) + len(b), a.dtype)
for i, v in enumerate(heapq.merge(a, b)):
c[i] = v
答案 5 :(得分:1)
import bisect
a = array([1,2,4,5,6,8,9])
b = array([3,4,7,10])
for i in b:
pos = bisect.bisect(a, i)
insert(a,[pos],i)
我现在无法测试,但should工作
答案 6 :(得分:1)
sortednp包实现了已排序的numpy数组的有效合并,仅对值进行排序,而不使它们唯一:
import numpy as np
import sortednp
a = np.array([1,2,4,5,6,8,9])
b = np.array([3,4,7,10])
c = sortednp.merge(a, b)
我测量了时间并在this answer to a similar post中进行了比较,结果优于numpy的mergesort(v1.17.4)。
答案 7 :(得分:0)
似乎没有人提及union1d
(union1d)。目前,它是unique(concatenate((ar1, ar2)))
的快捷方式,但它是一个值得记住的简短名称,它有可能被numpy开发人员优化,因为它具有库函数。它与seberg接受的大型数组答案的执行情况非常相似insort
。这是我的基准:
import numpy as np
def insort(a, b, kind='mergesort'):
# took mergesort as it seemed a tiny bit faster for my sorted large array try.
c = np.concatenate((a, b)) # we still need to do this unfortunatly.
c.sort(kind=kind)
flag = np.ones(len(c), dtype=bool)
np.not_equal(c[1:], c[:-1], out=flag[1:])
return c[flag]
size = int(1e7)
a = np.random.randint(np.iinfo(np.int).min, np.iinfo(np.int).max, size)
b = np.random.randint(np.iinfo(np.int).min, np.iinfo(np.int).max, size)
np.testing.assert_array_equal(insort(a, b), np.union1d(a, b))
import timeit
repetitions = 20
print("insort: %.5fs" % (timeit.timeit("insort(a, b)", "from __main__ import a, b, insort", number=repetitions)/repetitions,))
print("union1d: %.5fs" % (timeit.timeit("np.union1d(a, b)", "from __main__ import a, b; import numpy as np", number=repetitions)/repetitions,))
我机器上的输出:
insort: 1.69962s
union1d: 1.66338s