Palindrome checker in python, out of range index workaround - python

Hi I am trying to make this program work without using reverse or sort functions in python 3. It is simply supposed to check for palindromes. The problem I am running into is that I get an index out of range everytime I run it. Is there a way to work around this?
def is_palindrome(word):
if len(word) <= 1:
return True
else:
left = 0
right = len(word) - 1
while left < right:
if word[left] == word[right]:
left += 1
right += 1
else:
return False
return True
is_palindrome("mom")
is_palindrome("root")
is_palindrome("racecar")

right += 1 should be right -= 1.

You can write it a bit more succinctly.
>>> def is_palin(w):
... return len(w) <= 1 or (w[0] == w[-1] and is_palin(w[1:-1]))
...
>>> is_palin("xax")
True
>>> is_palin("xaax")
True
>>> is_palin("xaab")
False

Related

How to change an iteration to recursion and vice-versa

I would like to change a function from iteration to recursion.
Here is the function:
def differences(values):
result = []
for i in range(len(values) - 1):
result.append(values [i+1] - values[i])
return result
def palindrome(word):
if len(word) <=1:
return True
if word[0] != word[-1]:
return False
return palindrome(word[1:-1])
In pseudocode:
differences list =
if list has < 2 elements result is empty list
otherwise,
list = first, second, *rest
result is [second - first] + differences([second, *rest])
Implementing this in python, you can elegantly use the new match statement.
Iteration:
palindrome str =
first = 0, last = len(str) - 1
while first < last
if str[first] != str[last] return false
first++, last--
return true
The first function could be made recursive as follows:
def differences2(values, i = 1, res = []):
if i < len(values):
res.append(values[i] - values[i-1])
return differences2(values, i + 1, res)
else:
return res
The second could be made iterative like this:
def palindrome2(word):
pointer = 0
while 2 * pointer + 1 <= len(word):
if word[pointer] != word[-pointer-1]:
return False
pointer += 1
return True
The recursive implementation of this can be very compact:
def palindrome(word, i = 0):
if 2 * i + 1 <= len(word):
return False if word[i] != word[-i-1] else palindrome(word, i = i + 1)
return True
As a side note: in general, while very elegant, I am always a little bit worried in implementing recursive solutions in Python - iterative one are more readable and robust (again, those are my 2 cents).

Am I oversimplifying the concept of " if a string is one edit away"?

I've been doing a good amount of studying and practicing algorithms, and I ran into one that asked the question (summarizing here) "Given two strings, return True if the strings are one edit away (either by removing, inserting, or replacing a character). Return False if not."
I went about this problem by comparing two strings and counting the amount of letters that are in string1, but not in string2. If there is more than one letter missing, then it will return False.
Here is my Python code:
def oneAway(string1, string2):
string1 = string1.lower()
string2 = string2.lower()
# counts the number of edits required
counter = 0
for i in string1:
if i not in string2:
counter += 1
if counter > 1:
return False
else:
return True
I'd like to hear other people's approaches to this problem, and please point out if I have oversimplified this concept.
Why call .lower() on both strings? Based on the question, oneAway('abc', 'ABC') should be False.
Building off of the other comments, how about this:
def oneAway(s1, s2):
i, j = 0, 0
mistake_occurred = False
while i < len(s1) and j < len(s2):
if s1[i] != s2[j]:
if mistake_occurred:
return False
mistake_occurred = True
i += 1
j += 1
if mistake_occurred and (i != len(s1) or j != len(s2)):
return False
if i < len(s1) - 1 or j < len(s2) - 1:
return False
return True
You have to check each editing action specifically:
def isOneEdit(A,B):
# replacing one char
if len(A) == len(B) and sum(a!=b for a,b in zip(A,B)) == 1:
return True
# inserting one char in A to get B
if len(A) == len(B)-1 and any(A==B[:i]+B[i+1:] for i in range(len(B))):
return True
# removing one char (like inserting one in B to get A)
if len(A) == len(B)+1:
return isOneEdit(B,A)
return False
print(isOneEdit("abc","abd")) # True - replace c with d
print(isOneEdit("abd","abcd")) # True - insert c
print(isOneEdit("abcd","abd")) # True - delete c
print(isOneEdit("abcde","abd")) # False
Alternatively, you can compare the size of the common prefix and suffix of the two strings to the length of the longest one:
def isOneEdit(A,B):
if abs(len(A)-len(B))>1: return False
commonPrefix = next((i for i,(a,b) in enumerate(zip(A,B)) if a!=b),len(A))
commonSuffix = next((i for i,(a,b) in enumerate(zip(reversed(A),reversed(B))) if a!=b),len(B))
editSize = max(len(A),len(B)) - (commonPrefix+commonSuffix)
return editSize <= 1

Project Euler #37 issue

I tried to solve Project Euler #37:
The number 3797 has an interesting property. Being prime itself, it is possible to continuously remove digits from left to right, and remain prime at each stage: 3797, 797, 97, and 7. Similarly we can work from right to left: 3797, 379, 37, and 3.
Find the sum of the only eleven primes that are both truncatable from left to right and right to left.
NOTE: 2, 3, 5, and 7 are not considered to be truncatable primes.
I wrote my code in Python but I am facing weird issues.
Here's my code:
def isPrime(n):
if n == 2 or n == 3 or n == 5: return True
if n < 2 or n%2 == 0: return False
if n < 9: return True
if n%3 == 0: return False
if n%5 == 0: return False
r = int(n**0.5)
f = 5
while f <= r:
if n%f == 0: return False
if n%(f+2) == 0: return False
f +=6
return True
def gen(nb):
results = []
nb_str = str(nb)
for k in range(0, len(nb_str) - 1):
results.append(nb_str[k:])
results.append(nb_str[-k:])
return results
def check(nb):
for t in gen(nb):
if not isPrime(int(t)):
return False
return True
c = 0
s = 0
i = 2
while c != 11:
if check(i):
c += 1
s += i
i += 1
print(s)
Where does the error come from? (The expected result is 748317)
I suspect the errors coming from the results list
Yes, the gen() function is not working correctly as your slicing is off, also, you count 2, 3, 5 and 7 as truncatable primes which the question denies.
The second slice should be the other way around:
>>> s = 'abcd'
>>> for i in range(1,len(s)-1):
... print(s[i:])
... print(s[:-i])
...
bcd
abc
cd
ab
which we can see produces the right strings.
Altogether then, the function should be:
def gen(nb):
results = [nb]
nb_str = str(nb)
for k in range(1, len(nb_str)):
results.append(int(nb_str[k:]))
results.append(int(nb_str[:-k]))
return results
note I also added a string to int conversion - not sure how Python didn't make that obvious for you :/
And before get the full solution, Project Euler nearly always gives you an example which you can use to check your code:
>>> check(3797)
True
You must also add a condition in the check function to return False if the number is 2, 3, 5 or 7 as this is stated clearly in the question.
And the result is the expected: 748317.
Joe Iddon has explained the error in your code, but you can speed it up a little by turning gen into an actual generator. That way, you can stop checking the results for a given nb as soon as you detect a composite number (and gen will stop generating them). I've also made a few minor tweaks to your primality tester. Remember, the or operator short-circuits, so if a is True-ish in a or b then it doesn't bother evaluating b.
def isPrime(n):
if n in {2, 3, 5, 7}:
return True
if n < 2 or n%2 == 0:
return False
if n%3 == 0 or n%5 == 0:
return False
r = int(n**0.5)
f = 5
while f <= r:
if n%f == 0 or n%(f+2) == 0:
return False
f += 6
return True
def gen(nb):
yield nb
nb_str = str(nb)
for k in range(1, len(nb_str)):
yield int(nb_str[k:])
yield int(nb_str[:-k])
def check(nb):
for t in gen(nb):
if not isPrime(t):
return False
return True
c = s = 0
# Don't check single digit primes
i = 11
while c < 11:
if check(i):
c += 1
s += i
print(i)
i += 2
print('sum', s)
output
23
37
53
73
313
317
373
797
3137
3797
739397
sum 748317
In fact, you can get rid of the check function, and replace it with all, which also short-circuits, like or does. So you can replace the
if check(i):
with
if all(map(isPrime, gen(i))):

Find a number in a another number

How can I find if a number is in another number and it's location in python?
Example: finding if 58 is in 45896 and its location?
Output: True and 2 Place
PS: Is there a way without invoking strings. Just with number?
>>> x=str(45896)
>>> str(58) in x
True
>>> x.index(str('58')) + 1
2
Since you're asking for a method where the values are not converted to strings.
Beware, I've not tested this for all possible cases. So you may be able to find bugs. If and when you do, please let me know so I can fix this.
def find_length(num):
# If number is 0, return the length as 1.
if num == 0:
return 1
length = 0
# Loop over the number by dividing it by 10
# and incrementing the length counter.
while num != 0:
length += 1
num /= 10
return length
def find_inside(num1, num2):
# Trivial case, when the numbers are same.
if num1 == num2:
return (True, 1)
len_n2 = find_length(num2)
place = find_length(num1)
# Iterate over the numbers, extracting the last
# n digits everytime, where n is length of num2
# Keep dividing the original number by 10 and
# decrementing place everytime.
while num1 != 0:
if num2 == (num1 % (10 ** len_n2)):
return (True, place - len_n2 + 1)
num1 /= 10
place -= 1
return (False, -1)
Some test cases (Trivial):
>>> find_inside(10, 0)
(True, 2)
>>> find_inside(3456, 98)
(False, -1)
>>> find_inside(4589678, 58)
(True, 2)
>>> find_inside(10, 1)
(True, 1)
Another way using re:
re.search('58','45896').start()+1
One liner:
>>> x=45896
>>> y=58
>>> str(x).index(str(y))+1 if str(y) in str(x) else -1
2
>>>
Alternative (uses str(...) only for length!):
x=45896
y=58
yLength = len(str(y)) #you need the size!
t = 10**yLength
found = "false"
position = 0
while x > 0:
position = position +1
if(x%t == y):
found = "true"
break
x = x/10
print found
print str(position - 1)
NOTE: You can get the size easily without using str(...), but I will leave you to write that code.
If you want work without strings you will like this approach:
import math
original = 45896
find = 58
org_dgt = int(math.log10(original))+1
fnd_dgt = int(math.log10(find))+1
count = 1
found = False
while not found:
curr_dgt = org_dgt-fnd_dgt
if curr_dgt < 0:
break
if int((original%(10**org_dgt))/(10**(curr_dgt))) == find:
found = True
break
org_dgt -= 1
count += 1
if found:
print count
else:
print 'Not Found'

Palindrome with Recursion

I'm trying to make a slightly more advanced palindrome as according to the docstring. However I can't get it to work. Am I going it about the right way? This is what I have so far:
def pal_length(s: str, n: int) -> bool:
'''Return True iff s has a palindrome of length exactly n.
>>> pal_length('abclevel', 5)
True
>>> pal_length('level', 2)
False
'''
if not s:
return True
else:
index = 0
while index < len(s):
if s[index] == s[index+n]:
return pal_length(s[index+1:index+n-1],n-1)
index += 1
return False
I'm trying to not use any import modules etc. Just straight recursion.
Any help is appreciated. Thanks.
I think your indexing is a bit off. Shouldn't it be
index = 0
while index < len(s) - n + 1:
if s[index] == s[index+n-1]:
return pal_length(s[index+1:index+n-1], n-2)
index += 1
return False

Categories