Count Number of Ways - python

Assume you have a fruit and some vegetables. You gain 1 calorie from eating a vegetable. You can eat any number of vegetables to compensate the remaining gain from eating a fruit or no fruit.
Find the no of ways in which you can eat to gain exactly n calories.
m - no. of calories gained by eating a fruit
def totalWays(n, m):
lookup = [1,1,2] + [0]*(n-2)
for i in range(3, n + 1):
j = 1
while j <= m and (i - j) >= 0:
lookup[i] += lookup[i - j]
j = j + 1
return lookup[n]
for example, totalWays(5,3) should gives us 4 meaning we can have vvvvv, vvf, vfv, fvv instead it gives 13. I think instead of taking vegetable gain as 1, it is assuming all possible values like 3vvv,2v2vv, v2v2v...
Can someone help where I'm going wrong ?!

If only one fruit is allowed, then you can either eat one fruit or eat no fruits.
a) eating no fruits
The # of results is 1, as it is v n times -> vvvvv for n=5, etc.
b) eating one fruit
The # of results is n - m + 1, because you remove m vs from the previous case, leaving n - m vs, which yields n - m + 1 ways to place the f.
Therefore, if n is larger than or equal m, the result is n - m + 2. For n < m, you cannot substitute the vegetables for a fruit, and the result is then always 1.
def total_ways(n, m):
if n < m:
return 1
else:
return n - m + 2

Related

What I am doing wrong here - dynamic problem

PROBLEM STATEMENT:
You are about to go shopping for some candy in a local store. They sell candy for either $1 or $2 pieces. You would like to know in how many unique ways you can purchase candy based on the amount of money you have.
def buying_candy(amount_of_money):
if amount_of_money < 2:
return 1
dp = {0: 1, 1: 1}
x = 1
while x < amount_of_money:
if x not in dp:
dp[x] = 1
dp[x] = dp[x] + dp[x - 1]
x += 1
return dp[amount_of_money - 1]
print(buying_candy(4))
OUTPUT: 5
EXPLANATION:
1 + 1 + 1 + 1
2 + 2
2 + 1 + 1
1 + 2 + 1
1 + 1 + 2
UPDATE:
SOLUTION of Problem
def buying_candy(amount_of_money):
if amount_of_money < 2:
return 1
dp = {
0: 1,
1: 1
}
x = 2
while x < amount_of_money + 1:
if x not in dp:
dp[x] = 1
dp[x] = dp[x - 1] + dp[x - 2]
x += 1
return dp[amount_of_money]
This problem does not require dynamic programming. Denote the amount of money by n. There are between 0 and ⌊n/2⌋ twos. If the number of twos is k, then the number of ones is n−2k, and the total number of ones and twos is n-2k+k = n-k. Each solution with k twos corresponds to choosing k out of the n-k positions for the twos. So the total number of solutions with k twos is (n-k choose k), and the total number of solutions is the sum of this expression over k from 0 and ⌊n/2⌋.
In Python:
import math
n = 4 # amount of money
total = sum(math.comb(n-k,k) for k in range(n//2+1)) # total number of solutions
print(total)
If the rules of the game require using dynamic programming, here is a correct version:
def buying_candies(n):
if n < 2:
return 1
dp = [1, 1] # use list instead of dictionary, as keys are 0,1,2,...
for i in range(2, n+1):
dp.append(dp[i-1] + dp[i-2])
return dp[n]
print(buying_candies(4))
It is all just Fibonacci sequence, in fact :)
So there is in fact a closed formula.

python Find the sum of all multiples of n below m

Find the sum of all multiples of n below m
Keep in Mind n and m are natural numbers (positive integers) m is
excluded from the multiples
sumMul(2, 9) ==> 2 + 4 + 6 + 8 = 20
sumMul(3, 13) ==> 3 + 6 + 9 + 12 = 30
sumMul(4, -7) ==> "INVALID"
I did sum of list using range(n, m, n) using n as step.
I also tried modulus to avoid range 3 args error.
I can pass many tests but cannot pass all of them.
I have tried lots of logic but to no avail. What I am doing wrong?
CODEWARS: https://www.codewars.com/kata/57241e0f440cd279b5000829/train/python
MY CODE:
def sum_mul(n, m):
my_list = [number for number in range(n, m) if number % n == 0]
sum_list = sum(my_list)
if sum_list >= 1:
return sum_list
elif n == 0 and m == 0:
return 'INVALID'
elif n == m:
return n - m
elif n > m:
return 'INVALID'
Your code fails if n == 0 as then the number % n checks in the list comprehension fail, so you should check that before trying to compute the sum. Also, you could use a range with step and just do sum(range(n, m, n)). However, both ways might be too slow for some test cases with very large m.
You can do this in O(1) with the following observations:
there are (m-1) // n multiples of n below m
the sum of natural numbers from 1 to n is n*(n+1)//2
Combine those two to get the result.
Example for sumMul(3, 13) ==> 3 + 6 + 9 + 12 = 30:
(13-1) // 3 == 4 so we know there are 4 multiples of 3 below 13
those are 3 + 6 + 9 + 12 == 3 * (1 + 2 + 3 + 4)
with (2) we know 1 + 2 + 3 + 4 == 4*5//2 == 10
so the result is 10 * 3 == 30
Putting that into code and handling the special cases is left as an exercise to the interested reader.
You have one main problem, that is you should prevent the situation when n==0 and you divide it in your list comprehension. It will raise zero division error. so you should check before the validation that n is not equal to zero.
Second thing is that you need to check whether n or m are negatives, as the exercise declared both n and m should be positives.
def sum_mul(n, m):
if n==0:
return 'INVALID'
my_list = [number for number in range(n, m) if number % n == 0]
sum_list = sum(my_list)
if sum_list >= 1:
return sum_list
elif n < 0 and m <= 0:
return 'INVALID'
elif n == m:
return n - m
elif n > m:
return 'INVALID'
You can just compute that result mathematically using integer divisions:
def sum_mul(n, m):
if n<1 or n>m: return "INVALID"
return m//n*(m//n+1)//2*n
First you get the number of multiple of n in m (which is merely dividing m by n ignoring the remainder) : m//n
Multiples of n will be nx1, nx2, nx3, ... up to the number of multiples. Factorizing the sum of those by n we get: n(1+2+3+ ... m//n).
The sum of numbers from 1 up to a given number x is obtained by x(x+1)/2. In this case x is the number of multiples m//n
Putting it all together we get n * x * (x+1) /2 where x is m//n, so:
n * (m//n) * (m // n + 1) // 2
You should comprove all cases before call sum function.
Like this:
def sum_mul(n, m):
if n == 0 or m == 0:
return 'INVALID'
if n == m:
return n - m
if n<0 or m<0:
return 'INVALID'
my_list = [number for number in range(n, m) if number % n == 0]
return sum(my_list)
In fact, you dont't need to create if elif structure because you are using returns, so next instruction after return not executed.

Uneven Sharing in Python Performance Algorithm

A group of k boys, should be paid marbles as follows: they sit in a circle, and each boy will take 1 marble more than the boy to his right just did, and pass the bag with the remainder of the marbles to the boy on his left.
The leader starts by taking out 1 marble for himself. He passes the bag to the boy on his left, who then removes 2 marbles for himself, and passes the bag to his left. That boy then takes 3 coins and passes the bag on to his left, and so on. This process continues until the bag is empty (the last boy to take from the bag may not get as many marbles as he should).
I want to get the total number of marbles that the LEADER receives at the end of the process.
This is what I have, it works but it is too slow:
def countMarbles(n, k):
c = 0
leader = 0
while n>0:
for i in range(k):
c+=1
if i == 0:
if c<=n:
leader += c
else:
leader += n
n -= c
return leader
The marbles you are speding are 1, then 2, then 3...
There is a formula for this kind of sum, the sum of 1 to x is (x)(x + 1) / 2.
Now you are given n, and want to know how many passes of the bag you can make. This means getting highest x such that (x)(x + 1) / 2 is lower or equal to n.
We can get this by solving 0 = x^2 + x - 2n. We might get a decimal result there, so we should take the floor value of the positive answer to the equation.
Once we found the correct x, we just know that every k passes of the bag, 1 goes to the leader. He first gets 1 marble, then he gets k + 1 marbles, then 2k + 1...
If there were x passes, ceil of x / k went to the leader. Taking out the first pass which is always 1, we get l = ceil(x / k) - 1 passes that have a k coefficient greater than 0: ((k + 1) + (2k + 1) + ... + (lk + 1)) = (1 + 2 + 3 + ... + l) * k + l = (l * (l + 1) / 2) * k + l.
Considering leader started with 1, the solution is (l * (l + 1) / 2) * k + l + 1
The only problem is what happens with the remainding marbles that were left in the bag. In the case that those should have gone to leader, we also need to take them into account. For that to happen, x must be a multiple of k, meaning that we finished the round so the next should have been leader, but there were not enough marbles to make another pass.
Here is a python implementation:
import math
def solve (n, k):
x = math.floor((-1 + math.sqrt(1 + 8*n)) / 2)
l = math.ceil(x / k) - 1
sol = (l * (l + 1) / 2) * k + l + 1
if x % k == 0 :
sol += n - (x * (x + 1) / 2)
return int(sol)

How would I go about improving/making this run faster? [closed]

This question is unlikely to help any future visitors; it is only relevant to a small geographic area, a specific moment in time, or an extraordinarily narrow situation that is not generally applicable to the worldwide audience of the internet. For help making this question more broadly applicable, visit the help center.
Closed 9 years ago.
I'm a beginner in Python trying to get better, and I stumbled across the following exercise:
Let n be an integer greater than 1 and s(n) the sum of the dividors of
n. For example,
s(12) 1 + 2 + 3 + 4 + 6 + 12 = 28
Also,
s(s(12)) = s(28) = 1 + 2 + 4 + 7 + 14 + 28 = 56
And
s(s(s(12))) = s(56) = 1 + 2 + 4 + 7 + 8 + 14 + 28 + 56 = 120
We use the notations:
s^1(n) = s(n)
s^2(n) = s(s(n))
s^3(n) = s(s(s(n)))
s^ m (n) = s(s(. . .s(n) . . .)), m times
For the integers n for which exists a positive integer k so that
s^m(n) = k * n
are called (m, k)-perfect, for instance 12 is (3, 10)-perfect since
s^3(12) = s(s(s(12))) = 120 = 10 * 12
Special categories:
For m =1 we have multiperfect numbers
A special case of the above exist for m = 1 and k = 2 which are called
perfect numbers.
For m = 2 and k = 2 we have superperfect numbers.
Write a program which finds and prints all (m, k)-perfect numbers for
m <= MAXM, which are less or equal to (<=) MAXNUM. If an integer
belongs to one of the special categories mentioned above the program
should print a relevant message. Also, the program has to print how
many different (m, k)-perfect numbers were found, what percentage of
the tested numbers they were, in how many occurrences for the
different pairs of (m, k), and how many from each special category
were found(perfect numbers are counted as multiperfect as well).
Here's my code:
import time
start_time = time.time()
def s(n):
tsum = 0
i = 1
con = n
while i < con:
if n % i == 0:
temp = n / i
tsum += i
if temp != i:
tsum += temp
con = temp
i += 1
return tsum
#MAXM
#MAXNUM
i = 2
perc = 0
perc1 = 0
perf = 0
multperf = 0
supperf = 0
while i <= MAXNUM:
pert = perc1
num = i
for m in xrange(1, MAXM + 1):
tsum = s(num)
if tsum % i == 0:
perc1 += 1
k = tsum / i
mes = "%d is a (%d-%d)-perfect number" % (i, m, k)
if m == 1:
multperf += 1
if k == 2:
perf += 1
print mes + ", that is a perfect number"
else:
print mes + ", that is a multiperfect number"
elif m == 2 and k == 2:
supperf += 1
print mes + ", that is a superperfect number"
else:
print mes
num = tsum
i += 1
if pert != perc1: perc += 1
print "Found %d distinct (m-k)-perfect numbers (%.5f per cent of %d ) in %d occurrences" % (
perc, float(perc) / MAXNUM * 100, MAXNUM, perc1)
print "Found %d perfect numbers" % perf
print "Found %d multiperfect numbers (including perfect numbers)" % multperf
print "Found %d superperfect numbers" % supperf
print time.time() - start_time, "seconds"
It works fine, but I would like suggestions on how to make it run faster.
For instance is it faster to use
I = 1
while I <= MAXM:
…..
I += 1
instead of
for I in xrange(1, MAXM + 1)
Would it be better if instead of defining s(n) as a function I put the code into the main program? etc.
If you have anything to suggest for me to read on how to make a program run faster, I'd appreciate it.
And one more thing, originally the exercise required the program to be in C (which I don't know), having written this in Python, how difficult would it be for it to be made into C?
The biggest improvements come from using a better algorithm. Things like
Would it be better if instead of defining s(n) as a function I put the code into the main program?
or whether to use a while loop instead of for i in xrange(1, MAXM + 1): don't make much difference, so should not be considered before one has reached a state where algorithmic improvements are at least very hard to come by.
So let's take a look at your algorithm and how we can drastically improve it without caring about minuscule things like whether a while loop or a for iteration are faster.
def s(n):
tsum = 0
i = 1
con = n
while i < con:
if n % i == 0:
temp = n / i
tsum += i
if temp != i:
tsum += temp
con = temp
i += 1
return tsum
That already contains a good idea, you know that the divisors of n come in pairs and add both divisors once you found the smaller of the pair. You even correctly handle squares.
It works very well for numbers like 120: when you find the divisor 2, you set the stop condition to 60, when you find 3, to 40, ..., when you find 8, you set it to 15, when you find 10, you set it to 12, and then you have only the division by 11, and stop when i is incremented to 12. Not bad.
But it doesn't work so well when n is a prime, then con will never be set to a value smaller than n, and you need to iterate all the way to n before you found the divisor sum. It's also bad for numbers of the form n = 2*p with a prime p, then you loop to n/2, or n = 3*p (n/3, unless p = 2) etc.
By the prime number theorem, the number of primes not exceeding x is asymptotically x/log x (where log is the natural logarithm), and you have a lower bound of
Ω(MAXNUM² / log MAXNUM)
just for computing the divisor sums of the primes. That's not good.
Since you already consider the divisors of n in pairs d and n/d, note that the smaller of the two (ignoring the case d = n/d when n is a square for the moment) is smaller than the square root of n, so once the test divisor has reached the square root, you know that you have found and added all divisors, and you're done. Any further looping is futile wasted work.
So let us consider
def s(n):
tsum = 0
root = int(n**0.5) # floor of the square root of n, at least for small enough n
i = 1
while i < root + 1:
if n % i == 0:
tsum += i + n/i
i += 1
# check whether n is a square, if it is, we have added root twice
if root*root == n:
tsum -= root
return tsum
as a first improvement. Then you always loop to the square root, and computing s(n) for 1 <= n <= MAXNUM is Θ(MAXNUM^1.5). That's already quite an improvement. (Of course, you have to compute the iterated divisor sums, and s(n) can be larger than MAXNUM for some n <= MAXNUM, so you can't infer a complexity bound of O(MAXM * MAXNUM^1.5) for the total algorithm from that. But s(n) cannot be very much larger, so the complexity can't be much worse either.)
But we can still improve on that by using what twalberg suggested, using the prime factorisation of n to compute the divisor sum.
First, if n = p^k is a prime power, the divisors of n are 1, p, p², ..., p^k, and the divisor sum is easily computed (a closed formula for the geometric sum is
(p^(k+1) - 1) / (p - 1)
but whether one uses that or adds the k+1 powers of p dividing n is not important).
Next, if n = p^k * m with a prime p and an m such that p does not divide m, then
s(n) = s(p^k) * s(m)
An easy way to see that decomposition is to write each divisor d of n in the form d = p^a * g where p does not divide g. Then p^a must divide p^k, i.e. a <= k, and g must divide m. Conversely, for every 0 <= a <= k and every g dividing m, p^a * g is a divisor of n. So we can lay out the divisors of n (where 1 = g_1 < g_2 < ... < g_r = m are the divisors of m)
1*g_1 1*g_2 ... 1*g_r
p*g_1 p*g_2 ... p*g_r
: : :
p^k*g_1 p^k*g_2 ... p^k*g_r
and the sum of each row is p^a * s(m).
If we have a list of primes handy, we can then write
def s(n):
tsum = 1
for p in primes:
d = 1
# divide out all factors p of n
while n % p == 0:
n = n//p
d = p*d + 1
tsum *= d
if p*p > n: # n = 1, or n is prime
break
if n > 1: # one last prime factor to account for
tsum *= 1 + n
return tsum
The trial division goes to the second largest prime factor of n [if n is composite] or the square root of the largest prime factor of n, whichever is larger. It has a worst-case bound for the largest divisor tried of n**0.5, which is reached for primes, but for most composites, the division stops much earlier.
If we don't have a list of primes handy, we can replace the line for p in primes: with for p in xrange(2, n): [the upper limit is not important, since it is never reached if it is larger than n**0.5] and get a not too much slower factorisation. (But it can easily be sped up a lot by avoiding even trial divisors larger than 2, that is using a list [2] + [3,5,7...] - best as a generator - for the divisors, even more by also skipping multiples of 3 (except 3), [2,3] + [5,7, 11,13, 17,19, ...] and if you want of a few further small primes.)
Now, that helped, but computing the divisor sums for all n <= MAXNUM still takes Ω(MAXNUM^1.5 / log MAXNUM) time (I haven't analysed, that could be also an upper bound, or the MAXNUM^1.5 could still be a lower bound, anyway, a logarithmic factor rarely makes much of a difference [beyond a constant factor]).
And you compute a lot of divisor sums more than once (in your example, you compute s(56) when investigating 12, again when investigating 28, again when investigating 56). To alleviate the impact of that, memoizing s(n) would be a good idea. Then you need to compute each s(n) only once.
And now we have already traded space for time, so we can use a better algorithm to compute the divisor sums for all 1 <= n <= MAXNUM in one go, with a better time complexity (and also smaller constant factors). Instead of trying out each small enough (prime) number whether it divides n, we can directly mark only multiples, thus avoiding all divisions that leave a remainder - which is the vast majority.
The easy method to do that is
def divisorSums(n):
dsums = [0] + [1]*n
for k in xrange(2, n+1):
for m in xrange(k, n+1, k):
dsums[m] += k
return dsums
with an O(n * log n) time complexity. You can do it a bit better (O(n * log log n) complexity) by using the prime factorisation, but that method is somewhat more complicated, I'm not adding it now, maybe later.
Then you can use the list of all divisor sums to look up s(n) if n <= MAXNUM, and the above implementation of s(n) to compute the divisor sum for values larger than MAXNUM [or you may want to memoize the values up to a larger limit].
dsums = divisorSums(MAXNUM)
def memo_s(n):
if n <= MAXNUM:
return dsums[n]
return s(n)
That's not too shabby,
Found 414 distinct (m-k)-perfect numbers (0.10350 per cent of 400000 ) in 496 occurrences
Found 4 perfect numbers
Found 8 multiperfect numbers (including perfect numbers)
Found 7 superperfect numbers
12.709428072 seconds
for
import time
start_time = time.time()
def s(n):
tsum = 1
for p in xrange(2,n):
d = 1
# divide out all factors p of n
while n % p == 0:
n = n//p
d = p*d + 1
tsum *= d
if p*p > n: # n = 1, or n is prime
break
if n > 1: # one last prime factor to account for
tsum *= 1 + n
return tsum
def divisorSums(n):
dsums = [0] + [1]*n
for k in xrange(2, n+1):
for m in xrange(k, n+1, k):
dsums[m] += k
return dsums
MAXM = 6
MAXNUM = 400000
dsums = divisorSums(MAXNUM)
def memo_s(n):
if n <= MAXNUM:
return dsums[n]
return s(n)
i = 2
perc = 0
perc1 = 0
perf = 0
multperf = 0
supperf = 0
while i <= MAXNUM:
pert = perc1
num = i
for m in xrange(1, MAXM + 1):
tsum = memo_s(num)
if tsum % i == 0:
perc1 += 1
k = tsum / i
mes = "%d is a (%d-%d)-perfect number" % (i, m, k)
if m == 1:
multperf += 1
if k == 2:
perf += 1
print mes + ", that is a perfect number"
else:
print mes + ", that is a multiperfect number"
elif m == 2 and k == 2:
supperf += 1
print mes + ", that is a superperfect number"
else:
print mes
num = tsum
i += 1
if pert != perc1: perc += 1
print "Found %d distinct (m-k)-perfect numbers (%.5f per cent of %d ) in %d occurrences" % (
perc, float(perc) / MAXNUM * 100, MAXNUM, perc1)
print "Found %d perfect numbers" % perf
print "Found %d multiperfect numbers (including perfect numbers)" % multperf
print "Found %d superperfect numbers" % supperf
print time.time() - start_time, "seconds"
By memoizing also the needed divisor sums for n > MAXNUM:
d = {}
for i in xrange(1, MAXNUM+1):
d[i] = dsums[i]
def memo_s(n):
if n in d:
return d[n]
else:
t = s(n)
d[n] = t
return t
the time drops to
3.33684396744 seconds
from functools import lru_cache
...
#lru_cache
def s(n):
...
should make it significantly faster.
[update] oh, sorry, that was added in 3.2 according to the docs. but any cache will do. see Is there a decorator to simply cache function return values?

How to calculate no. of palindroms in a large number interval?

I want to calculate how many numbers are palindrome in large interval data say 10^15
My simple code (python) snippet is:
def count_palindromes(start, end):
count = 0
for i in range(start, end + 1):
if str(i) == str(i)[::-1]:
count += 1
return count
start = 1000 #some initial number
end = 10000000000000 #some other large number
if __name__ == "__main__":
print count_palindromes(start, end)
Its a simple program which checks each number one by one. Its vary time consuming and takes a lot of computer resources.
Is there any other method/technique by which we can count Palindrome numbers? Any Algorithm to use for this?
I want to minimize time taken in producing the output.
When you want to count the numbers having some given property between two limits, it is often useful to solve the somewhat simpler problem
How many numbers with the given property are there between 0 and n?
Keeping one limit fixed can make the problem significantly simpler to tackle. When the simpler problem is solved, you can get the solution to the original problem with a simple subtraction:
countBetween(a,b) = countTo(b) - countTo(a)
or countTo(b ± 1) - countTo(a ± 1), depending on whether the limit is included in countTo and which limits shall be included in countBetween.
If negative limits can occur (not for palindromes, I presume), countTo(n) should be <= 0 for negative n (one can regard the function as an integral with respect to the counting measure).
So let us determine
palindromes_below(n) = #{ k : 0 <= k < n, k is a palindrome }
We get more uniform formulae for the first part if we pretend that 0 is not a palindrome, so for the first part, we do that.
Part 1: How many palindromes with a given number d of digits are there?
The first digit cannot be 0, otherwise it's unrestricted, hence there are 9 possible choices (b-1 for palindromes in an arbitrary base b).
The last digit is equal to the first by the fact that it shall be a palindrome.
The second digit - if d >= 3 - can be chosen arbitrarily and independently from the first. That also determines the penultimate digit.
If d >= 5, one can also freely choose the third digit, and so on.
A moment's thought shows that for d = 2*k + 1 or d = 2*k + 2, there are k digits that can be chosen without restriction, and one digit (the first) that is subject to the restriction that it be non-zero. So there are
9 * 10**k
d-digit palindromes then ((b-1) * b**k for base b).
That's a nice and simple formula. From that, using the formula for a geometric sum, we can easily obtain the number of palindromes smaller than 10n (that is, with at most n digits):
if n is even, the number is
n/2-1 n/2-1
2 * ∑ 9*10**k = 18 * ∑ 10**k = 18 * (10**(n/2) - 1) / (10 - 1) = 2 * (10**(n/2) - 1)
k=0 k=0
if n is odd, the number is
2 * (10**((n-1)/2) - 1) + 9 * 10**((n-1)/2) = 11 * (10**((n-1)/2) - 2
(for general base b, the numbers are 2 * (b**(n/2) - 1) resp. (b+1) * b**((n-1)/2) - 2).
That's not quite as uniform anymore, but still simple enough:
def palindromes_up_to_n_digits(n):
if n < 1:
return 0
if n % 2 == 0:
return 2*10**(n//2) - 2
else:
return 11*10**(n//2) - 2
(remember, we don't count 0 yet).
Now for the remaining part. Given n > 0 with k digits, the palindromes < n are either
palindromes with fewer than k digits, there are palindromes_up_to_n_digits(k-1) of them, or
palindromes with exactly k digits that are smaller than n.
So it remains to count the latter.
Part 2:
Letm = (k-1)//2 and
d[1] d[2] ... d[m] d[m+1] ... d[k]
the decimal representation of n (the whole thing works with the same principle for other bases, but I don't explicitly mention that in the following), so
k
n = ∑ d[j]*10**(k-j)
j=1
For each 1 <= c[1] < d[1], we can choose the m digits c[2], ..., c[m+1] freely to obtain a palindrome
p = c[1] c[2] ... c[m+1] {c[m+1]} c[m] ... c[2] c[1]
(the digit c[m+1] appears once for odd k and twice for even k). Now,
c[1]*(10**(k-1) + 1) <= p < (c[1] + 1)*10**(k-1) <= d[1]*10**(k-1) <= n,
so all these 10**m palindromes (for a given choice of c[1]!) are smaller than n.
Thus there are (d[1] - 1) * 10**m k-digit palindromes whose first digit is smaller than the first digit of n.
Now let us consider the k-digit palindromes with first digit d[1] that are smaller than n.
If k == 2, there is one if d[1] < d[2] and none otherwise. If k >= 3, for each 0 <= c[2] < d[2], we can freely choose the m-1 digits c[3] ... c[m+1] to obtain a palindrome
p = d[1] c[2] c[3] ... c[m] c[m+1] {c[m+1]} c[m] ... c[3] c[2] d[1]
We see p < n:
d[1]*(10**(k-1) + 1) + c[2]*(10**(k-2) + 10)
<= p < d[1]*(10**(k-1) + 1) + (c[2] + 1)*(10**(k-2) + 10)
<= d[1]*(10**(k-1) + 1) + d[2]*(10**(k-2) + 10) <= n
(assuming k > 3, for k == 3 replace 10**(k-2) + 10 with 10).
So that makes d[2]*10**(m-1) k-digit palindromes with first digit d[1] and second digit smaller than d[2].
Continuing, for 1 <= r <= m, there are
d[m+1]*10**(m-r)
k-digit palindromes whose first r digits are d[1] ... d[r] and whose r+1st digit is smaller than d[r+1].
Summing up, there are
(d[1]-1])*10**m + d[2]*10**(m-1) + ... + d[m]*10 + d[m+1]
k-digit palindromes that have one of the first m+1 digits smaller than the corresponding digit of n and all preceding digits equal to the corresponding digit of n. Obviously, these are all smaller than n.
There is one k-digit palindrome p whose first m+1 digits are d[1] .. d[m+1], we must count that too if p < n.
So, wrapping up, and now incorporating 0 too, we get
def palindromes_below(n):
if n < 1:
return 0
if n < 10:
return n # 0, 1, ..., n-1
# General case
dec = str(n)
digits = len(dec)
count = palindromes_up_to_n_digits(digits-1) + 1 # + 1 for 0
half_length = (digits-1) // 2
front_part = dec[0:half_length + 1]
count += int(front_part) - 10**half_length
i, j = half_length, half_length+1
if digits % 2 == 1:
i -= 1
while i >= 0 and dec[i] == dec[j]:
i -= 1
j += 1
if i >= 0 and dec[i] < dec[j]:
count += 1
return count
Since the limits are both to be included in the count for the given problem (unless the OP misunderstood), we then have
def count_palindromes(start, end):
return palindromes_below(end+1) - palindromes_below(start)
for a fast solution:
>>> bench(10**100,10**101-1)
900000000000000000000000000000000000000000000000000 palindromes between
10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
and
99999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
in 0.000186920166016 seconds
Actually, it's a problem for Google Codejam (which I'm pretty sure you're not supposed to get outside help on) but alas, I'll throw in my 2 cents.
The idea I came up with (but failed to implement) for the large problem was to precompile (generated at runtime, not hardcoded into the source) a list of all palindromic numbers less than 10^15 (there's not very many, it takes like ~60 seconds) then find out how many of those numbers lie between the bounds of each input.
EDIT: This won't work on the 10^100 problem, like you said, that would be a mathematical solution (although there is a pattern if you look, so you'd just need an algorithm to generate all numbers with that pattern)
I presume this is for something like Project Euler... my rough idea would be to generate all numbers up to half the length of your limit (like, if you're going to 99999, go up to 99). Then reverse them, append them to the unreversed one, and potentially add a digit in the middle (for the numbers with odd lengths). You'll might have to do some filtering for duplicates, or weird ones (like if you had a zero at the beginning of the number or sommat) but that should be a lot faster than what you were doing.

Categories

Resources