7
\$\begingroup\$

I have written a Python function called closest_multiple_10() which takes an integer as input and returns the closest multiple of 10. The function works as expected, but its execution time is quite long, and I'm looking for ways to optimize it. Below is my current implementation:

def closest_multiple_10(i):
    a = i
    b = i
    j = 0
    c = 0
    while a/10 != a//10:
        j+=1
        a+=1
    while b/10 != b//10:
        c+=1
        b-=1
    if j>c:
        return i-c
    else:
        return i+j

I'm seeking suggestions for improvements in the code to make it more efficient and reduce the execution time. Any tips or alternative solutions would be greatly appreciated.

\$\endgroup\$
4
  • 3
    \$\begingroup\$ For 15, is 10 closer or 20? Why loop at all? \$\endgroup\$
    – greybeard
    May 8, 2023 at 22:22
  • \$\begingroup\$ it is a good quetion if 15 is closer to 20 \$\endgroup\$
    – XMehdi01
    May 8, 2023 at 22:23
  • 3
    \$\begingroup\$ Why not return ((a+5) // 10) * 10? \$\endgroup\$
    – Brendan
    May 9, 2023 at 7:40
  • \$\begingroup\$ It's your code, so you should decide what rounding mode you want to use as a tie-break. If negative inputs are possible, keep in mind whether "round up" is "away from zero" or "towards +infinity" when deciding what result you want for every input. See How do I find the next multiple of 10 of any integer? for some algorithms and a table of how they round around -10 and +10. (C's division operator truncates toward zero, unlike Python's integer division. And rounding up is harder than Brendan's point of using a+5 and round down.) \$\endgroup\$ May 9, 2023 at 16:53

5 Answers 5

9
\$\begingroup\$

There is a very easy solution to this problem. The algorithm is:

  1. Get remainder of number with modulo by 10
  2. If remainder is less than 5, return number minus remainder
  3. Else, return number + (10 - remainder)
def closest_multiple_10(x):
    rem = x % 10
    return x - rem if rem < 5 else x + (10 - rem)

You can even expand this function to take in any multiple.

def closest_multiple(x, mul):
    rem = x % mul
    return x - rem if rem < (mul / 2) else x + (mul - rem)
\$\endgroup\$
3
  • 1
    \$\begingroup\$ mul - rem in the generic version. Also mul must be > 1. \$\endgroup\$
    – slepic
    May 9, 2023 at 4:16
  • \$\begingroup\$ @slepic thanks for catching that! \$\endgroup\$
    – Linny
    May 9, 2023 at 6:11
  • \$\begingroup\$ Thank you very much, a wonderful solution from a creative developer \$\endgroup\$
    – XMehdi01
    May 9, 2023 at 13:29
7
\$\begingroup\$

Check out the built-in function round(number, [ndigits]). ndigits can be a negative number, which causes it to round to the left of the decimal point. For example,

round(127, -1) # returns 130

If the most significant digit of the part being rounded is a 5 round() returns the nearest even value

round(125, -1) # returns 120

whereas your code always rounds up, so it returns 130.

If the rounding mode matters, the Decimal module provides a way to set the rounding mode using the context.

import decimal
D = decimal.Decimal

getcontext().rounding = decimal.ROUND_UP

n = D(125)
round(n, -1)  # returns D(1.3E+2)
\$\endgroup\$
0
2
\$\begingroup\$

Let us list the differences between the successive integers and their next multiple:

  0    0    0
  1    0    1
  2    0    2
  3    0    3
  4    0    4
  5   10   -5
  6   10   -4
  7   10   -3
  8   10   -2
  9   10   -1
 10   10    0
 11   10    1
 12   10    2
 13   10    3
 14   10    4
 15   20   -5
 16   20   -4
 17   20   -3
 18   20   -2
     ...

and so on, periodically with period 10. If you add 5 and shift up by 5 positions, you get a repetition of the integers 0 to 9. As you can see, the difference can be expressed as (n + 5) % 10 - 5. So the rounding you are after can be written

(n + 5) - (n + 5) % 10.

More generally,

n+= m >> 1
n-= n % m

I don't know how you want to handle the negatives, so I did not care about this case.

\$\endgroup\$
0
1
\$\begingroup\$

The function has no docstring. I recommend adding one:

def closest_multiple_10(i):
    """Return the nearest multiple of 10 to i,
       rounding up if halfway between two multiples."""

The algorithm is inefficient. Instead of a linear search up and down for a multiple of 10, we can use the % operator to determine exactly how far above an exact multiple we begin. The expression i - (i % 10) will round down to the next lowest multiple. To adapt that to round to the nearest, we can add 10/2 (i.e. 5) before rounding down:

    i += 5
    return i - i % 10

To round it all off, we can add type hints, and some unit tests:

def closest_multiple_10(i: int) -> int:
    """ Return the nearest multiple of 10 to i, rounding up if halfway
    between two multiples.
    >>> closest_multiple_10(0)
    0
    >>> closest_multiple_10(1)
    0
    >>> closest_multiple_10(4)
    0
    >>> closest_multiple_10(5)
    10
    >>> closest_multiple_10(105)
    110
    >>> closest_multiple_10(114)
    110
    >>> closest_multiple_10(-106)
    -110
    >>> closest_multiple_10(-115)
    -110
    """
    i += 5
    return i - i % 10

if __name__ == '__main__':
    import doctest
    doctest.testmod()

Exercise for the reader: add a parameter m to be used instead of the fixed value 10.

\$\endgroup\$
1
  • \$\begingroup\$ peps.python.org/pep-0257 : "For consistency, always use """triple double quotes""" around docstrings." - it's a bit long, too. \$\endgroup\$
    – AcK
    May 9, 2023 at 8:09
0
\$\begingroup\$

I suspect built-in's like round will always be faster, but a simple way to deal with negative numbers (but not negative or zero multipliers) is, with rounding away from 0:

def closest_multiple( number, base ):
    if number < 0: return -closest_multiple( -number, base )
    return base * ( ( number + base // 2 ) // base )
\$\endgroup\$
2
  • 1
    \$\begingroup\$ Welcome to Code Review! You have presented an alternative solution, but haven't reviewed the code. Please edit to show what aspects of the question code prompted you to write this version, and in what ways it's an improvement over the original. It may be worth (re-)reading How to Answer. \$\endgroup\$ May 9, 2023 at 9:45
  • 1
    \$\begingroup\$ @TobySpeight: Seems to me that the actual problem is the question being posted on the wrong site: it's asking for a better algorithm, not really for a code-review. It's written as a Stack Overflow question. \$\endgroup\$ May 9, 2023 at 16:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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