Python program to find Armstrong numbers in a certain rangeCalculation of the distance to the next space stationNecklace counting problem-with consecutive prime constraintFinding the smallest number whose digits sum to NArmstrong numbers in CProject Euler #43Find good numbers in a given rangeCodeFights Quora botSudoku-lite challengeFind “prime polynomials” for a user's given prime, bounds, and degreePython program for “0-1 knapsack problem”

Grade-school elementary algebra presented in an abstract-algebra style?

How can I make an argument that my time is valuable?

Mysterious procedure calls without parameters - but no exceptions generated

Is this statement about cut time correct?

Popcorn is the only acceptable snack to consume while watching a movie

Why did Theresa May offer a vote on a second Brexit referendum?

Must a warlock replace spells with new spells of exactly their Pact Magic spell slot level?

Is it legal to meet with potential future employers in the UK, whilst visiting from the USA

WordPress 5.2.1 deactivated my jQuery

Is the Unsullied name meant to be ironic? How did it come to be?

Is superuser the same as root?

Natural Armour and Weapons

What are the conditions for RAA?

Can I tell a prospective employee that everyone in the team is leaving?

How do I superimpose two math symbols?

How to patch glass cuts in a bicycle tire?

What could a self-sustaining lunar colony slowly lose that would ultimately prove fatal?

Manager questioning my time estimates for a project

Why isn't Tyrion mentioned in the in-universe book "A Song of Ice and Fire"?

includegraphics: get the "scale" value of a figure whose size is expressed by "width"

Why was this character made Grand Maester?

Make 24 using exactly three 3s

Why is the Eisenstein ideal paper so great?

Why didn't Thanos use the Time Stone to stop the Avengers' plan?



Python program to find Armstrong numbers in a certain range


Calculation of the distance to the next space stationNecklace counting problem-with consecutive prime constraintFinding the smallest number whose digits sum to NArmstrong numbers in CProject Euler #43Find good numbers in a given rangeCodeFights Quora botSudoku-lite challengeFind “prime polynomials” for a user's given prime, bounds, and degreePython program for “0-1 knapsack problem”






.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty,.everyoneloves__bot-mid-leaderboard:empty margin-bottom:0;








2












$begingroup$


I have written a Python program to check for Armstrong numbers in a certain range.



An Armstrong number of three digits is an integer such that the sum of the cubes of its digits is equal to the number itself. For example, 371 is an Armstrong number since 3**3 + 7**3 + 1**3 = 371.



Here is my code:



lower = int(input("Enter lower range: "))
upper = int(input("Enter upper range: "))

for num in range(lower, upper + 1):

order = len(str(num))

sum = 0

temp = num
while temp > 0:
digit = temp % 10
sum += digit ** order
temp //= 10

if num == sum:
print(num)


Here is an example output:



Enter lower range: 200
Enter upper range: 5000

370
371
407
1634


So, I would like to know whether I could make this program shorter and more efficient.



Any help would be highly appreciated.










share|improve this question











$endgroup$











  • $begingroup$
    "sum of the cubes of its digits"? The power should be equal to the number of digits.
    $endgroup$
    – Sedsarq
    14 hours ago










  • $begingroup$
    @Sedsarq - taken from - pages.mtu.edu/~shene/COURSES/cs201/NOTES/chap04/arms.html
    $endgroup$
    – Justin
    14 hours ago










  • $begingroup$
    @Sedsarq - Yes, you are also right - the power should be equal to the number of digits.
    $endgroup$
    – Justin
    14 hours ago










  • $begingroup$
    @vurmux - I thought the performance tag was necessary as a larger range takes more time to print the output and therefore I needed the code to be as efficient as possible.
    $endgroup$
    – Justin
    14 hours ago






  • 1




    $begingroup$
    You're right, so I rolled back the removal.
    $endgroup$
    – Mast
    14 hours ago

















2












$begingroup$


I have written a Python program to check for Armstrong numbers in a certain range.



An Armstrong number of three digits is an integer such that the sum of the cubes of its digits is equal to the number itself. For example, 371 is an Armstrong number since 3**3 + 7**3 + 1**3 = 371.



Here is my code:



lower = int(input("Enter lower range: "))
upper = int(input("Enter upper range: "))

for num in range(lower, upper + 1):

order = len(str(num))

sum = 0

temp = num
while temp > 0:
digit = temp % 10
sum += digit ** order
temp //= 10

if num == sum:
print(num)


Here is an example output:



Enter lower range: 200
Enter upper range: 5000

370
371
407
1634


So, I would like to know whether I could make this program shorter and more efficient.



Any help would be highly appreciated.










share|improve this question











$endgroup$











  • $begingroup$
    "sum of the cubes of its digits"? The power should be equal to the number of digits.
    $endgroup$
    – Sedsarq
    14 hours ago










  • $begingroup$
    @Sedsarq - taken from - pages.mtu.edu/~shene/COURSES/cs201/NOTES/chap04/arms.html
    $endgroup$
    – Justin
    14 hours ago










  • $begingroup$
    @Sedsarq - Yes, you are also right - the power should be equal to the number of digits.
    $endgroup$
    – Justin
    14 hours ago










  • $begingroup$
    @vurmux - I thought the performance tag was necessary as a larger range takes more time to print the output and therefore I needed the code to be as efficient as possible.
    $endgroup$
    – Justin
    14 hours ago






  • 1




    $begingroup$
    You're right, so I rolled back the removal.
    $endgroup$
    – Mast
    14 hours ago













2












2








2


1



$begingroup$


I have written a Python program to check for Armstrong numbers in a certain range.



An Armstrong number of three digits is an integer such that the sum of the cubes of its digits is equal to the number itself. For example, 371 is an Armstrong number since 3**3 + 7**3 + 1**3 = 371.



Here is my code:



lower = int(input("Enter lower range: "))
upper = int(input("Enter upper range: "))

for num in range(lower, upper + 1):

order = len(str(num))

sum = 0

temp = num
while temp > 0:
digit = temp % 10
sum += digit ** order
temp //= 10

if num == sum:
print(num)


Here is an example output:



Enter lower range: 200
Enter upper range: 5000

370
371
407
1634


So, I would like to know whether I could make this program shorter and more efficient.



Any help would be highly appreciated.










share|improve this question











$endgroup$




I have written a Python program to check for Armstrong numbers in a certain range.



An Armstrong number of three digits is an integer such that the sum of the cubes of its digits is equal to the number itself. For example, 371 is an Armstrong number since 3**3 + 7**3 + 1**3 = 371.



Here is my code:



lower = int(input("Enter lower range: "))
upper = int(input("Enter upper range: "))

for num in range(lower, upper + 1):

order = len(str(num))

sum = 0

temp = num
while temp > 0:
digit = temp % 10
sum += digit ** order
temp //= 10

if num == sum:
print(num)


Here is an example output:



Enter lower range: 200
Enter upper range: 5000

370
371
407
1634


So, I would like to know whether I could make this program shorter and more efficient.



Any help would be highly appreciated.







python performance python-3.x






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited 11 hours ago







Justin

















asked 15 hours ago









JustinJustin

401117




401117











  • $begingroup$
    "sum of the cubes of its digits"? The power should be equal to the number of digits.
    $endgroup$
    – Sedsarq
    14 hours ago










  • $begingroup$
    @Sedsarq - taken from - pages.mtu.edu/~shene/COURSES/cs201/NOTES/chap04/arms.html
    $endgroup$
    – Justin
    14 hours ago










  • $begingroup$
    @Sedsarq - Yes, you are also right - the power should be equal to the number of digits.
    $endgroup$
    – Justin
    14 hours ago










  • $begingroup$
    @vurmux - I thought the performance tag was necessary as a larger range takes more time to print the output and therefore I needed the code to be as efficient as possible.
    $endgroup$
    – Justin
    14 hours ago






  • 1




    $begingroup$
    You're right, so I rolled back the removal.
    $endgroup$
    – Mast
    14 hours ago
















  • $begingroup$
    "sum of the cubes of its digits"? The power should be equal to the number of digits.
    $endgroup$
    – Sedsarq
    14 hours ago










  • $begingroup$
    @Sedsarq - taken from - pages.mtu.edu/~shene/COURSES/cs201/NOTES/chap04/arms.html
    $endgroup$
    – Justin
    14 hours ago










  • $begingroup$
    @Sedsarq - Yes, you are also right - the power should be equal to the number of digits.
    $endgroup$
    – Justin
    14 hours ago










  • $begingroup$
    @vurmux - I thought the performance tag was necessary as a larger range takes more time to print the output and therefore I needed the code to be as efficient as possible.
    $endgroup$
    – Justin
    14 hours ago






  • 1




    $begingroup$
    You're right, so I rolled back the removal.
    $endgroup$
    – Mast
    14 hours ago















$begingroup$
"sum of the cubes of its digits"? The power should be equal to the number of digits.
$endgroup$
– Sedsarq
14 hours ago




$begingroup$
"sum of the cubes of its digits"? The power should be equal to the number of digits.
$endgroup$
– Sedsarq
14 hours ago












$begingroup$
@Sedsarq - taken from - pages.mtu.edu/~shene/COURSES/cs201/NOTES/chap04/arms.html
$endgroup$
– Justin
14 hours ago




$begingroup$
@Sedsarq - taken from - pages.mtu.edu/~shene/COURSES/cs201/NOTES/chap04/arms.html
$endgroup$
– Justin
14 hours ago












$begingroup$
@Sedsarq - Yes, you are also right - the power should be equal to the number of digits.
$endgroup$
– Justin
14 hours ago




$begingroup$
@Sedsarq - Yes, you are also right - the power should be equal to the number of digits.
$endgroup$
– Justin
14 hours ago












$begingroup$
@vurmux - I thought the performance tag was necessary as a larger range takes more time to print the output and therefore I needed the code to be as efficient as possible.
$endgroup$
– Justin
14 hours ago




$begingroup$
@vurmux - I thought the performance tag was necessary as a larger range takes more time to print the output and therefore I needed the code to be as efficient as possible.
$endgroup$
– Justin
14 hours ago




1




1




$begingroup$
You're right, so I rolled back the removal.
$endgroup$
– Mast
14 hours ago




$begingroup$
You're right, so I rolled back the removal.
$endgroup$
– Mast
14 hours ago










3 Answers
3






active

oldest

votes


















4












$begingroup$

Names



Using sum as a variable name is not adviced as it hides the sum builtin. I'd suggest sum_pow as an alternative.



Also, temp does not convey much information. I'd use remaining even though I am not fully convinced.



Extracting digits from a number



You've used divisions and modulo to compute the different digits for a number. You can use divmod which is a pretty unknown builtin which returns the result for both operations in one go.



That would give:



 remaining = num
while remaining:
remaining, digit = divmod(remaining, 10)
sum_pow += digit ** order


Also, a better alternative would be to avoid doing this ourselves: extracting the different digits is pretty much what str does for us. Also, we already call it anyway, we could make the most out of it and reuse the result.



 num_str = str(num)
order = len(num_str)
sum_pow = 0
for digit in num_str:
sum_pow += int(digit) ** order


More builtins



Disclaimer: next comment may be a bit overwhelming for a beginner. Don't worry, just take your time and read documentation online if need be.



We've already delegated most of the hard work to Python builtins but we can go further. The summing part could be handled by the sum builtin along with generator expressions.



for num in range(lower, upper + 1):
num_str = str(num)
order = len(num_str)
sum_pow = sum(int(digit) ** order for digit in num_str)
if num == sum_pow:
print(num)


Going further



A few other things could be improved from an organisation point of view:



  • split the code in functions

  • use the if name == "main" check

Going even further, we could write unit tests for the logic.






share|improve this answer









$endgroup$












  • $begingroup$
    Upvoted! Thanks for the very detailed response!
    $endgroup$
    – Justin
    13 hours ago


















4












$begingroup$

Python has a good standart module for work with decimal numbers: decimal. Your code (still C/C++-style) can be replaced with this code:



import decimal

# A separated function to check if the number is Armstrong number
def is_armstrong(number):
# Get tuple of number digits
num_digits = decimal.Decimal(number).as_tuple().digits
# Return the result of check if it is an Armstrong number
return number == sum(d ** len(num_digits) for d in num_digits)

lower = int(input("Enter lower range: "))
upper = int(input("Enter upper range: "))

for num in range(lower, upper):
if is_armstrong(num):
print(num)



About perfomance:



I checked how long your code works for one million numbers:



import datetime

t1 = datetime.datetime.now()
for num in range(1, 1000000):
order = len(str(num))
sum_ = 0
temp = num
while temp > 0:
digit = temp % 10
sum_ += digit ** order
temp //= 10
if num == sum_:
q = num
t2 = datetime.datetime.now()
str(t2-t1)


And it returns me:



'0:00:02.568923'



Two and the half seconds. I think it is not the program one should think about perfomance. Moreover, the complexity of each is_armstrong() call is O(log(N)) (we summarize powers O(1) of digits O(log(N))) for each number so the result complexity is O(N log(N)). For one BILLION numbers this script will work less than hour! It is uncomparable with, for example, some kind of graph algorithms with O(N^3 E^2) complexity that works for days and every little improvement can save literally hours of CPU working.



P.S. If you can't understand what I am talking about, check this article in Wikipedia.






share|improve this answer










New contributor



vurmux is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.





$endgroup$












  • $begingroup$
    Upvoted! Some really good stuff in here!
    $endgroup$
    – Justin
    13 hours ago






  • 1




    $begingroup$
    Good answer, I did not know the Decimal module. Minor point that could be improved: "return (True if expression else False)" could be "return bool(expresion)" or even "return express".
    $endgroup$
    – Josay
    13 hours ago










  • $begingroup$
    Definetly! Thank you! I thought that it can be simplified but missed it somehow.
    $endgroup$
    – vurmux
    13 hours ago


















3












$begingroup$

I'm going to focus on the performance aspect, as I believe other parts already have good answers. While the program may be simple and performant, it is still fun to go in and micro-optimize a little. In general I would recommend against it.



Here is your code put into a function to make it clear what changes in the future. I've made the function return the numbers since that was easier for me to work with. I don't have anything new that I would suggest changing.



def armstrong(lower, upper):
armstrong_numbers = []
for num in range(lower, upper + 1):
order = len(str(num))
sum = 0

temp = num
while temp > 0:
digit = temp % 10
sum += digit ** order
temp //= 10

if num == sum:
armstrong_numbers.append(num)

return armstrong_numbers


Precompute



A google search says another name for these numbers are narcissistic numbers and that there are only a finite number of them (88 to be exact). We could make a list of the numbers, loop over the list and return the numbers between lower and upper. This only works if someone else has done the work and generated all the numbers already.



Precompute a little



The above point could be useful though, the first 9 armstrong numbers are the numbers 1 through 9. Let's "precompute" as many of those as we need. We should really add a check to make sure lower <= upper, but alas...



def armstrong(lower, upper):
cutoff = min(10, upper + 1)
armstrong_numbers = list(range(lower, cutoff))
if lower < 10:
lower = 10

for num in range(lower, upper + 1):
order = len(str(num))
sum = 0

temp = num
while temp > 0:
digit = temp % 10
sum += digit ** order
temp //= 10

if num == sum:
armstrong_numbers.append(num)

return armstrong_numbers


Optimize the powers



Another good point that showed up when googling armstrong numbers is that no number bigger than 1060 can be an armstrong number. This means we can work out every possible answer for digitorder ahead of time and reuse it each loop. This should be useful as I think computing arbitrary powers is not as fast as looking up the answer in a table.



As plainly as I can state it, there are only 10 digits, and order is at most 60, so we can make a table 10 * 60 big that stores all the answers to digit**order and use that instead.



def armstrong(lower, upper):
cutoff = min(10, upper + 1)
armstrong_numbers = list(range(lower, cutoff))
if lower < 10:
lower = 10

powers_table = [[d ** n for d in range(10)] for n in range(60)]

for num in range(lower, upper + 1):
order = len(str(num))
row = powers_table[order] # We only care about one row of the table at a time.
sum = 0

temp = num
while temp > 0:
digit = temp % 10
sum += row[digit]
temp //= 10

if num == sum:
armstrong_numbers.append(num)

return armstrong_numbers


Check less numbers



The last idea that I found online (see section 5) is to skip numbers with certain prefixes. We can guarantee a number will never work if it the sum of all of its digits except the last one is odd.



The reason for this is as follows. Raising a number to a power won't change its parity. In other words if a number x is even, xn is also even. If x is odd xn is also odd. The sum of the digits raised to a power n will have the same parity as the sum of the digits. For example if we have the 3 digit number 18X. The sum of it's digits cubed is 13 (odd) + 83 (even) + X**3 which is the same as 1 (odd) + 8 (even) + X.



Assume the sum of all the digits of an armstrong number excluding the last digit is odd then we have either



(A**n + B**n + C**n + ...W**n) + X**n == odd + X**n == odd if X is even or
(A**n + B**n + C**n + ...W**n) + X**n == odd + X**n == even if X is odd


But if the last digit (X) is even, the sum has to be even it to which it isn't.
If the last digit is odd, the sum has to be odd, but it isn't. Either way, we get a contradiction, so our assumption must be wrong.



The code is a bit messy, but it gives the idea. It agrees with the other snippets above for the query (1, 100000)



def armstrong3(lower, upper): 
cutoff = min(10, upper + 1)
armstrong_numbers = list(range(lower, cutoff))
if lower < 10:
lower = 10

powers_table = [[d ** n for d in range(10)] for n in range(60)]

start, end = lower // 10, upper // 10
for leading_digits in range(start, end + 1):
if sum(c in "13579" for c in str(leading_digits)) % 2 == 1:
# Skip numbers with an odd sum parity
continue

order = len(str(leading_digits)) + 1 # We will add a last digit later
row = powers_table[order] # We only care about one row of the table at a time.
sum = 0

temp = leading_digits
while temp > 0:
digit = temp % 10
sum += row[digit]
temp //= 10

for last_digit in range(10):
final_total = sum + row[last_digit]
if 10 * leading_digits + last_digit == final_total and final_total <= upper:
armstrong_numbers.append(num)

return armstrong_numbers


Micro-benchmarked locally I get the following



%timeit armstrong(1, 100000)
143 ms ± 104 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit armstrong2(1, 100000)
69.4 ms ± 2.52 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit armstrong3(1, 100000)
14.9 ms ± 31.4 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)


So the extra tuning was worth it.



Other work



I didn't get around to implementing the ideas at this github project. The code is in java and run with N < 10 at the smallest (above you can see my benchmarks were only for N < 5), so I don't think the performance is anywhere near as good as that code. It would be the next place to go if you are interested in pushing things further.



I looked at using divmod instead of modding and dividing by 10. The performance was worse for me, so I chose not to use it.



%timeit armstrong(1, 100000)
143 ms ± 104 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit armstrong_divmod(1, 100000)
173 ms ± 5.5 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)





share|improve this answer









$endgroup$












  • $begingroup$
    Upvoted! Thanks for the amazing response. It helped me a lot.
    $endgroup$
    – Justin
    3 hours ago











Your Answer






StackExchange.ifUsing("editor", function ()
StackExchange.using("externalEditor", function ()
StackExchange.using("snippets", function ()
StackExchange.snippets.init();
);
);
, "code-snippets");

StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "196"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);

StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);

else
createEditor();

);

function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader:
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
,
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);



);













draft saved

draft discarded


















StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f220738%2fpython-program-to-find-armstrong-numbers-in-a-certain-range%23new-answer', 'question_page');

);

Post as a guest















Required, but never shown

























3 Answers
3






active

oldest

votes








3 Answers
3






active

oldest

votes









active

oldest

votes






active

oldest

votes









4












$begingroup$

Names



Using sum as a variable name is not adviced as it hides the sum builtin. I'd suggest sum_pow as an alternative.



Also, temp does not convey much information. I'd use remaining even though I am not fully convinced.



Extracting digits from a number



You've used divisions and modulo to compute the different digits for a number. You can use divmod which is a pretty unknown builtin which returns the result for both operations in one go.



That would give:



 remaining = num
while remaining:
remaining, digit = divmod(remaining, 10)
sum_pow += digit ** order


Also, a better alternative would be to avoid doing this ourselves: extracting the different digits is pretty much what str does for us. Also, we already call it anyway, we could make the most out of it and reuse the result.



 num_str = str(num)
order = len(num_str)
sum_pow = 0
for digit in num_str:
sum_pow += int(digit) ** order


More builtins



Disclaimer: next comment may be a bit overwhelming for a beginner. Don't worry, just take your time and read documentation online if need be.



We've already delegated most of the hard work to Python builtins but we can go further. The summing part could be handled by the sum builtin along with generator expressions.



for num in range(lower, upper + 1):
num_str = str(num)
order = len(num_str)
sum_pow = sum(int(digit) ** order for digit in num_str)
if num == sum_pow:
print(num)


Going further



A few other things could be improved from an organisation point of view:



  • split the code in functions

  • use the if name == "main" check

Going even further, we could write unit tests for the logic.






share|improve this answer









$endgroup$












  • $begingroup$
    Upvoted! Thanks for the very detailed response!
    $endgroup$
    – Justin
    13 hours ago















4












$begingroup$

Names



Using sum as a variable name is not adviced as it hides the sum builtin. I'd suggest sum_pow as an alternative.



Also, temp does not convey much information. I'd use remaining even though I am not fully convinced.



Extracting digits from a number



You've used divisions and modulo to compute the different digits for a number. You can use divmod which is a pretty unknown builtin which returns the result for both operations in one go.



That would give:



 remaining = num
while remaining:
remaining, digit = divmod(remaining, 10)
sum_pow += digit ** order


Also, a better alternative would be to avoid doing this ourselves: extracting the different digits is pretty much what str does for us. Also, we already call it anyway, we could make the most out of it and reuse the result.



 num_str = str(num)
order = len(num_str)
sum_pow = 0
for digit in num_str:
sum_pow += int(digit) ** order


More builtins



Disclaimer: next comment may be a bit overwhelming for a beginner. Don't worry, just take your time and read documentation online if need be.



We've already delegated most of the hard work to Python builtins but we can go further. The summing part could be handled by the sum builtin along with generator expressions.



for num in range(lower, upper + 1):
num_str = str(num)
order = len(num_str)
sum_pow = sum(int(digit) ** order for digit in num_str)
if num == sum_pow:
print(num)


Going further



A few other things could be improved from an organisation point of view:



  • split the code in functions

  • use the if name == "main" check

Going even further, we could write unit tests for the logic.






share|improve this answer









$endgroup$












  • $begingroup$
    Upvoted! Thanks for the very detailed response!
    $endgroup$
    – Justin
    13 hours ago













4












4








4





$begingroup$

Names



Using sum as a variable name is not adviced as it hides the sum builtin. I'd suggest sum_pow as an alternative.



Also, temp does not convey much information. I'd use remaining even though I am not fully convinced.



Extracting digits from a number



You've used divisions and modulo to compute the different digits for a number. You can use divmod which is a pretty unknown builtin which returns the result for both operations in one go.



That would give:



 remaining = num
while remaining:
remaining, digit = divmod(remaining, 10)
sum_pow += digit ** order


Also, a better alternative would be to avoid doing this ourselves: extracting the different digits is pretty much what str does for us. Also, we already call it anyway, we could make the most out of it and reuse the result.



 num_str = str(num)
order = len(num_str)
sum_pow = 0
for digit in num_str:
sum_pow += int(digit) ** order


More builtins



Disclaimer: next comment may be a bit overwhelming for a beginner. Don't worry, just take your time and read documentation online if need be.



We've already delegated most of the hard work to Python builtins but we can go further. The summing part could be handled by the sum builtin along with generator expressions.



for num in range(lower, upper + 1):
num_str = str(num)
order = len(num_str)
sum_pow = sum(int(digit) ** order for digit in num_str)
if num == sum_pow:
print(num)


Going further



A few other things could be improved from an organisation point of view:



  • split the code in functions

  • use the if name == "main" check

Going even further, we could write unit tests for the logic.






share|improve this answer









$endgroup$



Names



Using sum as a variable name is not adviced as it hides the sum builtin. I'd suggest sum_pow as an alternative.



Also, temp does not convey much information. I'd use remaining even though I am not fully convinced.



Extracting digits from a number



You've used divisions and modulo to compute the different digits for a number. You can use divmod which is a pretty unknown builtin which returns the result for both operations in one go.



That would give:



 remaining = num
while remaining:
remaining, digit = divmod(remaining, 10)
sum_pow += digit ** order


Also, a better alternative would be to avoid doing this ourselves: extracting the different digits is pretty much what str does for us. Also, we already call it anyway, we could make the most out of it and reuse the result.



 num_str = str(num)
order = len(num_str)
sum_pow = 0
for digit in num_str:
sum_pow += int(digit) ** order


More builtins



Disclaimer: next comment may be a bit overwhelming for a beginner. Don't worry, just take your time and read documentation online if need be.



We've already delegated most of the hard work to Python builtins but we can go further. The summing part could be handled by the sum builtin along with generator expressions.



for num in range(lower, upper + 1):
num_str = str(num)
order = len(num_str)
sum_pow = sum(int(digit) ** order for digit in num_str)
if num == sum_pow:
print(num)


Going further



A few other things could be improved from an organisation point of view:



  • split the code in functions

  • use the if name == "main" check

Going even further, we could write unit tests for the logic.







share|improve this answer












share|improve this answer



share|improve this answer










answered 14 hours ago









JosayJosay

26.4k14087




26.4k14087











  • $begingroup$
    Upvoted! Thanks for the very detailed response!
    $endgroup$
    – Justin
    13 hours ago
















  • $begingroup$
    Upvoted! Thanks for the very detailed response!
    $endgroup$
    – Justin
    13 hours ago















$begingroup$
Upvoted! Thanks for the very detailed response!
$endgroup$
– Justin
13 hours ago




$begingroup$
Upvoted! Thanks for the very detailed response!
$endgroup$
– Justin
13 hours ago













4












$begingroup$

Python has a good standart module for work with decimal numbers: decimal. Your code (still C/C++-style) can be replaced with this code:



import decimal

# A separated function to check if the number is Armstrong number
def is_armstrong(number):
# Get tuple of number digits
num_digits = decimal.Decimal(number).as_tuple().digits
# Return the result of check if it is an Armstrong number
return number == sum(d ** len(num_digits) for d in num_digits)

lower = int(input("Enter lower range: "))
upper = int(input("Enter upper range: "))

for num in range(lower, upper):
if is_armstrong(num):
print(num)



About perfomance:



I checked how long your code works for one million numbers:



import datetime

t1 = datetime.datetime.now()
for num in range(1, 1000000):
order = len(str(num))
sum_ = 0
temp = num
while temp > 0:
digit = temp % 10
sum_ += digit ** order
temp //= 10
if num == sum_:
q = num
t2 = datetime.datetime.now()
str(t2-t1)


And it returns me:



'0:00:02.568923'



Two and the half seconds. I think it is not the program one should think about perfomance. Moreover, the complexity of each is_armstrong() call is O(log(N)) (we summarize powers O(1) of digits O(log(N))) for each number so the result complexity is O(N log(N)). For one BILLION numbers this script will work less than hour! It is uncomparable with, for example, some kind of graph algorithms with O(N^3 E^2) complexity that works for days and every little improvement can save literally hours of CPU working.



P.S. If you can't understand what I am talking about, check this article in Wikipedia.






share|improve this answer










New contributor



vurmux is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.





$endgroup$












  • $begingroup$
    Upvoted! Some really good stuff in here!
    $endgroup$
    – Justin
    13 hours ago






  • 1




    $begingroup$
    Good answer, I did not know the Decimal module. Minor point that could be improved: "return (True if expression else False)" could be "return bool(expresion)" or even "return express".
    $endgroup$
    – Josay
    13 hours ago










  • $begingroup$
    Definetly! Thank you! I thought that it can be simplified but missed it somehow.
    $endgroup$
    – vurmux
    13 hours ago















4












$begingroup$

Python has a good standart module for work with decimal numbers: decimal. Your code (still C/C++-style) can be replaced with this code:



import decimal

# A separated function to check if the number is Armstrong number
def is_armstrong(number):
# Get tuple of number digits
num_digits = decimal.Decimal(number).as_tuple().digits
# Return the result of check if it is an Armstrong number
return number == sum(d ** len(num_digits) for d in num_digits)

lower = int(input("Enter lower range: "))
upper = int(input("Enter upper range: "))

for num in range(lower, upper):
if is_armstrong(num):
print(num)



About perfomance:



I checked how long your code works for one million numbers:



import datetime

t1 = datetime.datetime.now()
for num in range(1, 1000000):
order = len(str(num))
sum_ = 0
temp = num
while temp > 0:
digit = temp % 10
sum_ += digit ** order
temp //= 10
if num == sum_:
q = num
t2 = datetime.datetime.now()
str(t2-t1)


And it returns me:



'0:00:02.568923'



Two and the half seconds. I think it is not the program one should think about perfomance. Moreover, the complexity of each is_armstrong() call is O(log(N)) (we summarize powers O(1) of digits O(log(N))) for each number so the result complexity is O(N log(N)). For one BILLION numbers this script will work less than hour! It is uncomparable with, for example, some kind of graph algorithms with O(N^3 E^2) complexity that works for days and every little improvement can save literally hours of CPU working.



P.S. If you can't understand what I am talking about, check this article in Wikipedia.






share|improve this answer










New contributor



vurmux is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.





$endgroup$












  • $begingroup$
    Upvoted! Some really good stuff in here!
    $endgroup$
    – Justin
    13 hours ago






  • 1




    $begingroup$
    Good answer, I did not know the Decimal module. Minor point that could be improved: "return (True if expression else False)" could be "return bool(expresion)" or even "return express".
    $endgroup$
    – Josay
    13 hours ago










  • $begingroup$
    Definetly! Thank you! I thought that it can be simplified but missed it somehow.
    $endgroup$
    – vurmux
    13 hours ago













4












4








4





$begingroup$

Python has a good standart module for work with decimal numbers: decimal. Your code (still C/C++-style) can be replaced with this code:



import decimal

# A separated function to check if the number is Armstrong number
def is_armstrong(number):
# Get tuple of number digits
num_digits = decimal.Decimal(number).as_tuple().digits
# Return the result of check if it is an Armstrong number
return number == sum(d ** len(num_digits) for d in num_digits)

lower = int(input("Enter lower range: "))
upper = int(input("Enter upper range: "))

for num in range(lower, upper):
if is_armstrong(num):
print(num)



About perfomance:



I checked how long your code works for one million numbers:



import datetime

t1 = datetime.datetime.now()
for num in range(1, 1000000):
order = len(str(num))
sum_ = 0
temp = num
while temp > 0:
digit = temp % 10
sum_ += digit ** order
temp //= 10
if num == sum_:
q = num
t2 = datetime.datetime.now()
str(t2-t1)


And it returns me:



'0:00:02.568923'



Two and the half seconds. I think it is not the program one should think about perfomance. Moreover, the complexity of each is_armstrong() call is O(log(N)) (we summarize powers O(1) of digits O(log(N))) for each number so the result complexity is O(N log(N)). For one BILLION numbers this script will work less than hour! It is uncomparable with, for example, some kind of graph algorithms with O(N^3 E^2) complexity that works for days and every little improvement can save literally hours of CPU working.



P.S. If you can't understand what I am talking about, check this article in Wikipedia.






share|improve this answer










New contributor



vurmux is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.





$endgroup$



Python has a good standart module for work with decimal numbers: decimal. Your code (still C/C++-style) can be replaced with this code:



import decimal

# A separated function to check if the number is Armstrong number
def is_armstrong(number):
# Get tuple of number digits
num_digits = decimal.Decimal(number).as_tuple().digits
# Return the result of check if it is an Armstrong number
return number == sum(d ** len(num_digits) for d in num_digits)

lower = int(input("Enter lower range: "))
upper = int(input("Enter upper range: "))

for num in range(lower, upper):
if is_armstrong(num):
print(num)



About perfomance:



I checked how long your code works for one million numbers:



import datetime

t1 = datetime.datetime.now()
for num in range(1, 1000000):
order = len(str(num))
sum_ = 0
temp = num
while temp > 0:
digit = temp % 10
sum_ += digit ** order
temp //= 10
if num == sum_:
q = num
t2 = datetime.datetime.now()
str(t2-t1)


And it returns me:



'0:00:02.568923'



Two and the half seconds. I think it is not the program one should think about perfomance. Moreover, the complexity of each is_armstrong() call is O(log(N)) (we summarize powers O(1) of digits O(log(N))) for each number so the result complexity is O(N log(N)). For one BILLION numbers this script will work less than hour! It is uncomparable with, for example, some kind of graph algorithms with O(N^3 E^2) complexity that works for days and every little improvement can save literally hours of CPU working.



P.S. If you can't understand what I am talking about, check this article in Wikipedia.







share|improve this answer










New contributor



vurmux is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.








share|improve this answer



share|improve this answer








edited 13 hours ago





















New contributor



vurmux is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.








answered 14 hours ago









vurmuxvurmux

3509




3509




New contributor



vurmux is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.




New contributor




vurmux is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.













  • $begingroup$
    Upvoted! Some really good stuff in here!
    $endgroup$
    – Justin
    13 hours ago






  • 1




    $begingroup$
    Good answer, I did not know the Decimal module. Minor point that could be improved: "return (True if expression else False)" could be "return bool(expresion)" or even "return express".
    $endgroup$
    – Josay
    13 hours ago










  • $begingroup$
    Definetly! Thank you! I thought that it can be simplified but missed it somehow.
    $endgroup$
    – vurmux
    13 hours ago
















  • $begingroup$
    Upvoted! Some really good stuff in here!
    $endgroup$
    – Justin
    13 hours ago






  • 1




    $begingroup$
    Good answer, I did not know the Decimal module. Minor point that could be improved: "return (True if expression else False)" could be "return bool(expresion)" or even "return express".
    $endgroup$
    – Josay
    13 hours ago










  • $begingroup$
    Definetly! Thank you! I thought that it can be simplified but missed it somehow.
    $endgroup$
    – vurmux
    13 hours ago















$begingroup$
Upvoted! Some really good stuff in here!
$endgroup$
– Justin
13 hours ago




$begingroup$
Upvoted! Some really good stuff in here!
$endgroup$
– Justin
13 hours ago




1




1




$begingroup$
Good answer, I did not know the Decimal module. Minor point that could be improved: "return (True if expression else False)" could be "return bool(expresion)" or even "return express".
$endgroup$
– Josay
13 hours ago




$begingroup$
Good answer, I did not know the Decimal module. Minor point that could be improved: "return (True if expression else False)" could be "return bool(expresion)" or even "return express".
$endgroup$
– Josay
13 hours ago












$begingroup$
Definetly! Thank you! I thought that it can be simplified but missed it somehow.
$endgroup$
– vurmux
13 hours ago




$begingroup$
Definetly! Thank you! I thought that it can be simplified but missed it somehow.
$endgroup$
– vurmux
13 hours ago











3












$begingroup$

I'm going to focus on the performance aspect, as I believe other parts already have good answers. While the program may be simple and performant, it is still fun to go in and micro-optimize a little. In general I would recommend against it.



Here is your code put into a function to make it clear what changes in the future. I've made the function return the numbers since that was easier for me to work with. I don't have anything new that I would suggest changing.



def armstrong(lower, upper):
armstrong_numbers = []
for num in range(lower, upper + 1):
order = len(str(num))
sum = 0

temp = num
while temp > 0:
digit = temp % 10
sum += digit ** order
temp //= 10

if num == sum:
armstrong_numbers.append(num)

return armstrong_numbers


Precompute



A google search says another name for these numbers are narcissistic numbers and that there are only a finite number of them (88 to be exact). We could make a list of the numbers, loop over the list and return the numbers between lower and upper. This only works if someone else has done the work and generated all the numbers already.



Precompute a little



The above point could be useful though, the first 9 armstrong numbers are the numbers 1 through 9. Let's "precompute" as many of those as we need. We should really add a check to make sure lower <= upper, but alas...



def armstrong(lower, upper):
cutoff = min(10, upper + 1)
armstrong_numbers = list(range(lower, cutoff))
if lower < 10:
lower = 10

for num in range(lower, upper + 1):
order = len(str(num))
sum = 0

temp = num
while temp > 0:
digit = temp % 10
sum += digit ** order
temp //= 10

if num == sum:
armstrong_numbers.append(num)

return armstrong_numbers


Optimize the powers



Another good point that showed up when googling armstrong numbers is that no number bigger than 1060 can be an armstrong number. This means we can work out every possible answer for digitorder ahead of time and reuse it each loop. This should be useful as I think computing arbitrary powers is not as fast as looking up the answer in a table.



As plainly as I can state it, there are only 10 digits, and order is at most 60, so we can make a table 10 * 60 big that stores all the answers to digit**order and use that instead.



def armstrong(lower, upper):
cutoff = min(10, upper + 1)
armstrong_numbers = list(range(lower, cutoff))
if lower < 10:
lower = 10

powers_table = [[d ** n for d in range(10)] for n in range(60)]

for num in range(lower, upper + 1):
order = len(str(num))
row = powers_table[order] # We only care about one row of the table at a time.
sum = 0

temp = num
while temp > 0:
digit = temp % 10
sum += row[digit]
temp //= 10

if num == sum:
armstrong_numbers.append(num)

return armstrong_numbers


Check less numbers



The last idea that I found online (see section 5) is to skip numbers with certain prefixes. We can guarantee a number will never work if it the sum of all of its digits except the last one is odd.



The reason for this is as follows. Raising a number to a power won't change its parity. In other words if a number x is even, xn is also even. If x is odd xn is also odd. The sum of the digits raised to a power n will have the same parity as the sum of the digits. For example if we have the 3 digit number 18X. The sum of it's digits cubed is 13 (odd) + 83 (even) + X**3 which is the same as 1 (odd) + 8 (even) + X.



Assume the sum of all the digits of an armstrong number excluding the last digit is odd then we have either



(A**n + B**n + C**n + ...W**n) + X**n == odd + X**n == odd if X is even or
(A**n + B**n + C**n + ...W**n) + X**n == odd + X**n == even if X is odd


But if the last digit (X) is even, the sum has to be even it to which it isn't.
If the last digit is odd, the sum has to be odd, but it isn't. Either way, we get a contradiction, so our assumption must be wrong.



The code is a bit messy, but it gives the idea. It agrees with the other snippets above for the query (1, 100000)



def armstrong3(lower, upper): 
cutoff = min(10, upper + 1)
armstrong_numbers = list(range(lower, cutoff))
if lower < 10:
lower = 10

powers_table = [[d ** n for d in range(10)] for n in range(60)]

start, end = lower // 10, upper // 10
for leading_digits in range(start, end + 1):
if sum(c in "13579" for c in str(leading_digits)) % 2 == 1:
# Skip numbers with an odd sum parity
continue

order = len(str(leading_digits)) + 1 # We will add a last digit later
row = powers_table[order] # We only care about one row of the table at a time.
sum = 0

temp = leading_digits
while temp > 0:
digit = temp % 10
sum += row[digit]
temp //= 10

for last_digit in range(10):
final_total = sum + row[last_digit]
if 10 * leading_digits + last_digit == final_total and final_total <= upper:
armstrong_numbers.append(num)

return armstrong_numbers


Micro-benchmarked locally I get the following



%timeit armstrong(1, 100000)
143 ms ± 104 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit armstrong2(1, 100000)
69.4 ms ± 2.52 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit armstrong3(1, 100000)
14.9 ms ± 31.4 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)


So the extra tuning was worth it.



Other work



I didn't get around to implementing the ideas at this github project. The code is in java and run with N < 10 at the smallest (above you can see my benchmarks were only for N < 5), so I don't think the performance is anywhere near as good as that code. It would be the next place to go if you are interested in pushing things further.



I looked at using divmod instead of modding and dividing by 10. The performance was worse for me, so I chose not to use it.



%timeit armstrong(1, 100000)
143 ms ± 104 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit armstrong_divmod(1, 100000)
173 ms ± 5.5 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)





share|improve this answer









$endgroup$












  • $begingroup$
    Upvoted! Thanks for the amazing response. It helped me a lot.
    $endgroup$
    – Justin
    3 hours ago















3












$begingroup$

I'm going to focus on the performance aspect, as I believe other parts already have good answers. While the program may be simple and performant, it is still fun to go in and micro-optimize a little. In general I would recommend against it.



Here is your code put into a function to make it clear what changes in the future. I've made the function return the numbers since that was easier for me to work with. I don't have anything new that I would suggest changing.



def armstrong(lower, upper):
armstrong_numbers = []
for num in range(lower, upper + 1):
order = len(str(num))
sum = 0

temp = num
while temp > 0:
digit = temp % 10
sum += digit ** order
temp //= 10

if num == sum:
armstrong_numbers.append(num)

return armstrong_numbers


Precompute



A google search says another name for these numbers are narcissistic numbers and that there are only a finite number of them (88 to be exact). We could make a list of the numbers, loop over the list and return the numbers between lower and upper. This only works if someone else has done the work and generated all the numbers already.



Precompute a little



The above point could be useful though, the first 9 armstrong numbers are the numbers 1 through 9. Let's "precompute" as many of those as we need. We should really add a check to make sure lower <= upper, but alas...



def armstrong(lower, upper):
cutoff = min(10, upper + 1)
armstrong_numbers = list(range(lower, cutoff))
if lower < 10:
lower = 10

for num in range(lower, upper + 1):
order = len(str(num))
sum = 0

temp = num
while temp > 0:
digit = temp % 10
sum += digit ** order
temp //= 10

if num == sum:
armstrong_numbers.append(num)

return armstrong_numbers


Optimize the powers



Another good point that showed up when googling armstrong numbers is that no number bigger than 1060 can be an armstrong number. This means we can work out every possible answer for digitorder ahead of time and reuse it each loop. This should be useful as I think computing arbitrary powers is not as fast as looking up the answer in a table.



As plainly as I can state it, there are only 10 digits, and order is at most 60, so we can make a table 10 * 60 big that stores all the answers to digit**order and use that instead.



def armstrong(lower, upper):
cutoff = min(10, upper + 1)
armstrong_numbers = list(range(lower, cutoff))
if lower < 10:
lower = 10

powers_table = [[d ** n for d in range(10)] for n in range(60)]

for num in range(lower, upper + 1):
order = len(str(num))
row = powers_table[order] # We only care about one row of the table at a time.
sum = 0

temp = num
while temp > 0:
digit = temp % 10
sum += row[digit]
temp //= 10

if num == sum:
armstrong_numbers.append(num)

return armstrong_numbers


Check less numbers



The last idea that I found online (see section 5) is to skip numbers with certain prefixes. We can guarantee a number will never work if it the sum of all of its digits except the last one is odd.



The reason for this is as follows. Raising a number to a power won't change its parity. In other words if a number x is even, xn is also even. If x is odd xn is also odd. The sum of the digits raised to a power n will have the same parity as the sum of the digits. For example if we have the 3 digit number 18X. The sum of it's digits cubed is 13 (odd) + 83 (even) + X**3 which is the same as 1 (odd) + 8 (even) + X.



Assume the sum of all the digits of an armstrong number excluding the last digit is odd then we have either



(A**n + B**n + C**n + ...W**n) + X**n == odd + X**n == odd if X is even or
(A**n + B**n + C**n + ...W**n) + X**n == odd + X**n == even if X is odd


But if the last digit (X) is even, the sum has to be even it to which it isn't.
If the last digit is odd, the sum has to be odd, but it isn't. Either way, we get a contradiction, so our assumption must be wrong.



The code is a bit messy, but it gives the idea. It agrees with the other snippets above for the query (1, 100000)



def armstrong3(lower, upper): 
cutoff = min(10, upper + 1)
armstrong_numbers = list(range(lower, cutoff))
if lower < 10:
lower = 10

powers_table = [[d ** n for d in range(10)] for n in range(60)]

start, end = lower // 10, upper // 10
for leading_digits in range(start, end + 1):
if sum(c in "13579" for c in str(leading_digits)) % 2 == 1:
# Skip numbers with an odd sum parity
continue

order = len(str(leading_digits)) + 1 # We will add a last digit later
row = powers_table[order] # We only care about one row of the table at a time.
sum = 0

temp = leading_digits
while temp > 0:
digit = temp % 10
sum += row[digit]
temp //= 10

for last_digit in range(10):
final_total = sum + row[last_digit]
if 10 * leading_digits + last_digit == final_total and final_total <= upper:
armstrong_numbers.append(num)

return armstrong_numbers


Micro-benchmarked locally I get the following



%timeit armstrong(1, 100000)
143 ms ± 104 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit armstrong2(1, 100000)
69.4 ms ± 2.52 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit armstrong3(1, 100000)
14.9 ms ± 31.4 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)


So the extra tuning was worth it.



Other work



I didn't get around to implementing the ideas at this github project. The code is in java and run with N < 10 at the smallest (above you can see my benchmarks were only for N < 5), so I don't think the performance is anywhere near as good as that code. It would be the next place to go if you are interested in pushing things further.



I looked at using divmod instead of modding and dividing by 10. The performance was worse for me, so I chose not to use it.



%timeit armstrong(1, 100000)
143 ms ± 104 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit armstrong_divmod(1, 100000)
173 ms ± 5.5 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)





share|improve this answer









$endgroup$












  • $begingroup$
    Upvoted! Thanks for the amazing response. It helped me a lot.
    $endgroup$
    – Justin
    3 hours ago













3












3








3





$begingroup$

I'm going to focus on the performance aspect, as I believe other parts already have good answers. While the program may be simple and performant, it is still fun to go in and micro-optimize a little. In general I would recommend against it.



Here is your code put into a function to make it clear what changes in the future. I've made the function return the numbers since that was easier for me to work with. I don't have anything new that I would suggest changing.



def armstrong(lower, upper):
armstrong_numbers = []
for num in range(lower, upper + 1):
order = len(str(num))
sum = 0

temp = num
while temp > 0:
digit = temp % 10
sum += digit ** order
temp //= 10

if num == sum:
armstrong_numbers.append(num)

return armstrong_numbers


Precompute



A google search says another name for these numbers are narcissistic numbers and that there are only a finite number of them (88 to be exact). We could make a list of the numbers, loop over the list and return the numbers between lower and upper. This only works if someone else has done the work and generated all the numbers already.



Precompute a little



The above point could be useful though, the first 9 armstrong numbers are the numbers 1 through 9. Let's "precompute" as many of those as we need. We should really add a check to make sure lower <= upper, but alas...



def armstrong(lower, upper):
cutoff = min(10, upper + 1)
armstrong_numbers = list(range(lower, cutoff))
if lower < 10:
lower = 10

for num in range(lower, upper + 1):
order = len(str(num))
sum = 0

temp = num
while temp > 0:
digit = temp % 10
sum += digit ** order
temp //= 10

if num == sum:
armstrong_numbers.append(num)

return armstrong_numbers


Optimize the powers



Another good point that showed up when googling armstrong numbers is that no number bigger than 1060 can be an armstrong number. This means we can work out every possible answer for digitorder ahead of time and reuse it each loop. This should be useful as I think computing arbitrary powers is not as fast as looking up the answer in a table.



As plainly as I can state it, there are only 10 digits, and order is at most 60, so we can make a table 10 * 60 big that stores all the answers to digit**order and use that instead.



def armstrong(lower, upper):
cutoff = min(10, upper + 1)
armstrong_numbers = list(range(lower, cutoff))
if lower < 10:
lower = 10

powers_table = [[d ** n for d in range(10)] for n in range(60)]

for num in range(lower, upper + 1):
order = len(str(num))
row = powers_table[order] # We only care about one row of the table at a time.
sum = 0

temp = num
while temp > 0:
digit = temp % 10
sum += row[digit]
temp //= 10

if num == sum:
armstrong_numbers.append(num)

return armstrong_numbers


Check less numbers



The last idea that I found online (see section 5) is to skip numbers with certain prefixes. We can guarantee a number will never work if it the sum of all of its digits except the last one is odd.



The reason for this is as follows. Raising a number to a power won't change its parity. In other words if a number x is even, xn is also even. If x is odd xn is also odd. The sum of the digits raised to a power n will have the same parity as the sum of the digits. For example if we have the 3 digit number 18X. The sum of it's digits cubed is 13 (odd) + 83 (even) + X**3 which is the same as 1 (odd) + 8 (even) + X.



Assume the sum of all the digits of an armstrong number excluding the last digit is odd then we have either



(A**n + B**n + C**n + ...W**n) + X**n == odd + X**n == odd if X is even or
(A**n + B**n + C**n + ...W**n) + X**n == odd + X**n == even if X is odd


But if the last digit (X) is even, the sum has to be even it to which it isn't.
If the last digit is odd, the sum has to be odd, but it isn't. Either way, we get a contradiction, so our assumption must be wrong.



The code is a bit messy, but it gives the idea. It agrees with the other snippets above for the query (1, 100000)



def armstrong3(lower, upper): 
cutoff = min(10, upper + 1)
armstrong_numbers = list(range(lower, cutoff))
if lower < 10:
lower = 10

powers_table = [[d ** n for d in range(10)] for n in range(60)]

start, end = lower // 10, upper // 10
for leading_digits in range(start, end + 1):
if sum(c in "13579" for c in str(leading_digits)) % 2 == 1:
# Skip numbers with an odd sum parity
continue

order = len(str(leading_digits)) + 1 # We will add a last digit later
row = powers_table[order] # We only care about one row of the table at a time.
sum = 0

temp = leading_digits
while temp > 0:
digit = temp % 10
sum += row[digit]
temp //= 10

for last_digit in range(10):
final_total = sum + row[last_digit]
if 10 * leading_digits + last_digit == final_total and final_total <= upper:
armstrong_numbers.append(num)

return armstrong_numbers


Micro-benchmarked locally I get the following



%timeit armstrong(1, 100000)
143 ms ± 104 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit armstrong2(1, 100000)
69.4 ms ± 2.52 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit armstrong3(1, 100000)
14.9 ms ± 31.4 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)


So the extra tuning was worth it.



Other work



I didn't get around to implementing the ideas at this github project. The code is in java and run with N < 10 at the smallest (above you can see my benchmarks were only for N < 5), so I don't think the performance is anywhere near as good as that code. It would be the next place to go if you are interested in pushing things further.



I looked at using divmod instead of modding and dividing by 10. The performance was worse for me, so I chose not to use it.



%timeit armstrong(1, 100000)
143 ms ± 104 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit armstrong_divmod(1, 100000)
173 ms ± 5.5 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)





share|improve this answer









$endgroup$



I'm going to focus on the performance aspect, as I believe other parts already have good answers. While the program may be simple and performant, it is still fun to go in and micro-optimize a little. In general I would recommend against it.



Here is your code put into a function to make it clear what changes in the future. I've made the function return the numbers since that was easier for me to work with. I don't have anything new that I would suggest changing.



def armstrong(lower, upper):
armstrong_numbers = []
for num in range(lower, upper + 1):
order = len(str(num))
sum = 0

temp = num
while temp > 0:
digit = temp % 10
sum += digit ** order
temp //= 10

if num == sum:
armstrong_numbers.append(num)

return armstrong_numbers


Precompute



A google search says another name for these numbers are narcissistic numbers and that there are only a finite number of them (88 to be exact). We could make a list of the numbers, loop over the list and return the numbers between lower and upper. This only works if someone else has done the work and generated all the numbers already.



Precompute a little



The above point could be useful though, the first 9 armstrong numbers are the numbers 1 through 9. Let's "precompute" as many of those as we need. We should really add a check to make sure lower <= upper, but alas...



def armstrong(lower, upper):
cutoff = min(10, upper + 1)
armstrong_numbers = list(range(lower, cutoff))
if lower < 10:
lower = 10

for num in range(lower, upper + 1):
order = len(str(num))
sum = 0

temp = num
while temp > 0:
digit = temp % 10
sum += digit ** order
temp //= 10

if num == sum:
armstrong_numbers.append(num)

return armstrong_numbers


Optimize the powers



Another good point that showed up when googling armstrong numbers is that no number bigger than 1060 can be an armstrong number. This means we can work out every possible answer for digitorder ahead of time and reuse it each loop. This should be useful as I think computing arbitrary powers is not as fast as looking up the answer in a table.



As plainly as I can state it, there are only 10 digits, and order is at most 60, so we can make a table 10 * 60 big that stores all the answers to digit**order and use that instead.



def armstrong(lower, upper):
cutoff = min(10, upper + 1)
armstrong_numbers = list(range(lower, cutoff))
if lower < 10:
lower = 10

powers_table = [[d ** n for d in range(10)] for n in range(60)]

for num in range(lower, upper + 1):
order = len(str(num))
row = powers_table[order] # We only care about one row of the table at a time.
sum = 0

temp = num
while temp > 0:
digit = temp % 10
sum += row[digit]
temp //= 10

if num == sum:
armstrong_numbers.append(num)

return armstrong_numbers


Check less numbers



The last idea that I found online (see section 5) is to skip numbers with certain prefixes. We can guarantee a number will never work if it the sum of all of its digits except the last one is odd.



The reason for this is as follows. Raising a number to a power won't change its parity. In other words if a number x is even, xn is also even. If x is odd xn is also odd. The sum of the digits raised to a power n will have the same parity as the sum of the digits. For example if we have the 3 digit number 18X. The sum of it's digits cubed is 13 (odd) + 83 (even) + X**3 which is the same as 1 (odd) + 8 (even) + X.



Assume the sum of all the digits of an armstrong number excluding the last digit is odd then we have either



(A**n + B**n + C**n + ...W**n) + X**n == odd + X**n == odd if X is even or
(A**n + B**n + C**n + ...W**n) + X**n == odd + X**n == even if X is odd


But if the last digit (X) is even, the sum has to be even it to which it isn't.
If the last digit is odd, the sum has to be odd, but it isn't. Either way, we get a contradiction, so our assumption must be wrong.



The code is a bit messy, but it gives the idea. It agrees with the other snippets above for the query (1, 100000)



def armstrong3(lower, upper): 
cutoff = min(10, upper + 1)
armstrong_numbers = list(range(lower, cutoff))
if lower < 10:
lower = 10

powers_table = [[d ** n for d in range(10)] for n in range(60)]

start, end = lower // 10, upper // 10
for leading_digits in range(start, end + 1):
if sum(c in "13579" for c in str(leading_digits)) % 2 == 1:
# Skip numbers with an odd sum parity
continue

order = len(str(leading_digits)) + 1 # We will add a last digit later
row = powers_table[order] # We only care about one row of the table at a time.
sum = 0

temp = leading_digits
while temp > 0:
digit = temp % 10
sum += row[digit]
temp //= 10

for last_digit in range(10):
final_total = sum + row[last_digit]
if 10 * leading_digits + last_digit == final_total and final_total <= upper:
armstrong_numbers.append(num)

return armstrong_numbers


Micro-benchmarked locally I get the following



%timeit armstrong(1, 100000)
143 ms ± 104 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit armstrong2(1, 100000)
69.4 ms ± 2.52 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit armstrong3(1, 100000)
14.9 ms ± 31.4 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)


So the extra tuning was worth it.



Other work



I didn't get around to implementing the ideas at this github project. The code is in java and run with N < 10 at the smallest (above you can see my benchmarks were only for N < 5), so I don't think the performance is anywhere near as good as that code. It would be the next place to go if you are interested in pushing things further.



I looked at using divmod instead of modding and dividing by 10. The performance was worse for me, so I chose not to use it.



%timeit armstrong(1, 100000)
143 ms ± 104 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit armstrong_divmod(1, 100000)
173 ms ± 5.5 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)






share|improve this answer












share|improve this answer



share|improve this answer










answered 9 hours ago









spyr03spyr03

1,262519




1,262519











  • $begingroup$
    Upvoted! Thanks for the amazing response. It helped me a lot.
    $endgroup$
    – Justin
    3 hours ago
















  • $begingroup$
    Upvoted! Thanks for the amazing response. It helped me a lot.
    $endgroup$
    – Justin
    3 hours ago















$begingroup$
Upvoted! Thanks for the amazing response. It helped me a lot.
$endgroup$
– Justin
3 hours ago




$begingroup$
Upvoted! Thanks for the amazing response. It helped me a lot.
$endgroup$
– Justin
3 hours ago

















draft saved

draft discarded
















































Thanks for contributing an answer to Code Review Stack Exchange!


  • Please be sure to answer the question. Provide details and share your research!

But avoid


  • Asking for help, clarification, or responding to other answers.

  • Making statements based on opinion; back them up with references or personal experience.

Use MathJax to format equations. MathJax reference.


To learn more, see our tips on writing great answers.




draft saved


draft discarded














StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f220738%2fpython-program-to-find-armstrong-numbers-in-a-certain-range%23new-answer', 'question_page');

);

Post as a guest















Required, but never shown





















































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown

































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown







Popular posts from this blog

Log på Navigationsmenu

Creating second map without labels using QGIS?How to lock map labels for inset map in Print Composer?How to Force the Showing of Labels of a Vector File in QGISQGIS Valmiera, Labels only show for part of polygonsRemoving duplicate point labels in QGISLabeling every feature using QGIS?Show labels for point features outside map canvasAbbreviate Road Labels in QGIS only when requiredExporting map from composer in QGIS - text labels have moved in output?How to make sure labels in qgis turn up in layout map?Writing label expression with ArcMap and If then Statement?

Detroit Tigers Spis treści Historia | Skład zespołu | Sukcesy | Członkowie Baseball Hall of Fame | Zastrzeżone numery | Przypisy | Menu nawigacyjneEncyclopedia of Detroit - Detroit TigersTigers Stadium, Detroit, MITigers Timeline 1900sDetroit Tigers Team History & EncyclopediaTigers Timeline 1910s1935 World Series1945 World Series1945 World Series1984 World SeriesComerica Park, Detroit, MI2006 World Series2012 World SeriesDetroit Tigers 40-Man RosterDetroit Tigers Coaching StaffTigers Hall of FamersTigers Retired Numberse