如何在大整数列表中快速找到2个列表元素的第一个倍数?

时间:2016-02-29 13:06:54

标签: python python-3.x math

我有一个大约10万个排序甚至整数的列表,范围是10 ^ 12到10 ^ 14。我的目标是在列表中找到第一个整数x,使x*2也是列表的成员。由于我的列表相当长,速度非常重要。

我的第一个想法是迭代列表并检查每个元素乘以2是否也在列表中,但是在实现它时很明显这对我的目的来说太慢了。

我的下一个想法是使用SymPy的factorint将列表中的每个元素分解为其主要分解,然后在我的分解列表中搜索相同的分解,除了额外的2。这不是显然更快,但我觉得必须有一种使用素数分解的方法,如果不是别的话。

5 个答案:

答案 0 :(得分:7)

您可以使用两个迭代器迭代列表:一个指向当前元素,一个指向第一个大于或等于它的double。这将是O(N)。

以下是该想法的草稿:

l = [1, 3, 5, 7, 10, 12, 15]

# ...
j = 0
for i in range(0, len(l)):
    while l[j] < 2*l[i]:
        j += 1
        if j == len(l):
            return -1
    if l[j] == 2*l[i]:
        return i

编辑:在另一个答案中发表评论后,一些表演测试表明,通过消除乘法,调用len并减少此版本,此版本将更快(在我的测试中为3倍)列表中的项目检索数量:

j = 0
s = len(l)
for i in range(0, s):
    l_i = l[i]
    l_i2 = l_i<<1
    while l[j] < l_i2:
        j += 1
        if j == s:
            return -1
    if l[j] == l_i2:
        return i

答案 1 :(得分:3)

Colin Pitrat的解决方案非常好,但我猜你在使用sortedcontainers.SortedSet

时可以超越它

我已生成一个包含1.000.000随机数的文件(使用numpy.random.randint)并检查至少有一个数字(在文件中间),这符合您的条件。

让我们比较两种方法:

import filecmp
from sortedcontainers import SortedSet
from timeit import Timer

def load2list():
    with open('data', 'r') as f:
        return [int(line) for line in f]

def save_result(i, fn):
    with open(fn, 'a') as f:
        print(i, file=f)

def test_Colin_Pitrat():
    j = 0
    for i in range(0, len(l)):
        while l[j] < 2*l[i]:
            j += 1
            if j == len(l):
                return -1
        if l[j] == 2*l[i]:
            save_result(l[i], 'Colin_Pitrat.out')
            return l[i]


def test_SortedSet():
    for i in sorted_set:
        if i<<1 in sorted_set:
            save_result(i, 'SortedSet.out')
            return i
    return -1


if __name__=='__main__':
    timeit_number = 10000
    l = load2list()
    sorted_set = SortedSet(l)
    print('len(l):\t\t%d' % (len(l)))
    print('len(sorted_set):\t%d' % (len(sorted_set)))
    print('Timeit results with %d executions:' %timeit_number)
    print('Colin_Pitrat:\t', Timer(test_Colin_Pitrat).timeit(timeit_number))
    print('SortedSet:\t', Timer(test_SortedSet).timeit(timeit_number))
    print("filecmp.cmp('Colin_Pitrat.out', 'SortedSet.out'):\t%s" % (filecmp.cmp('Colin_Pitrat.out', 'SortedSet.out')))

输出:

len(l):         1000001
len(sorted_set):        999504
Timeit results with 10000 executions:
Colin_Pitrat:    35.94529931032006
SortedSet:       2.548847197918647
filecmp.cmp('Colin_Pitrat.out', 'SortedSet.out'):       True

PS,你可以看到SortedSet非常快。

更新:(现在我在家测试,我的电脑慢得多,所以我会将执行次数减少到1.000)

正如Colin Pitrat建议我现在为最坏的情况生成数据(大约100.000个数字) - 当时找不到匹配项。现在我将比较三个函数:test_Colin_Pitrat,test_Colin_Pitrat2(调优版),test_SortedSet ......

数据生成器脚本:

import numpy as np

l = np.random.randint(10**7, 10**9, 200000)
l = l[ l % 2 > 0 ]
np.savetxt('data', np.sort(l), fmt='%d')

代码:

import filecmp
from sortedcontainers import SortedSet
from timeit import Timer

def load2list():
    with open('data', 'r') as f:
        return [int(line) for line in f]

def save_result(i, fn):
    with open(fn, 'a') as f:
        print(i, file=f)

def test_Colin_Pitrat():
    j = 0
    for i in range(0, len(l)):
        while l[j] < 2*l[i]:
            j += 1
            if j == len(l):
                return -1
        if l[j] == 2*l[i]:
            return l[i]

def test_Colin_Pitrat2():
    j = 0
    s = len(l)
    for i in range(0, s):
        l_i = l[i]
        l_i2 = l_i<<1
        while l[j] < l_i2:
            j += 1
            if j == s:
                return -1
        if l[j] == l_i2:
            return l[j]


def test_SortedSet():
    for i in sorted_set:
        if i<<1 in sorted_set:
            return i
    return -1


if __name__=='__main__':
    timeit_number = 1000
    l = load2list()
    sorted_set = SortedSet(l)
    print('len(l):\t\t%d' % (len(l)))
    print('len(sorted_set):\t%d' % (len(sorted_set)))
    print('Timeit results for %d executions:' %timeit_number)
    print('Colin_Pitrat:\t', Timer(test_Colin_Pitrat).timeit(timeit_number))
    print('Colin_Pitrat2:\t', Timer(test_Colin_Pitrat2).timeit(timeit_number))
    print('SortedSet:\t', Timer(test_SortedSet).timeit(timeit_number))

输出:

len(l):         99909
len(sorted_set):        99899
Timeit results for 1000 executions:
Colin_Pitrat:    153.04753258882357
Colin_Pitrat2:   103.68264272815443
SortedSet:       99.59669211136577

结论:与Colin_Pitrat相比,Colin_Pitrat2快33%,几乎和SortedSet一样快。

答案 2 :(得分:1)

我会再添加一个答案,因为我已经超载了我之前的那个...... 现在我提出了一个新想法 - SortedSets的交集,与修改后的Colin和我以前的解决方案相比,它的工作速度非常快。 想法是生成两个SortedSet: l2 - 是两个列表/集合的交集:原始的l和包含l的所有元素的一个乘以2:SortedSet(对于x中的x表示x * 2)。 l1 - 是一个SortedSet,包含属于l2的所有元素,除以2:SortedSet(对于l2中的x,x // 2) 码: 从datetime导入日期时间为dt 来自sortedcontainers导入SortedSet 来自timeit import Timer def load2list():     打开(&#39;数据&#39;&#39; r&#39;)为f:         return [int(line)for f in f] def test_Colin_Pitrat2():     j = 0     s = len(l)     对于范围(0,s)中的i:         l_i = l [i]         l_i2 = l_i&lt;&lt; 1         而l [j]&lt; l_i2:             j + = 1             如果j == s:                 返回-1         如果l [j] == l_i2:             返回l [i] def test_SortedSet():     for sorted in sorted_set:         如果i&lt;&lt; 1 in sorted_set:             回来我     返回-1 def test_SetIntersection():     对于我在l1:         如果在l2中我* 2:             回来我     返回-1 if __name __ ==&#39; __ main __&#39;:     start_ts = dt.now()     timeit_number = 10000     l = load2list()     print(&#39; load2list()采用:\ t%d微秒&#39;%((dt.now() - start_ts).microseconds))     start_ts = dt.now()     sorted_set = SortedSet(l)     l2 = SortedSet(l).intersection(SortedSet(对于x中的x为2))     l1 = SortedSet(对于l2中的x,x // 2)     打印(&#39;准​​备设置:\ t%d微秒&#39;%((dt.now() - start_ts).microseconds))     print(&#39; len(l):\ t \ t%d&#39;%(len(l)))     print(&#39; len(l1):\ t%d&#39;%(len(l1)))     print(&#39; len(l2):\ t%d&#39;%(len(l2)))     print(&#39; len(sorted_set):\ t%d&#39;%(len(sorted_set)))     打印(&#39;%d执行的Timeit结果:&#39;%timeit_number)     print(&#39; Colin_Pitrat2:\ t \ t&#39;,Timer(test_Colin_Pitrat2).timeit(timeit_number))     print(&#39; SortedSet:\ t \ t&#39;,Timer(test_SortedSet).timeit(timeit_number))     print(&#39; SetIntersection:\ t&#39;,Timer(test_SetIntersection).timeit(timeit_number)) 输出: load2list()占用:230023微秒 准备设置:58106微秒 len(l):498786 len(l1):256 len(l2):256 len(sorted_set):498562 10000次执行的Timeit结果: Colin_Pitrat2:23.557948959065648 SortedSet:6.658937808213555 SetIntersection:0.012540539222982261 PS我真的很喜欢这个问题,因为它给了我一个学习新东西的机会。而且我也喜欢科林的算法 - 它很聪明。我已经对它进行了投票。

答案 3 :(得分:0)

您可以将列表分成两部分,以便下面的列表包含原始列表中第一个数字的最后一个数字,该数字等于或小于列表中最大值的一半。这将加快你的搜索速度,因为你将只迭代列表的一半,并搜索x * 2是否在另一半。参见下面的示例

l = [1, 3, 5, 7, 10, 12, 15]

max_l = l[-1]

for i in range(len(l)):
  if l[i] > max_l/2:
    break
  pos_mid_l = i

print pos_mid_l # returns position 3

lower_l = l[:pos_mid_l+1]
upper_l = l[pos_mid_l+1:]

print lower_l   # returns lower half of your list
print upper_l   # returns upper half of your list

for i in  lower_l:
  if i < upper_l[0]/2:
    if i*2 in lower_l:
      ans = i
      break
  else:
    if i*2 in upper_l:
      ans = i
      break

print ans # Your answer :)

答案 4 :(得分:0)

在我看来,最好的算法会使用预先排序的列表和一组。

#!/usr/local/pypy3-2.4.0/bin/pypy3

import time
import random


def get_random_numbers():
    result = []
    for counter in range(99998):
        random_number = (random.randrange(10**12, 10**14) // 2) * 2
        result.append(random_number)
    # Make sure there's at least one solution to the problem
    moderately_large = 10**14 // 2
    result.append(moderately_large)
    result.append(moderately_large * 2)
    # This sort is of course O(n*logn), but I don't think you're
    # counting that, are you?
    result.sort()
    return result


def get_best(list_):
    # This is O(n)
    set_ = set(list_)
    for value in list_:
        if value * 2 in set_:
            return value
    return None


def main():
    list_ = get_random_numbers()
    time0 = time.time()
    result = get_best(list_)
    time1 = time.time()
    print(result)
    print('{} seconds'.format(time1 - time0))


main()

HTH