我有一个大约10万个排序甚至整数的列表,范围是10 ^ 12到10 ^ 14。我的目标是在列表中找到第一个整数x
,使x*2
也是列表的成员。由于我的列表相当长,速度非常重要。
我的第一个想法是迭代列表并检查每个元素乘以2是否也在列表中,但是在实现它时很明显这对我的目的来说太慢了。
我的下一个想法是使用SymPy的factorint
将列表中的每个元素分解为其主要分解,然后在我的分解列表中搜索相同的分解,除了额外的2。这不是显然更快,但我觉得必须有一种使用素数分解的方法,如果不是别的话。
答案 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)
答案 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