6
\$\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\$
9
\$\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.

| improve this answer | |
\$\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
12
\$\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 misleading. Maybe you should rename it to something like: near_multiple_of_ten(num)

| improve this answer | |
\$\endgroup\$
  • 4
    \$\begingroup\$ Unfortunately, 8 <= a <= 2 doesn't work, but not 2 < a < 8 does! \$\endgroup\$ – mkrieger1 Jul 10 '15 at 12:32
7
\$\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).
| improve this answer | |
\$\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

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