|
| 1 | +""" |
| 2 | +Project Euler Problem 686: https://projecteuler.net/problem=686 |
| 3 | +
|
| 4 | +2^7 = 128 is the first power of two whose leading digits are "12". |
| 5 | +The next power of two whose leading digits are "12" is 2^80. |
| 6 | +
|
| 7 | +Define p(L,n) to be the nth-smallest value of j such that |
| 8 | +the base 10 representation of 2^j begins with the digits of L. |
| 9 | +
|
| 10 | +So p(12, 1) = 7 and p(12, 2) = 80. |
| 11 | +
|
| 12 | +You are given that p(123, 45) = 12710. |
| 13 | +
|
| 14 | +Find p(123, 678910). |
| 15 | +""" |
| 16 | + |
| 17 | +import math |
| 18 | + |
| 19 | + |
| 20 | +def log_difference(number: int) -> float: |
| 21 | + """ |
| 22 | + This function returns the decimal value of a number multiplied with log(2) |
| 23 | + Since the problem is on powers of two, finding the powers of two with |
| 24 | + large exponents is time consuming. Hence we use log to reduce compute time. |
| 25 | +
|
| 26 | + We can find out that the first power of 2 with starting digits 123 is 90. |
| 27 | + Computing 2^90 is time consuming. |
| 28 | + Hence we find log(2^90) = 90*log(2) = 27.092699609758302 |
| 29 | + But we require only the decimal part to determine whether the power starts with 123. |
| 30 | + SO we just return the decimal part of the log product. |
| 31 | + Therefore we return 0.092699609758302 |
| 32 | +
|
| 33 | + >>> log_difference(90) |
| 34 | + 0.092699609758302 |
| 35 | + >>> log_difference(379) |
| 36 | + 0.090368356648852 |
| 37 | +
|
| 38 | + """ |
| 39 | + |
| 40 | + log_number = math.log(2, 10) * number |
| 41 | + difference = round((log_number - int(log_number)), 15) |
| 42 | + |
| 43 | + return difference |
| 44 | + |
| 45 | + |
| 46 | +def solution(number: int = 678910) -> int: |
| 47 | + """ |
| 48 | + This function calculates the power of two which is nth (n = number) |
| 49 | + smallest value of power of 2 |
| 50 | + such that the starting digits of the 2^power is 123. |
| 51 | +
|
| 52 | + For example the powers of 2 for which starting digits is 123 are: |
| 53 | + 90, 379, 575, 864, 1060, 1545, 1741, 2030, 2226, 2515 and so on. |
| 54 | + 90 is the first power of 2 whose starting digits are 123, |
| 55 | + 379 is second power of 2 whose starting digits are 123, |
| 56 | + and so on. |
| 57 | +
|
| 58 | + So if number = 10, then solution returns 2515 as we observe from above series. |
| 59 | +
|
| 60 | + Wwe will define a lowerbound and upperbound. |
| 61 | + lowerbound = log(1.23), upperbound = log(1.24) |
| 62 | + because we need to find the powers that yield 123 as starting digits. |
| 63 | +
|
| 64 | + log(1.23) = 0.08990511143939792, log(1,24) = 0.09342168516223506. |
| 65 | + We use 1.23 and not 12.3 or 123, because log(1.23) yields only decimal value |
| 66 | + which is less than 1. |
| 67 | + log(12.3) will be same decimal vale but 1 added to it |
| 68 | + which is log(12.3) = 1.093421685162235. |
| 69 | + We observe that decimal value remains same no matter 1.23 or 12.3 |
| 70 | + Since we use the function log_difference(), |
| 71 | + which returns the value that is only decimal part, using 1.23 is logical. |
| 72 | +
|
| 73 | + If we see, 90*log(2) = 27.092699609758302, |
| 74 | + decimal part = 0.092699609758302, which is inside the range of lowerbound |
| 75 | + and upperbound. |
| 76 | +
|
| 77 | + If we compute the difference between all the powers which lead to 123 |
| 78 | + starting digits is as follows: |
| 79 | +
|
| 80 | + 379 - 90 = 289 |
| 81 | + 575 - 379 = 196 |
| 82 | + 864 - 575 = 289 |
| 83 | + 1060 - 864 = 196 |
| 84 | +
|
| 85 | + We see a pattern here. The difference is either 196 or 289 = 196 + 93. |
| 86 | +
|
| 87 | + Hence to optimize the algorithm we will increment by 196 or 93 depending upon the |
| 88 | + log_difference() value. |
| 89 | +
|
| 90 | + Lets take for example 90. |
| 91 | + Since 90 is the first power leading to staring digits as 123, |
| 92 | + we will increment iterator by 196. |
| 93 | + Because the difference between any two powers leading to 123 |
| 94 | + as staring digits is greater than or equal to 196. |
| 95 | + After incrementing by 196 we get 286. |
| 96 | +
|
| 97 | + log_difference(286) = 0.09457875989861 which is greater than upperbound. |
| 98 | + The next power is 379, and we need to add 93 to get there. |
| 99 | + The iterator will now become 379, |
| 100 | + which is the next power leading to 123 as starting digits. |
| 101 | +
|
| 102 | + Lets take 1060. We increment by 196, we get 1256. |
| 103 | + log_difference(1256) = 0.09367455396034, |
| 104 | + Which is greater than upperbound hence we increment by 93. Now iterator is 1349. |
| 105 | + log_difference(1349) = 0.08946415071057 which is less than lowerbound. |
| 106 | + The next power is 1545 and we need to add 196 to get 1545. |
| 107 | +
|
| 108 | + Conditions are as follows: |
| 109 | +
|
| 110 | + 1) If we find a power, whose log_difference() is in the range of |
| 111 | + lower and upperbound, we will increment by 196. |
| 112 | + which implies that the power is a number which will lead to 123 as starting digits. |
| 113 | + 2) If we find a power, whose log_difference() is greater than or equal upperbound, |
| 114 | + we will increment by 93. |
| 115 | + 3) if log_difference() < lowerbound, we increment by 196. |
| 116 | +
|
| 117 | + Reference to the above logic: |
| 118 | + https://math.stackexchange.com/questions/4093970/powers-of-2-starting-with-123-does-a-pattern-exist |
| 119 | +
|
| 120 | + >>> solution(1000) |
| 121 | + 284168 |
| 122 | +
|
| 123 | + >>> solution(56000) |
| 124 | + 15924915 |
| 125 | +
|
| 126 | + >>> solution(678910) |
| 127 | + 193060223 |
| 128 | +
|
| 129 | + """ |
| 130 | + |
| 131 | + power_iterator = 90 |
| 132 | + position = 0 |
| 133 | + |
| 134 | + lower_limit = math.log(1.23, 10) |
| 135 | + upper_limit = math.log(1.24, 10) |
| 136 | + previous_power = 0 |
| 137 | + |
| 138 | + while position < number: |
| 139 | + difference = log_difference(power_iterator) |
| 140 | + |
| 141 | + if difference >= upper_limit: |
| 142 | + power_iterator += 93 |
| 143 | + |
| 144 | + elif difference < lower_limit: |
| 145 | + power_iterator += 196 |
| 146 | + |
| 147 | + else: |
| 148 | + previous_power = power_iterator |
| 149 | + power_iterator += 196 |
| 150 | + position += 1 |
| 151 | + |
| 152 | + return previous_power |
| 153 | + |
| 154 | + |
| 155 | +if __name__ == "__main__": |
| 156 | + import doctest |
| 157 | + |
| 158 | + doctest.testmod() |
| 159 | + |
| 160 | + print(f"{solution() = }") |
0 commit comments