Reputation: 499
I've completed a codingame assessment but i was not able to pass all tests on one challenge about lucky numbers. I need some help.
Definition: a lucky number is a number that contains either 6
s or 8
s in its digits but not both.
For example 16
, 38
, 666
are lucky numbers. 234
, 687
are not.
Task: Print the number of lucky numbers between L
and H
inclusive.
Constraints:
L < H
Memory: 512MB
Time: 6 seconds
Here's what I did (I chose Python as programming language)
def is_lucky(nbr):
nbr = [*str(nbr)]
if '6' in nbr and '8' in nbr:
return False
if '6' in nbr or '8' in nbr:
return True
return False
n_lucky_number = 0
for number in range(L, H + 1):
n_lucky_number += is_lucky(number)
print(n_lucky_number)
I failed the tests where L
and H
are big (or the gap between) due to timeout.
L, H = 1, 1000000000000000000
L, H = 92871036442, 3363728910382456
Could someone help me to optimize my code?
Upvotes: 10
Views: 10517
Reputation: 306
6 seconds? That seems really hard in Python, usually slow compared to C, Java etc. Even though I haven't figured out a way that passes the test, there are a few places you can improve:
nbr
can be just converted to str
, no need to unpack into list
. str
supports in
.
Use the fact that in mathematical calcs, True==1
, False==0
.
E.g. A is '6' in nbr
, B is '8' in nbr
:
Both are true, A+B=2
One is true, A+B=1
Both are false, A+B=0
So we can just check if A+B == 1.
Before:
def is_lucky(nbr):
nbr = [*str(nbr)]
if '6' in nbr and '8' in nbr:
return False
if '6' in nbr or '8' in nbr:
return True
return False
After:
def is_lucky(nbr):
nbr = str(nbr)
return ('6' in nbr) + ('8' in nbr) == 1
If the number now has checked False
, we can improve it.
If the final digit is less than 5, increment 6-n%10.
For example, 311 isn't a lucky number. So of course 312~315 aren't, too, so just add directly to 316 by 6-1=5.
Why not <6? 5 adds 1 no matter what, so we don't need it :]
Before:
n_lucky_number = 0
for number in range(L, H + 1):
n_lucky_number += is_lucky(number)
print(n_lucky_number)
After:
number, s = L, 0
while n <= H:
x = is_lucky(number)
n_lucky_number += x
m = number % 10
if not x and m < 5:
number += 6-m
else:
number += 1
print(n_lucky_number)
Upvotes: -2
Reputation: 12731
It's an interesting problem and @sulav shrestha has already delivered a working algorithm. Unfortunately his answer lacks some explanation and the code itself can be cleaner.
As others have pointed out, you have to come up with a formula in order to perform with big numbers.
@user3386109 rightfully pointed out that lucky(L,H) = lucky(0,H) - lucky(0,L-1)
. From there let's see how many lucky numbers there are between 0 and any given number.
In a decimal base, any number can be written like a sum of power of ten:
12345 = 1e4 + 2e3 + 3e2 + 4e1 + 5e0
So let's calculate the number of lucky numbers between 0 and 10^n
. It comes down to count the possible combinations of n-digits numbers with either:
8
and at least one 6
6
and at least one 8
The number of combination with no 8
is 9^n
(you get to choose among 9 possibilities for each of the n digits).
Then you remove all combinations NOT containing any 6
: 8^n
.
So the number of lucky numbers between 0 and 10^n
is 2*(9^n-8^n)
.
From there you can iterate over your digits from left to right and count:
import time
def magic_numbers_until(num):
num_str=str(num)
str_len=len(num_str)
c=[]
count=0
for i,digit in enumerate(num_str): # we're going from left to right in num
power_ten=str_len-i-1
for k in range(int(digit)): # summing all magic numbers for this power of ten
if (k==6 or k==8 # if 6 (resp. 8) was already seen,
or "6" in c or "8" in c): # you just calculate the combinations without 8 (resp. 6)
count=count+9**power_ten
else: # the case described in my answer
count=count+2*(9**power_ten-8**power_ten)
c.append(digit)
if "6" in c and "8" in c: # if 6 and 8 are in num, all remaining combinations will be non-magic
break
return(int(count))
L=1
H=1000000000000000000
t0 = time.time()
print(magic_numbers_until(H+1)-magic_numbers_until(L))
print(time.time()-t0)
Upvotes: 2
Reputation: 41
def coo(a):
b=str(a)
l=len(b)
c=[]
count=0
for i,j in enumerate(b):
d=l-i-1
for k in range(int(j)):
if k==6 or k==8:
if k==6 and "8" not in c:
count=count+9**d
elif k==8 and "6" not in c:
count=count+9**d
elif "6" not in c and "8" not in c:
count=count+2*((9**d)-(8**d))
elif "6" in c or "8" in c:
count=count+9**d
c.append(j)
if "6" in c and "8" in c:
break
return(int(count))
L=21345323634
H=8392440035734
print(coo(H+1)-coo(L))
Upvotes: 3