5
\$\begingroup\$

Some numbers have funny properties. For example:

$$\eqalign{89 &→ 8^1 + 9^2 &= 89 &= 89 × 1 \\ 695 &→ 6^2 + 9^3 + 5^4 &= 1390 &= 695 × 2 \\ 46288 &→ 4^3 + 6^4 + 2^5 + 8^6 + 8^7 &= 2360688 &= 46288 × 51}$$

Given a positive integer \$n\$ written in decimal as \$abcd\dotso\$, and a positive integer \$p\$, we want to find a positive integer \$k\$, if it exists, such that the sum of the digits of \$n\$ taken to the successive powers \$p\$, \$p+1\$, \$p+2\$, …, is equal to \$nk\$. In other words:

Is there an integer \$k\$ such that \$ a^p + b^{p+1} + c^{p+2} + d^{p+3} + \dotsb = nk\$?

If it is the case we will return \$k\$, if not return \$-1\$.

Note: \$n\$, \$p\$ will always be given as strictly positive integers.

  • dig_pow(89, 1) should return \$1\$ since \$8^1 + 9^2 = 89 = 89 × 1\$.
  • dig_pow(92, 1) should return \$-1\$ since there is no \$k\$ such that \$9^1 + 2^2\$ equals \$92k\$
  • dig_pow(695, 2) should return \$2\$ since \$6^2 + 9^3 + 5^4 = 1390 = 695 × 2\$
  • dig_pow(46288, 3) should return \$51\$ since \$4^3 + 6^4 + 2^5 + 8^7 + 8^6 = 2360688 = 46288 × 51\$
import math

def dig_pow(n,p):

    ''' 
    Formula :

    (a ^ p + b ^ (p+1) + c ^(p+2) + d ^ (p+3) + ...) = n * k

    '''

    ''' calculate LHS '''
    digits, temp = [], n
    for i in range(len(str(n))):
        digits.append(temp%10)
        temp = temp / 10

    Tsum = 0
    for i in reversed(digits):
        Tsum = Tsum + math.pow(i,p)
        p = p + 1

    ''' Calculate RHS '''
    if Tsum % n == 0:
        return Tsum / n
    else:
        return -1

print dig_pow(46288, 3)
print dig_pow(89, 1)
print dig_pow(695, 2)
print dig_pow(92, 1)

Please feel free to suggest better/faster approaches.

\$\endgroup\$

2 Answers 2

6
\$\begingroup\$
  1. The name could be more meaningful. It's usually a good idea to name a function after its result, which in this case is the multiplier \$k\$, so something like digit_power_multiplier is needed.

  2. The docstring doesn't explain the arguments to the function, or what it returns. Write it from the point of view of a user who is trying to figure out how to call it.

  3. Only the first string in a function definition becomes the docstring: the others are ignored. So make them comments instead.

  4. Returning exceptional values to indicate failure (here, -1 when no \$k\$ is found) is risky: the caller might forget to check for the exceptional value. It's more reliable to raise an exception.

  5. Python has an exponentiation operator ** so there is no need to use math.pow.

  6. When splitting n into its digits you use len(str(n)) to find the number of digits. But if you're going to call str(n) you may as well extract the digits from the result, as suggested by vertere in another answer.

  7. The division operator / changed its meaning in Python 3: it's now floating-point division. To make your code portable to Python 3, use the floor division operator // when you want the integer part of the quotient.

  8. When simultaneously iterating over a sequence and an index, use enumerate. So instead of:

    Tsum = 0
    for i in digits:
        Tsum = Tsum + math.pow(i,p)
        p = p + 1
    

    write:

    Tsum = 0
    for q, d in enumerate(digits, p):
        Tsum = Tsum + d ** q
    

    [Thanks to Josay in comments for improving this.]

  9. Take advantage of the sum function and write:

    Tsum = sum(d ** q for q, d in enumerate(digits, p))
    

Putting all this together:

def digit_power_multiplier(n,p):
    """Given a positive integer n with decimal digits a, b, c, d, ..., and
    a positive integer p, return k such that:

        a ** p + b ** (p+1) + c ** (p+2) + d ** (p+3) + ... = n * k

    If there is no such k, raise ValueError. For example:

        >>> digit_power_multiplier(695, 2)
        2

    since 6**2 + 9**3 + 5**4 = 1390 = 695 * 2.

    """
    lhs = sum(int(d) ** q for q, d in enumerate(str(n), p))
    if lhs % n == 0:
        return lhs // n
    else:
        raise ValueError("no k such that {} = {}*k".format(lhs, n))
\$\endgroup\$
2
  • \$\begingroup\$ Excellent answer as usual. Out of curiosity, any reason for using zip and count instead of enumerate with a start value ? I'd write sum(int(d) ** p for p, d in enumerate(str(n), p)) for instance. \$\endgroup\$
    – SylvainD
    Jun 10, 2015 at 10:38
  • \$\begingroup\$ @Josay: I forgot about the second argument to enumerate! \$\endgroup\$ Jun 10, 2015 at 10:41
2
\$\begingroup\$

To make your code more pythonic we could modify the calculation of LHS in the following way:

''' calculate LHS '''
digits = [int(i) for i in list(str(n))]

tsum = 0
for i in digits:
    tsum += math.pow(i, p)
    p += 1

We use a list comprehension to build the digits list. The benefit being that later on when building up tsum we no longer have to call reversed on digits.

\$\endgroup\$
1
  • \$\begingroup\$ The call to list is not required. Also, you don't need to create the list digits, you just iterate over the string directly (or use a generator expression). \$\endgroup\$
    – SylvainD
    Jun 10, 2015 at 10:35

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.