简体   繁体   English

从列表中删除是列表中任何其他数字的倍数的数字的最佳方法是什么

[英]What is the optimal way to remove numbers from a list that are multiples of any other number in the list

I was attempting to generalize an optimal answer to Problem 1 on Project Euler , and realized that while using the inclusion/exclusion method, the answer comes out wrong if you enter in a list where one of the numbers is a multiple of any of the other numbers in the list.我试图概括Project Euler上问题 1 的最佳答案,并意识到在使用包含/排除方法时,如果您输入的列表中的一个数字是另一个数字的倍数,那么答案就会出现错误列表中的数字。

For example, with a limit of 1000, and a list of [3, 5, 6, 8], the answer comes out as 306004, but the answer SHOULD come out as 266824. This is because 6 is a multiple of 3, and needs to be removed.例如,限制为 1000,列表为 [3, 5, 6, 8],答案为 306004,但答案应该为 266824。这是因为 6 是 3 的倍数,并且需要删除。

I came up with the following code to remove extraneous multiples from a list:我想出了以下代码来从列表中删除无关的倍数:

def cleanMults(mults):
    m = sorted(mults)
    x = [m[0]]
    for i in range(len(m) - 1, 0, -1):
        multFound = False
        for j in range(i - 1, -1, -1):
            if m[i] % m[j] == 0: 
                multFound = True
                break
        if multFound == False: x.append(m[i])
    return sorted(x)

Here I'm sorting the list (just in case it's out of order), then starting with the last element in the list, comparing every element to every other element.在这里,我对列表进行排序(以防它乱序),然后从列表中的最后一个元素开始,将每个元素与其他元素进行比较。 If an element being divided by another element results in a remainder of 0, then we set multFound = True, break, and don't add it to the solution list.如果一个元素除以另一个元素导致余数为 0,那么我们设置 multFound = True,break 并且不将其添加到解决方案列表中。 Otherwise, if no divisor is found, we do add it to the list.否则,如果没有找到除数,我们会将其添加到列表中。

My question is, is there a more optimized way to do this?我的问题是,有没有更优化的方法来做到这一点? Even ignoring the sort, this runs in O(n^2) time.即使忽略排序,这也会在 O(n^2) 时间内运行。 I know there is a way to compare two lists in O(n log(n)) time, but this isn't quite the same thing as that.我知道有一种方法可以在 O(n log(n)) 时间内比较两个列表,但这与那并不完全相同。 Anybody have any ideas or solutions here?有人在这里有任何想法或解决方案吗?

At the very least, you need to collect all of the numbers, de-duplicate, sort, and then check lower numbers against higher ones.至少,您需要收集所有数字、去重、排序,然后将较低的数字与较高的数字进行比较。 There are tricks you can use to streamline the process;您可以使用一些技巧来简化流程; you can make bit maps, check primality, etc. However, you still have an inherently O(N^2) process.您可以制作位图、检查素数等。但是,您仍然有一个固有的O(N^2)过程。

However, the faster way to solve the original problem is to take the sums of the individual arithmetic sequences:然而,解决原始问题的更快方法是对各个等差数列求和:

min3 = 3
max3 = (1000 // 3) * 3
cnt3 = max3 // 3
sum3 = (min3 + max3) * cnt3/ 2

min5 = 5
max5 = (1000 // 5) * 5
cnt5 = max5 // 5
sum5 = (min5 + max5) * cnt5 / 2

Now, since you've double-counted everything with both factors, you need to subtract the extra instance of multiples of 15. Compute sum15 in the same fashion.现在,由于您已经用这两个因素重复计算了所有内容,因此您需要减去 15 的倍数的额外实例。以相同的方式计算sum15

Finally:最后:

total = sum3 + sum5 - sum15

To generalize this to even more factors, composite numbers must be subtracted k-1 times, where k is the quantity of factors.要将其推广到更多因子,必须将合数减去k-1次,其中k是因子的数量。 Generalizing this for your general case may give you a solution more time-efficient than removing multiples.将这一点推广到您的一般情况可能会给您一个比删除倍数更省时的解决方案。

In short, doing the direct sequence computations greatly lowers the cost of having extra factors.简而言之,进行直接序列计算大大降低了拥有额外因子的成本。

Does that get you moving?这会让你感动吗?

Here's what I currently have:这是我目前拥有的:

import time 
from math import prod 
from itertools import combinations as co 

def SoM(lim, mul): 
    n = (lim - 1) //mul 
    return (n * (n + 1) * mul) // 2 

def inex(lim, mults): 
    ans = 0 
    for i in range(len(mults)): 
        for j in co(mults, i + 1): 
            ans += (-1)**i * SoM(lim, prod(list(j))) 
    return ans

def cleanMults(mults):
    m = sorted(mults)
    x = [m[0]]
    for i in range(len(m) - 1, 0, -1):
        multFound = False
        for j in range(i - 1, -1, -1):
            if m[i] % m[j] == 0: multFound = True
        if multFound == False: x.append(m[i])
    return sorted(x)

def toString(mults):
    if len(mults) == 1: return str(list(mults)[0])
    s = 'or ' + str(list(mults)[-1])
    for i in range(len(mults) - 2, -1, -1):
        s = str(list(mults)[i]) + ', ' + s
    return s

def SumOfMults(lim, mults):
    #Declare variables
    start = time.time()
    strnums, m = '', cleanMults(mults)

    #Solve the problem
    ans = str(inex(lim, m))
        
    #Print the results
    print('The sum of all of the multiples of ')
    print(toString(mults) + ' below ' + str(lim) + ' is ' + ans + '.')
    print('This took ' + str(time.time() - start) + ' seconds to calculate.')

I'm assuming there are no duplicates, though if there are, all I have to do is cast the list to a set, then back to a list again.我假设没有重复,但如果有,我所要做的就是将列表转换为一个集合,然后再次返回一个列表。

You say that:你说:

"To generalize this to even more factors, composite numbers must be subtracted k-1 times, where k is the quantity of factors." “要将其推广到更多因子,必须将合数减去 k-1 次,其中 k 是因子的数量。”

Can you go into a little more detail of what you mean, with examples?您能否通过示例更详细地说明您的意思?

In an example like the list [3, 5, 26], the 26 is a composite number, and it only has 2 factors (2, and 13), so if I'm understanding correctly, the sum of all factors of 26 up to the limit needs to be subtracted from the total once?在像列表 [3, 5, 26] 这样的示例中,26 是一个合数,它只有 2 个因数(2 和 13),所以如果我理解正确的话,所有 26 的因数之和到极限需要从总数中减去一次?

With my logic, I do currently do the following:按照我的逻辑,我目前正在执行以下操作:

sum3 + sum5 + sum26 - sum78 (26 * 3) - sum130 (26 * 5) - sum15 (3 * 5) + sum1170 (3 * 5 * 26). sum3 + sum5 + sum26 - sum78 (26 * 3) - sum130 (26 * 5) - sum15 (3 * 5) + sum1170 (3 * 5 * 26)。

Are you suggesting there is a more efficient way to calculate this?你是否建议有一种更有效的方法来计算这个?

You can use the gcd function going forward and backward once through the list.您可以使用 gcd function 在列表中前进和后退一次。 This will allow you to identify the divisors (eg 3) for which there is at least one multiple in the list.这将允许您识别列表中至少有一个倍数的除数(例如 3)。 You can then filter the list based on these divisors.然后,您可以根据这些除数过滤列表。

from math import gcd
def cleanMult(A):
    divisors = []
    for d in (1,-1):
        seen = 1
        for a in A[::d]:
            if seen%a: seen = a*seen//gcd(a,seen)
            else:      divisors.append(a)                    
    return [a for a in A if all(d==a or a%d for d in divisors)]

print(cleanMult([3,5,6,8]))
# [3, 5, 8]

print(cleanMult([6,5,9,15,16,12,8]))
# [6, 5, 9, 8]

However, for Euler problem one, there is a much simple way to get the answer:然而,对于欧拉问题一,有一种非常简单的方法可以得到答案:

sum(n for n in range(1,100) if n%5==0 or n%3==0)

You can also generate all multiples and place them in a set to only count them once each:您还可以生成所有倍数并将它们放在一个集合中,以便每个只计算一次:

multiset = set()
N = 1000
for f in [3,5,6,8]:
    multiset.update(range(f,N,f))
total = sum(multiset)
# 266824

or get some inspiration from the sieve or Eratosthenes:或者从筛子或 Eratosthenes 中获得一些灵感:

N=1000
sieve = [0]*N
for f in [3,5,6,8]:
    sieve[f::f] = range(f,N,f)
total = sum(sieve)
# 266824

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM