使用Python中的二进制搜索比较列表

时间:2015-05-17 22:25:20

标签: python list binary-search

我有2个列表列表x(100万个元素)和y(0.1百万个元素),并希望得到z = x-y。 每个列表由每个4个元素的子列表组成,每个子元素的第一个元素被排序。第一个元素严格增加,不存在重复。 现在我使用列表理解来做到这一点,大约需要6.5小时才能运行它。我想知道什么是最有效的方法,记住我的最终结果也应该是列表。

其次,由于我的所有第一个元素都已排序,我认为进行二分搜索会更好。 二元搜索的想法 - 例如,我有2个大小x = 30和y = 10的列表 我循环遍历y的元素,并使用二进制搜索将每个子列表的第一个元素与x中元素的元素进行比较,当我找到从x列表中删除子列表的匹配项时。 所以预期的输出列表应该包含20个元素。但是我写的代码给了我23(它没有删除最后三个匹配),我不知道它有什么问题。 下面是代码:

def intersection(x,y):
    temp=x[:]
    for i in range(len(y)):
        l=0
        h=len(x)-1
        while l<h:
            mid=l+((h-l)/2)
            if y[i][0]==temp[mid][0]:
                a=y[i]
                x.remove(a)
                break
            elif y[i][0]>temp[mid][0]:
                if l==mid:
                    break
                l=mid
            elif y[i][0]<temp[mid][0]:
                h=mid
    return(x)






X-List input of 30 elements
[[1.0, 25.0, 0.0, 0.0]
[2.0, 0.0, 25.0, 0.0]
[3.0, 0.0, 50.0, 0.0]
[4.0, 50.0, 50.0, 0.0]
[5.0, 50.0, 0.0, 0.0]
[6.0, 0.0, 25.0, 10.0]
[7.0, 25.0, 0.0, 10.0]
[8.0, 50.0, 0.0, 10.0]
[9.0, 50.0, 50.0, 10.0]
[10.0, 0.0, 50.0, 10.0]
[11.0, 0.0, 0.0, 0.0]
[12.0, 0.0, 0.0, 10.0]
[13.0, 17.6776695, 17.6776695, 0.0]
[14.0, 0.0, 34.3113632, 0.0]
[15.0, 25.9780293, 50.0, 0.0]
[16.0, 50.0, 25.9780293, 0.0]
[17.0, 34.3113632, 0.0, 0.0]
[18.0, 17.6776695, 17.6776695, 10.0]
[19.0, 34.3113632, 0.0, 10.0]
[20.0, 50.0, 25.9780293, 10.0]
[21.0, 25.9780293, 50.0, 10.0]
[22.0, 0.0, 34.3113632, 10.0]
[23.0, 11.6599302, 0.0, 0.0]
[24.0, 0.0, 11.6599302, 0.0]
[25.0, 0.0, 11.6599302, 10.0]
[26.0, 11.6599302, 0.0, 10.0]
[27.0, 27.9121876, 27.9121876, 0.0]
[28.0, 27.9121876, 27.9121876, 10.0]
[29.0, 9.77920055, 9.77920055, 0.0]
[30.0, 9.77920055, 9.77920055, 10.0]]
Y -List of 10 elements
[1.0, 25.0, 0.0, 0.0]
[2.0, 0.0, 25.0, 0.0]
[11.0, 0.0, 0.0, 0.0]
[13.0, 17.6776695, 17.6776695, 0.0]
[14.0, 0.0, 34.3113632, 0.0]
[17.0, 34.3113632, 0.0, 0.0]
[23.0, 11.6599302, 0.0, 0.0]
[24.0, 0.0, 11.6599302, 0.0]
[27.0, 27.9121876, 27.9121876, 0.0]
[29.0, 9.77920055, 9.77920055, 0.0]
------------------------------------------------------------------------------------------------------------------------------------------Z list (X-Y) the result should be 20 elements but its gives length as 23 elements. it does not remove the remaining 3 elements from the list.




[[3.0, 0.0, 50.0, 0.0],
 [4.0, 50.0, 50.0, 0.0],
 [5.0, 50.0, 0.0, 0.0],
 [6.0, 0.0, 25.0, 10.0],
 [7.0, 25.0, 0.0, 10.0],
 [8.0, 50.0, 0.0, 10.0],
 [9.0, 50.0, 50.0, 10.0],
 [10.0, 0.0, 50.0, 10.0],
 [12.0, 0.0, 0.0, 10.0],
 [15.0, 25.9780293, 50.0, 0.0],
 [16.0, 50.0, 25.9780293, 0.0],
 [18.0, 17.6776695, 17.6776695, 10.0],
 [19.0, 34.3113632, 0.0, 10.0],
 [20.0, 50.0, 25.9780293, 10.0],
 [21.0, 25.9780293, 50.0, 10.0],
 [22.0, 0.0, 34.3113632, 10.0],
 [24.0, 0.0, 11.6599302, 0.0],
 [25.0, 0.0, 11.6599302, 10.0],
 [26.0, 11.6599302, 0.0, 10.0],
 [27.0, 27.9121876, 27.9121876, 0.0],
 [28.0, 27.9121876, 27.9121876, 10.0],
 [29.0, 9.77920055, 9.77920055, 0.0],
 [30.0, 9.77920055, 9.77920055, 10.0]]

3 个答案:

答案 0 :(得分:0)

如果我理解正确,请使用bisect.bisect_left查找匹配项并删除:

from bisect import bisect_left

for ele in y:
    ind = bisect_left(x, ele)
    if ind < len(x) -1 and x[ind][0] == ele[0]:
        del x[ind]

如果查看source,您可以看到用于bisect_left的代码:

def bisect_left(a, x, lo=0, hi=None):
    """Return the index where to insert item x in list a, assuming a is sorted.

    The return value i is such that all e in a[:i] have e < x, and all e in
    a[i:] have e >= x.  So if x already appears in the list, a.insert(x) will
    insert just before the leftmost x already there.

    Optional args lo (default 0) and hi (default len(a)) bound the
    slice of a to be searched.
    """

    if lo < 0:
        raise ValueError('lo must be non-negative')
    if hi is None:
        hi = len(a)
    while lo < hi:
        mid = (lo+hi)//2
        if a[mid] < x: lo = mid+1
        else: hi = mid
    return lo

您可以将其改编为您自己的代码:

def intersection(x, y):
    for ele in y:
        lo = 0
        hi = len(x)
        while lo < hi:
            mid = (lo+hi)//2
            if x[mid] < ele:
                lo = mid+1
            else:
                hi = mid
        if lo < len(x) - 1 and x[ind][0] == ele[0]:
            del x[lo]
    return x

print(len(intersection(x,y)))
20

如果你有傻瓜,那么你需要使用删除。检查完全匹配的第一个元素是if lo < len(x) - 1 and x[ind][0] == ele[0]:但是如果您使用删除我不知道它是如何工作的,只是因为匹配的第一个元素并不意味着y[i]位于{{ 1}}所以x会失败。因此,如果您只匹配第一个元素,那么您可以更改逻辑并迭代x.remove将每个子列表中的所有第一个元素放入集合中,并使用生成器表达式更新x。

x

答案 1 :(得分:0)

Bisection可以工作,但另一个简单的解决方案是使用set

y_set = set(tuple(v) for v in y)

请注意,list必须变成不可变的东西。

现在只需生成结果:

z = [v for v in x if tuple(v) not in y_set]

这可能与您的初始解决方案非常相似,但此处的查找速度要快得多。

@StefanPochmann有一个很好的观点,你可能希望将查找基于比整个向量更具体的东西,例如只是第一个元素。问题不是很清楚(只说明那些是有分类的)。

答案 2 :(得分:0)

如果您可以使用第一个元素进行过滤:

ykeys = set(zip(*y)[0])
z = [s for s in x if s[0] not in ykeys]

Python 3版本:

ykeys = set(list(zip(*y))[0])
ykeys = {s[0] for s in y}

如果单凭第一个元素判断还不够:

yset = set(map(tuple, y))
return [s for s in x if tuple(s) not in yset]

在我的弱电脑上,通过测试你的尺寸,第一个解决方案大约需要0.4秒,第二个解决方案大约需要1秒钟。从set lookups average O(1))开始,并不令人惊讶。

这是第三个版本,这个版本可能是最有趣的,因为它不仅让Python完成工作,而且因为它更接近你的意图,但更好:

yi, last = 0, len(y) - 1
z = []
for s in x:
    while s > y[yi] and yi < last:
        yi += 1
    if s != y[yi]:
        z.append(s)

这会走过x,“并行”走过y。类似于merge-sort的合并步骤。使用yi我们指向y,我们会根据需要增加它。因此,我们有整体线性时间,因为我们只从头到尾遍历x,从开始到结束都超过y。我的笔记本电脑需要大约0.6秒,这比我的第二个解决方案快! (将它与我的第一个解决方案进行比较是不公平的,因为那个解决方案只关注第一个元素。)