python - print squares of numbers which are palindromes : improve efficiency - python

I have an assignment to do. The problem is something like this. You give a number, say x. The program calculates the square of the numbers starting from 1 and prints it only if it's a palindrome. The program continues to print such numbers till it reaches the number x provided by you.
I have solved the problem. It works fine for uptil x = 10000000. Works fine as in executes in a reasonable amount of time. I want to improve upon the efficiency of my code. I am open to changing the entire code, if required. My aim is to make a program that could execute 10^20 within around 5 mins.
limit = int(input("Enter a number"))
def palindrome(limit):
count = 1
base = 1
while count < limit:
base = base * base #square the number
base = list(str(base)) #convert the number into a list of strings
rbase = base[:] #make a copy of the number
rbase.reverse() #reverse this copy
if len(base) > 1:
i = 0
flag = 1
while i < len(base) and flag == 1:
if base[i] == rbase[i]: #compare the values at the indices
flag = 1
else:
flag = 0
i += 1
if flag == 1:
print(''.join(base)) #print if values match
base = ''.join(base)
base = int(base)
base = count + 1
count = count + 1
palindrome(limit)

He're my version:
import sys
def palindrome(limit):
for i in range(limit):
istring = str(i*i)
if istring == istring[::-1]:
print(istring,end=" ")
print()
palindrome(int(sys.argv[1]))
Timings for your version on my machine:
pu#pumbair: ~/Projects/Stackexchange time python3 palin1.py 100000
121 484 676 10201 12321 14641 40804 44944 69696 94249 698896 1002001 1234321
4008004 5221225 6948496 100020001 102030201 104060401 121242121 123454321 125686521
400080004 404090404 522808225 617323716 942060249
real 0m0.457s
user 0m0.437s
sys 0m0.012s
and for mine:
pu#pumbair: ~/Projects/Stackexchange time python3 palin2.py 100000
0 1 4 9
121 484 676 10201 12321 14641 40804 44944 69696 94249 698896 1002001 1234321
4008004 5221225 6948496 100020001 102030201 104060401 121242121 123454321 125686521
400080004 404090404 522808225 617323716 942060249
real 0m0.122s
user 0m0.104s
sys 0m0.010s
BTW, my version gives more results (0, 1, 4, 9).

Surely something like this will perform better (avoiding the unnecessary extra list operations) and is more readable:
def palindrome(limit):
base = 1
while base < limit:
squared = str(base * base)
reversed = squared[::-1]
if squared == reversed:
print(squared)
base += 1
limit = int(input("Enter a number: "))
palindrome(limit)

I think we can do it a little bit easier.
def palindrome(limit):
count = 1
while count < limit:
base = count * count # square the number
base = str(base) # convert the number into a string
rbase = base[::-1] # make a reverse of the string
if base == rbase:
print(base) #print if values match
count += 1
limit = int(input("Enter a number: "))
palindrome(limit)
String into number and number into string conversions were unnecessary. Strings can be compared, this is why you shouldn't make a loop.

You can keep a list of square palindromes upto a certain limit(say L) in memory.If the Input number x is less than sqrt(L) ,you can simply iterate over the list of palindromes and print them.This way you wont have to iterate over every number and check if its square is palindrome .
You can find a list of square palindromes here : http://www.fengyuan.com/palindrome.html

OK, here's my program. It caches valid suffixes for squares (i.e. the values of n^2 mod 10^k for a fixed k), and then searches for squares which have both that suffix and start with the suffix reversed. This program is very fast: in 24 seconds, it lists all the palindromic squares up to 10^24.
from collections import defaultdict
# algorithm will print palindromic squares x**2 up to x = 10**n.
# efficiency is O(max(10**k, n*10**(n-k)))
n = 16
k = 6
cache = defaultdict(list)
print 0, 0 # special case
# Calculate everything up to 10**k; these will be the prefix/suffix pairs we use later
tail = 10**k
for i in xrange(tail):
if i % 10 == 0: # can't end with 0 and still be a palindrome
continue
sq = i*i
s = str(sq)
if s == s[::-1]:
print i, s
prefix = int(str(sq % tail).zfill(k)[::-1])
cache[prefix].append(i)
prefixes = sorted(cache)
# Loop through the rest, but only consider matching prefix/suffix pairs
for l in xrange(k*2+1, n*2+1):
for p in prefixes:
low = (p * 10**(l-k))**.5
high = ((p+1) * 10**(l-k))**.5
low = int(low / tail) * tail
high = (int(high / tail) + 1) * tail
for n in xrange(low, high, tail):
for suf in cache[p]:
x = n + suf
s = str(x*x)
if s == s[::-1]:
print x, s
Sample output:
0 0
1 1
2 4
3 9
11 121
22 484
26 676
101 10201
111 12321
121 14641
202 40804
212 44944
<snip>
111010010111 12323222344844322232321
111100001111 12343210246864201234321
111283619361 12384043938083934048321
112247658961 12599536942224963599521
128817084669 16593841302620314839561
200000000002 40000000000800000000004

Related

123 to 321 problem giving errors when iterating

I made this program trying to solve a problem which is fliping a number. For example, when the number 123 is the number inputed the number 321 should be the output.
#function to swap number positions on the array
def swapPositions(list, pos1, pos2):
i = list[pos1]
list[pos1] = list[pos2]
list[pos2] = i
myList = []
theNum = int(input("enter the value"))
theNumInString = str(theNum)
#loop to separate numbers on the integer into each position of the array
for char in theNum2:
myList.append(char)
#this variable is to know how many times we should swap the positions
numofSwaps = len(myList) % 2
posi1 = 0
posi2 = len(myList) - 1
while numofSwaps != 0:
swapPositions(myList, posi1, posi2)
#I add one and subtract one from the positions so they move further to the middle to swap other positions
posi1 += 1
posi2 -= 1
numofSwaps -= 1
number = "".join(myList)
print(number)
what happens when I run the code and try for example 123 it returns 321 as expected
BUT here comes the problem... when I input 12345 the output is 52341 which only swaps the outer two numbers.
this can be done without converting the number to a string, for example
# note: this example works for positive numbers only
def reverseNum(x):
y = 0
while x > 0:
y = y*10 + x%10
x //= 10
return y
>>> reverseNum(3124)
4213

sum of every other digit and doubling the rest

I'm working on a card number check code, for now I created a function that asks for the card number and checks if it is 8 digits or not (it has to be 8) and then calls another function that will do the math and check if the card is valid. For this function:
Starting from the rightmost digit, form the sum of every other digit. For example, if the card number is 1234 5678, then you form the sum 8 + 6 + 4 + 2 = 20
Double each of the digits that were not included in the preview step and then add all digits of the resulting numbers. For example, the digits that were not included are 7 5 3 1, we double the, 14 10 6 2, and then we sum each digit, 1 + 4 + 1 + 0 + 6 + 2 = 14
Add the sums of the two steps, 20 + 14 = 34, if the last digit is 0 then the card is valid, otherwise it is not valid (which is our case)
My problem is that I don't know how to iterate and get the sum of every other digit or double the other number which were not included in step 2. My thought was to use a while loop but then what?
EDIT: since some answers used lists... we didn't study lists yet, so I should not use it, we are only allowed to use sample stuff, loops, functions, etc.. even sum(map()) we didn't study
That is my code for now (its not much but just thought put it anyway)
def getCard():
CardInput = int(input("Enter your 8 digit credit card number: "))
if len(CardInput) == 8:
CardCheck(CardInput)
else:
print("Invalid Input: Should be exactly 8 digits!")
getCard()
def CardCheck(CardNumber):
Position = 0
Sum = 0
DoubleSum = 0
FinalSum = 0
while CardNumber >= 0:
Position += 1
So, the ugly way of doing is, you can write a for loop and use indexing for access specific elements
for i in range(len(CardInput)):
# it will iterate from 0 to 7
print(CardInput[i]) # here you get ith element
if i % 2 == 1:
print("I am other!") # you can sum your things here into another variable
Or with while:
while position < len(CardInput):
print(CardInput[position])
position += 1
It assumes CardInput is str, so I recommend to not convert it earlier.
However pythonic way would be
sum(map(int, CardInput[1::2])))
CardInput[1::2] returns list of every second element starting from second (0 is first).
map converts every element to in.
sum sums elements.
prompt = "Enter the eight-digit number: "
while True:
number = input(prompt)
if len(number) == 8 and number.isdigit():
break
prompt = "Oops, try again: "
first_digits = number[1::2] # If the user entered '12345678', this will be the substring '2468'
first_sum = sum(map(int, first_digits)) # Take each digit (character), map it to a single-digit integer, and take the sum of all single-digit integers
second_digits = number[0::2] # If the user entered '12345678', this will be the substring '1357'
doubled_ints = [int(char) * 2 for char in second_digits] # Take each digit (character), turn it into an integer, double it, and put it in a list.
second_sum = sum(map(int, "".join(map(str, doubled_ints)))) # Merge all integers in 'doubled_ints' into a single string, take each character, map it to a single digit integer, and take the sum of all integers.
total_sum = first_sum + second_sum
total_sum_last_digit = str(total_sum)[-1]
is_valid_card = (total_sum_last_digit == '0')
if is_valid_card:
print("Your card is valid (total sum: {})".format(total_sum))
else:
print("Your card is NOT valid (total sum: {})".format(total_sum))
def getCard():
CardInput = input("Enter your 8 digit credit card number: ")
if len(CardInput) == 8:
CardCheck(CardInput)
else:
print("Invalid Input: Should be exactly 8 digits!")
getCard()
def CardCheck(CardNumber):
list_CardNumber = [x for x in "25424334"]
Sum = sum(int(x) for x in list_CardNumber[1:8:-2])
DoubleSum = 2*sum(int(x) for x in list_CardNumber[0:8:-2])
FinalSum = Sum + DoubleSum
if str(FinalSum)[-1] == "0":
print("Valid Input")
else:
print("Invalid Input")
To get you started, you should check out enumerate(), it'll simplify things if you're just going to use loops by giving you easy access to both the index and value every loop.
step1 = 0
for i, x in enumerate(number):
if i % 2:
print('index: '+ str(i), 'value: '+ x)
step1 += int(x)
print('step 1: ', step1)
Output:
index: 1 value: 2
index: 3 value: 4
index: 5 value: 6
index: 7 value: 8
step 1: 20
You can use:
# lets say
CardNumber = '12345678'
# as mentioned by kosciej16
# get every other digit starting from the second one
# convert them to integers and sum
part1 = sum(map(int, CardNumber[1::2]))
# get every other digit starting from the first one
# convert them to integers and double them
# join all the digits into a string then sum all the digits
part2 = sum(map(int,''.join(list(map(lambda x: str(int(x)*2), CardNumber[0::2])))))
result = part1 + part2
print(result)
Output:
34
Edit:
Only with loops you can use:
# lets say
CardNumber = '12345678'
total_sum = 0
for idx, digit in enumerate(CardNumber):
if idx % 2 == 1:
total_sum += int(digit)
else:
number = str(int(digit)*2)
for d in number:
total_sum += int(d)
print(total_sum)
output:
34
Since you need to iterate over the digits, it's actually easier IMO if you leave it as a string, rather than converting the input to an int; that way you can just iterate over the digits and convert them to int individuall to do math on them.
Given an 8-digit long string card, it might look like this, broken into steps:
even_sum = sum(int(n) for n in card[1::2])
double_odds = (2 * int(n) for n in card[0::2])
double_odd_sum = sum(int(c) for do in double_odds for c in str(do))
All together with some logic to loop while the input is invalid:
def get_card() -> str:
"""Returns a valid card number, or raises ValueError."""
card = input("Enter your 8 digit credit card number: ")
if len(card) != 8 or not card.isdecimal():
raise ValueError("Invalid input: Should be exactly 8 digits!")
card_check(card)
return card
def card_check(card: str) -> None:
"""Raises ValueError if card checksum fails, otherwise returns None."""
even_sum = sum(int(n) for n in card[1::2])
double_odds = (2 * int(n) for n in card[::2])
double_odd_sum = sum(int(c) for do in double_odds for c in str(do))
if (even_sum + double_odd_sum) % 10:
raise ValueError("Card checksum failed!")
while True:
try:
print(f"{get_card()} is a valid card number!")
except ValueError as e:
print(e)

Trying to use zfill and increment characters with Python

Hello lovely stackoverflowians!
I am fairly new to programming. Only have been programming a little under 2 months using CS50 which uses C and MITx Python. I went on Codewars and am trying to solve a problem where you basically get an id and then come out with a license plate number like this aaa001...aaa999, aab001...zzz999
if you catch my drift. For some reason my code compiles but when I run it I get this error.
File "/Users/pauliekennedy/Desktop/temp.py", line 9, in find_the_number_plate
a_str = (a_numb.zfill(3), range(0, 10))
AttributeError: 'int' object has no attribute 'zfill'
Because of this I am not able to test my code. If you could help me with this problem I would be much appreciated. As well, if you have anything to say about my code in general, tips, advice how to make it better, and if it will achieve this goal at all. Here is my code thanks again all.
#set number to 1 to start
a_numb = 1
#incrementing loop when 999 go back set back 0
while a_numb <1001:
a_numb += 1
a_str = str(a_numb)
# giving the number 00 or just 0 in front
if a_numb < 100:
a_str = (a_numb.zfill(3), range(0, 10))
#resetting the number back to 1
if a_numb == 999:
a_numb = 1
# Setting i to 0 and incrementing the characters
i = 0
ch = 'a'
ch2 = 'a'
ch3 = 'a'
#looping through alphabet
for i in range(26):
ch = chr(ord(ch) + 1)
print(ch)
if i == 26:
i = 0
if ch == 'z':
ch2 = chr(ord(ch) + 1)
if ch == 'z' & ch2 == 'z':
ch3(ord(ch) + 1)
# Adding results together and returning the string of them all
letter_plate = str(ch3 + ch2 + ch)
plate = str(a_numb) + str(letter_plate)
return plate```
Maybe you could consider using f-string string formatting instead:
def find_the_number_plate(customer_id):
number_part = customer_id % 999 + 1
customer_id //= 999
letter_part = ['a', 'a', 'a']
i = 0
while customer_id:
letter_part[i] = chr(ord('a') + customer_id % 26)
customer_id //= 26
i += 1
return f"{''.join(letter_part)}{number_part:03}"
You could use product from itertools to form the license plate numbers from 3 letters and numbers from 1 to 999 formatted with leading zeros:
from itertools import product
letter = "abcdefghijklmnopqrstuvwxyz"
numbers = (f"{n:03}" for n in range(1,1000))
plates = [*map("".join,product(letter,letter,letter,numbers))]
for plate in plates: print(plate)
aaa001
aaa002
aaa003
aaa004
aaa005
aaa006
aaa007
aaa008
...
If you only need to access a license place at a specific index, you don't have to generate the whole list. You can figure out which plate number will be at a given index by decomposing the index in chunks of 999,26,26,26 corresponding to the available option at each position/chunk of the number.
def plate(i):
letter = "abcdefghijklmnopqrstuvwxyz"
result = f"{i%999+1:03}"
i //= 999
for _ in range(3):
result = letter[i%26] + result
i //= 26
return result
output:
for i in range(10):print(plate(i))
aaa001
aaa002
aaa003
aaa004
aaa005
aaa006
aaa007
aaa008
aaa009
aaa010
plate(2021) # aac024

Print Each Combination in Shell

If you have a range of numbers from 1-49 with 6 numbers to choose from, there are nearly 14 million combinations. Using my current code (below), I have only 85,805 combinations remaining. I want to get all those 85,805 combinations to print into the Python shell showing every combination rather than the number of combinations possible as I'm currently seeing. Is that possible? Here's my code:
import functools
_MIN_SUM = 152
_MAX_SUM = 152
_MIN_NUM = 1
_MAX_NUM = 49
_NUM_CHOICES = 6
_MIN_ODDS = 2
_MAX_ODDS = 4
#functools.lru_cache(maxsize=None)
def f(n, l, s = 0, odds = 0):
if s > _MAX_SUM or odds > _MAX_ODDS:
return 0
if n == 0 :
return int(s >= _MIN_SUM and odds >= _MIN_ODDS)
return sum(f(n-1, i+2, s+i, odds + i % 2) for i in range(l, _MAX_NUM+1))
result = f(_NUM_CHOICES, _MIN_NUM)
print('Number of choices = {}'.format(result))
Thank you!
Printing to the console is rather slow. You might want to print it to a file instead.
print("Hello World")
# vs
with open("file.txt", "w") as f:
print("Hello World", file=f)
Try using for loops and recursion together:
def combinations(base, numbers, placesRemaining):
out = []
for i in numbers:
if placesRemaining <= 1:
out.append(base*i)
else:
out.extend(combinations(base*i, numbers, placesRemaining-1))
return out
places = 6
numbers = range(1, 50)
answer = combinations(1, numbers, places)
That solution is not likely to run into the recursion limit, as the maximum recursion depth is equal to places. I did not run this on the full problem, but it performed well on smaller ones. Altering the starting base will multiply every number you calculate by that number, so I do not recommend it.

Kaprekar numbers in Python

I aas solving a problem on HackerRank and the problem is as follows:
A modified Kaprekar number is a positive whole number n with d digits, such that when we split its square into two pieces - a right hand piece r with d digits and a left hand piece l that contains the remaining d or d−1 digits, the sum of the pieces is equal to the original number (i.e. l + r = n).
Alternatively, a modified Kaprekar number is a positive whole number n with 2d digits (if its number of digits is even) or 2d + 1 digits (if its number of digits is odd), such that when we split its square into two pieces, a right hand piece r containing d or d + 1 digits, and a left piece l containing the remaining d digits, the sum of the two pieces is equal to the original number.
We need to find Kaprekar numbers within a given range. So I wrote the following piece of code:
def checkIsKaprekar( num ):
string_num = str(num**2)
if num == int(string_num[:len(string_num)//2]) + int(string_num[len(string_num)//2:]):
return True
kaprekars = [ str(i) for i in range(int(input()),int(input())) if checkIsKaprekar(i) == True ]
print (' '.join(kaprekars))
The problem with the above solution is when we pass single digit numbers, they are converted into a string of which the right half is the number itself and the left half is ''. Now I am converting these strings to int(), which throws an exception.
To avoid this situation, I changed my code to this:
def checkIsKaprekar( num ):
string_num = str(num**2)
left_string = string_num[:len(string_num)//2]
right_string = string_num[len(string_num)//2:]
left_num = int(left_string) if left_string != '' else 0
right_num = int(right_string) if right_string != '' else 0
if num == left_num + right_num:
return True
kaprekars = [ str(i) for i in range(int(input()),int(input())) if checkIsKaprekar(i) == True ]
print (' '.join(kaprekars))
But I don't like this code even though it does solve the purpose. How can I tweak my original code to produce the right output?
Link to the question: https://www.hackerrank.com/challenges/kaprekar-numbers
Is there any way I can specify a default value of 0 if the string is ''?
As answered by B.M. int('0'+string_num[:len(string_num)//2]) works perfectly. int('0'+'') produces 0.
Maybe the cleanest way would be to define a function like this:
def to_int(s)
return int(s) if s else 0
But it may not be worth the overhead in this case.
It's often convenient to solve problems on integers ... with integers:
def kaprekar(n):
l=1
while l<=n : l*=10
return n== (n*n)//l + (n*n)%l
[print(x,end=' ') for x in range(1,100) if kaprekar(x)]
# 1 9 45 55 99
def checkIsKaprekar( num ):
if(num==1):
return True
elif((num**2)<15):
return False
string_num = str(num**2)
if num == int(string_num[:len(string_num)//2]) + int(string_num[len(string_num)//2:]):
return True
You can find Kaprekar numbers within a given range by testing each number for the given condition in the given range. For example,
def print_Kaprekar_nums(start, end):
for i in range(start, end + 1):
# Get the digits from the square in a list:
sqr = i ** 2
digits = str(sqr)
# Now loop from 1 to length of the number - 1, sum both sides and check
length = len(digits)
for x in range(1, length):
left = int("".join(digits[:x]))
right = int("".join(digits[x:]))
if (left + right) == i:
print("Number: " + str(i) + "Left: " + str(left) + " Right: " + str(right))
print_Kaprekar_nums(150, 8000)
This will give the output:
Number: 297Left: 88 Right: 209
Number: 703Left: 494 Right: 209
Number: 999Left: 998 Right: 1
Number: 1000Left: 1000 Right: 0
Number: 2223Left: 494 Right: 1729
Number: 2728Left: 744 Right: 1984
Number: 4879Left: 238 Right: 4641
Number: 4950Left: 2450 Right: 2500
Number: 5050Left: 2550 Right: 2500
Number: 5292Left: 28 Right: 5264
Number: 7272Left: 5288 Right: 1984
Number: 7777Left: 6048 Right: 1729

Categories

Resources