5
\$\begingroup\$

Is there a way to make my code simpler/more pythonic?

Given a non-negative number "num", return True if num is within 2 of a multiple of 10. Note: (a % b) is the remainder of dividing a by b, so (7 % 5) is 2. See also: Introduction to Mod

near_ten(12) → True

near_ten(17) → False

near_ten(19) → True

def near_ten(num):

    a = num % 10
    if  (10 - (10-a)) <= 2 or (10 - a) <= 2:
        return True
    else:
        return False
\$\endgroup\$
7
\$\begingroup\$

I'm no Python expert, so I can't say if this is more or less Pythonic than @MrSmith42's, but it seems just a bit clearer, without the negation - though that may be offset by the modular arithmetic trick. Anyway...

def near_ten(num):
    return (num + 2) % 10 <= 4

And to @Curt's point, it seems a bit faster than all but the numpy version, though without a copy of IPython at hand, I had trouble replicating his tests in quite the same way.

\$\endgroup\$
  • \$\begingroup\$ I really like the idea to shift the problem so it can be simply solved by one compare. \$\endgroup\$ – MrSmith42 Jul 31 '15 at 6:57
10
\$\begingroup\$

You could simplify your code to make it faster and also avoid the 'if-else' construct:

def near_ten(num):
    a = num % 10
    return 8 <= a or 2 >= a

Here's another version: (Thanks to the comment of @mkrieger1)

def near_ten(num): 
    return not(2 < (num % 10) < 8)

I think the name of the function is a misleading. Maybe you should rename it to something like: near_multiple_of_ten(num)

\$\endgroup\$
  • 3
    \$\begingroup\$ Unfortunately, 8 <= a <= 2 doesn't work, but not 2 < a < 8 does! \$\endgroup\$ – mkrieger1 Jul 10 '15 at 12:32
5
\$\begingroup\$

I thought MrSmith42's answers were really compact and readable. But I wanted to see if they were faster.

Also, I wanted to test numpy for comparison. Here's my numpy version:

def near_ten5(num_list):
    a = np.mod(num_list, 10)
    return np.logical_or(2 >= a, 8 <= a)

All the versions presented so far:

def near_ten1(num):
    a = num % 10
    if  (10 - (10-a)) <= 2 or (10 - a) <= 2:
        return True
    else:
        return False

def near_ten2(num):
    a = num % 10
    return 8 <= a or 2 >= a

def near_ten3(num): 
    return not(2 < (num % 10) < 8)

def near_ten4(num):
    return abs(5 - num % 10) >= 3

def near_ten5(num_list):
    a = np.mod(num_list, 10)
    return np.logical_or(2 >= a, 8 <= a)

Code to test them for accuracy and speed:

from random import randint, random
import timeit
import numpy as np

accuracy_test = [-3.4, -2, 0.1, 22, 23]
for f in [near_ten1, near_ten2, near_ten3, near_ten4, near_ten5]:
    print [f(x) for x in accuracy_test] 

timer_test = [random()*randint(0, 20) for _ in xrange(10**5)]

%timeit [near_ten1(n) for n in timer_test]
%timeit [near_ten2(n) for n in timer_test]
%timeit [near_ten3(n) for n in timer_test]
%timeit [near_ten4(n) for n in timer_test]
%timeit near_ten5(timer_test)

Output:

[False, True, True, True, False]
[False, True, True, True, False]
[False, True, True, True, False]
[False, True, True, True, False]
[False, True, True, True, False]
10 loops, best of 3: 36.5 ms per loop
10 loops, best of 3: 26.5 ms per loop
10 loops, best of 3: 25.2 ms per loop
10 loops, best of 3: 28.1 ms per loop
100 loops, best of 3: 3.85 ms per loop

Unsurprisingly, the numpy solution is the fastest, but is admittedly less elegant than what I call near_ten3(), which is the answer from MrSmith42 / mkrieger1.

A couple of other minor comments:

  1. Docstrings would be good!
  2. All the current functions will fail if complex numbers are provided (and obviously also if non-numbers are provided).
\$\endgroup\$
  • \$\begingroup\$ Why do you have the numpy version do 100 loops and everything else only do 10? \$\endgroup\$ – PhatWrat Jul 10 '15 at 18:04
  • \$\begingroup\$ timeit decides how many loops to do based on how long things are taking. Since numpy was faster it did more loops. \$\endgroup\$ – Curt F. Jul 10 '15 at 18:21
  • 1
    \$\begingroup\$ @Curt While numpy is clearly the fastest - by almost an order of magnitude - I think it got a bit of an unfair leg up in your benchmarks, due to the loop over timer_test being inside the function call while all the others had it outside. A similar change to the version in my answer shaved off about a third of the runtime. Nonetheless, very nice analysis work! \$\endgroup\$ – bibach Jul 11 '15 at 22:18
  • \$\begingroup\$ Good point bibach -- it was something I wondered about a bit, but your ocmment convinced me: before I post future benchmarking I'll try to do a better job of "apples to apples" comparison. \$\endgroup\$ – Curt F. Jul 12 '15 at 5:30

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.