[Tutor] List comprehension question
Richard D. Moores
rdmoores at gmail.com
Tue Nov 9 17:17:55 CET 2010
On Tue, Nov 9, 2010 at 05:51, Martin A. Brown <martin at linux-ip.net> wrote:
>
> Hello,
>
> : def proper_divisors_sum(n):
> : pd_list = []
> : for x in range(1, int(n**.5)+1):
> : if n % x == 0:
> : factor = int(x)
> : pd_list.append(factor)
> : factor2 = int(n/factor)
> : pd_list.append(factor2)
> : pd_list = list(set(pd_list))
> : pd_list.sort()
> : pd_list = pd_list[:-1]
> : return sum(pd_list)
>
> A few questions--noting, of course, that I'm not reading this with
> an eye toward performance, which it seems you are, but these occur
> to me:
>
> * Why use a list (pd_list = []), when you want unique divisors?
> Why not, pd = set() ? Then, instead of pd_list.append(factor),
> pd.add(factor) or something like pd.update((factor,factor2))?
> (See also my suggestion below.)
>
> * Also, since you are throwing away the divisor n, anyway,
> why not skip it from the beginning? Like this:
>
> pd_list = [1,]
> for x in range(2, int(n**.5)+1):
>
> * So, I'm not terribly math aware, but I don't think I have
> substantially changed the meaning of your program. I find
> breaking out the functions makes things a bit clearer to
> somebody who might be reading my code later.
>
> Combining these suggestions and splitting into separate functions
> (you don't really need to sort before summing), I end up with the
> below. Note, that I stuff the proper divisor 1 in the set at
> creation time. This is probably not worth it from an optimization
> perspective, but as I have learned, the only way to know is to time
> it (and I didn't time it).
>
> def proper_divisors_sum(n):
> return sum(proper_divisors(n))
>
> def proper_divisors_sorted(n):
> return sorted(proper_divisors(n))
>
> def proper_divisors(n):
> pd = set((1,))
> for x in range(2, int(n**.5)+1):
> factor, mod = divmod(n,x)
> if mod == 0:
> pd.update((x,factor))
> return list(pd)
Thanks for your ideas, Martin. I adopted your idea of starting at 2,
with an initial list of [1]. It resulted in a small increase in
speed. And you're right, I don't need to sort.
Speed is important, because the function is the heart of my amiable
numbers script. Did you happen to see that post of mine?
#Here's my fastest version before seeing Martin's post:
def proper_divisors_sum1(n):
pd_list = []
for x in range(1, int(n**.5)+1):
if n % x == 0:
pd_list.append(x)
pd_list.append(n//x)
pd_list = list(set(pd_list))
pd_list.sort()
pd_list = pd_list[:-1]
return sum(pd_list)
#This uses several of Martin's ideas
# It's now my fastest version
def proper_divisors_sum2(n):
pd = set((1,))
for x in range(2, int(n**.5)+1):
if n % x == 0:
pd.update((x, n//x))
return sum(list(pd))
Here's what I put together from all your points, except for the
splitting off of several small functions:
# This incorporates all of Martin's ideas
# Seems that divmod() is a speed killer
def proper_divisors_sum3(n):
pd = set((1,))
for x in range(2, int(n**.5)+1):
factor, mod = divmod(n,x)
if mod == 0:
pd.update((x,factor))
return sum(list(pd))
It may be that I've haven't done your suggestions justice, but that
function is 76% slower than proper_divisors_sum2().
See <http://tutoree7.pastebin.com/R82876Eg> for a speed test with n =
100,000 and 100,000 loops
Thanks again, Martin.
Dick
More information about the Tutor
mailing list