IndexError when checking if input is palindrome using while loop - python

I'd like to know how do I check if an input is a palindrome with a while loop, using Python.
Thanks:
i tried this
i = 0
n = len(msg_list)
while i < n:
palindrome = msg_list[i]
if palindrome == msg_list[-1]:
print("Palindrome? True")
msg_list.pop(-1)
else:
print("Palindrome? False")
i=i+1
but at the end I receive an error message that the list index is out of range

You don't need to iterate till the end, but only till the middle character. And compare every character to the character at the same index when counted in reverse:
s = "abcca"
length = len(s)
i = 0
while i < length / 2 + 1:
if s[i] != s[-i - 1]:
print "Not Palindrome"
break
i += 1
else:
print "Palidrome"
else part of the while loop is executed, when the loop completes its iteration without any break.
Alternatively, if you can use anything else than a while loop, then this task is just of single line in Python:
if s == s[::-1]:
print "Palindrome"
Oh, it became two lines.

With a while loop
import string
palin = 'a man, a plan, a canal, panama'
def testPalindrome(in_val):
in_val = in_val.lower()
left, right = 0, len(in_val) - 1
while left < right:
char_left, char_right = '#', '#'
while char_left not in string.lowercase:
char_left = in_val[left]
left += 1
while char_right not in string.lowercase:
char_right = in_val[right]
right -= 1
if char_left != char_right:
return False
return True
print testPalindrome(palin)
Without
>>> palindrome = 'a man, a plan, a canal, panama'
>>> palindrome = palindrome.replace(',', '').replace(' ', '')
>>> palindrome
'amanaplanacanalpanama'
>>> d[::-1] == d
True

A short solution using reversed:
for c, cr in s, reversed(s):
if c != cr:
print("Palindrome? False")
break
else:
print("Palindrome? True")

Another way using a while loop. Once two characters don't match, the while loop stops, so it's quite efficient but of course not the best way to do it in Python.
def palindrome(word):
chars_fw = list(word)
chars_bw = list(reversed(word))
chars_num = len(word)
is_palindrome = True
while chars_num:
if chars_fw[chars_num-1] != chars_bw[chars_num-1]:
is_palindrome = False
break
chars_num -= 1
return is_palindrome

Figured I would add another alternative for people still viewing this question. It uses a while loop, and is reasonably concise (although, I still prefer the if word = word[::-1] approach.
def is_palindrome(word):
word = list(word.replace(' ', '')) # remove spaces and convert to list
# Check input
if len(word) == 1:
return True
elif len(word) == 0:
return False
# is it a palindrome....
while word[0] == word[-1]:
word.pop(0)
word.pop(-1)
if len(word) <= 1:
return True
return False

word = "quiniuq"
pairs = zip(word,reversed(word))
a,b = next(pairs)
try:
while a == b:
a,b = next(pairs)
return False # we got here before exhausting pairs
except StopIteration:
return True # a == b was true for every pair
The use of a while loop here is contrived, but it will consume the whole list and perform the test.
If a while loop wasn't a requirement, one would do: all(a == b for a,b in zip(word,reversed(word)))

Related

How can I transform my for loop code into the while loop in Python?

I am trying to write a function named has_no_e that takes a string as an argument and returns False if the string contains the letter e and True if the string does not contains the letter e.
I have written the function using the for loop but I am failing to write a similar function that does the same thing using a while loop.
Here is my function with the for loop:
def has_no_e(word):
for letters in word:
if letters == "e":
return False
return True
And the function I have written is:
def has_no_e2(word):
index = 0
while index < len(word):
if word[index] != "e":
index += 1
return False
return True
Can you tell me what is wrong with my function with the while loop? It returns False every time not depending on whether there is "e" or not.
There are two ways to implement the same fix for this. The one that keeps more closely to your existing code is
def has_no_e2(word):
index = 0
while index < len(word):
if word[index] != "e":
index += 1
continue # skip the rest of the loop and go back to the top
return False
return True
The better one is
def has_no_e2(word):
index = 0
while index < len(word):
if word[index] == "e":
return False
index += 1
return True
Note that the following two pieces of code are roughly equivalent:
for elem in iterable:
# code
_index = 0
while _index < len(iterable):
elem = iterable[_index]
# code
_index += 1
It's more complicated than that, because for loops often use iterators instead of indices, but you get the idea.
Try:
index = 0
while index < len(word):
if word[index] != "e":
index += 1
else:
return False
return True
Currently your code returns false at the end of the first loop
def has_no_e1(_string):
return 'e' not in _string
def has_no_e2(_string):
return _string.find('e') == -1
def has_no_e3(_string):
while _string:
if _string[0] == 'e':
return False
_string = _string[1:]
return True
if __name__ == "__main__":
word = 'With the letter `e`'
print(
has_no_e1(word),
has_no_e2(word),
has_no_e3(word),
)
word = 'Without it'
print(
has_no_e1(word),
has_no_e2(word),
has_no_e3(word),
)
You can use the in operator:
def has_no_e(word):
return 'e' not in word
In case you really need a while loop:
def while_has_no_e(word):
characters = list(word)
while characters:
if characters.pop() == 'e':
return False
return True
print(has_no_e('foo'))
print(has_no_e('element'))
print(while_has_no_e('foo'))
print(while_has_no_e('element'))
Out:
True
False
True
False
Some changes and you are good to go, you just need to write whatever condition you wrote in for loop into while, that's it:
def has_no_e2(word):
index = 0
while index < len(word):
if word[index] == "e":
return False
index+=1
return True
Now let me give you a one liner:
>>> string = 'hello'
>>> False if string.count('e') > 0 else True
False
#changing the contents
>>> string = 'hlo'
>>> False if string.count('e') > 0 else True
True
You don't need to create a function, it exists already and it's called find()
has_e = word.find("e")
The function either returns the exact position of the letter or it returns -1 if it doesn't exist.
Reference

Counting issue wth Python dictionaries

So I'm trying to count unique letter in this function. If their are unique letters return True if there isn't return False. In the word 'Programming'
there are two r's and two m's and two g's. Its return True where the actual answer is False? Is my logic wrong here?
def unique_chars_in_string(input_string):
unique_chars = {}
if input_string == "":
return True
for letter in input_string:
if letter in unique_chars:
unique_chars[letter]+=1
else:
unique_chars[letter]=1
for k in unique_chars:
if unique_chars[k] > 1:
return False
else:
return True
print(unique_chars_in_string('Programming'))
According to your algorithm, you're just checking the first character of the string. And not the entire string.
This block of code has erroneus logic:
for k in unique_chars:
if unique_chars[k] > 1:
return False
else:
return True
You don't need that else condition inside the for loop.
Try this instead:
def unique_chars_in_string(input_string):
unique_chars = {}
if input_string == "":
return True
for letter in input_string:
if letter in unique_chars:
unique_chars[letter]+=1
else:
unique_chars[letter]=1
for k in unique_chars:
if unique_chars[k] > 1:
return False
return True
print(unique_chars_in_string('Programming'))
So, if any character has more than 1 count, your code will return false else it will return true.
Also, check Jean-François Fabre's answer to this problem, it is a short and sweet Pythonic implementation.
Happy Coding! :)
if you want to check if all letters occur only once use all
return all(k == 1 for k in unique_chars.values())
that will avoid to return too soon a result when you haven't scanned the rest of the letters.
Also consider counting your letters with collections.Counter which is a specialized dictionary made for counting hashable elements:
unique_chars = collections.Counter(input_string)
so combining in one line:
return all(k == 1 for k in collections.Counter(input_string).values())
That said, we're missing the point. If we need to check if a word has non-duplicate letters this can be done without counting using a set:
return len(input_string) == len(set(input_string))
for k in unique_chars:
if unique_chars[k] > 1:
return False
else:
return True
This is your problem. This checks if any value in unique_chars is greater than one, or if the first value is one or less. You should instead do:
for k in unique_chars:
if unique_chars[k] > 1:
return False
return True # since this is after the for loop, it only runs if
# no character has a count greater than one.
Alternatively this can be made more efficient with a set.
seen = set()
for c in input_string:
if c in seen:
return False
seen.add(c)
return True
You return too early in the loop when you encounter the first unique character. You only know all are unique at the end of the loop:
def unique_chars_in_string(input_string):
unique_chars = {}
for letter in input_string:
if letter in unique_chars:
unique_chars[letter]+=1
else:
unique_chars[letter]=1
for k in unique_chars:
if unique_chars[k] > 1:
return False # not all are unique -> return early
# but only when all have been checked, can you know for sure all are
return True
This whole function can be shortened, using collections.Counter and all, but maintaining the underlying algorithm:
from collections import Counter
def unique_chars_in_string(input_string):
return all(v==1 for v in Counter(input_string).values())
Or even simpler:
def unique_chars_in_string(input_string):
return len(input_string) == len(set(input_string))
s = 'Programming'
a = [True for i in s if s.count(i) > 1]
if any(a):
print(False)
else:
print(True)

Recursion Function Isn't Working

Okay, so I'm trying to make a recursive function that returns True if the function is a palindrome, and False otherwise. However, it doesn't go to the very end, and randomly stops.
Code:
def is_palindrome(word):
if len(word) == 1 or len(word) == 0:
return True
else:
lst = len(word) - 1
if word[0] == word[lst]:
print(len(word), " --> ", word)
print(word[0], " # ", word[lst])
is_palindrome(word[0+1:lst])
else:
return False
For the life of me, I can't figure out why. Here's a sample output:
7 --> racecar
r # r
5 --> aceca
a # a
3 --> cec
c # c
^ It stops right here. Why doesn't it continue and return True when length = 1?
You need to return your call to the recursive function:
def is_palindrome(word):
if len(word) == 1 or len(word) == 0:
return True
else:
lst = len(word) - 1
if word[0] == word[lst]:
print(len(word), " --> ", word)
print(word[0], " # ", word[lst])
return is_palindrome(word[0+1:lst]) # change here
else:
return False
The reason you code appears to stop at the final step of recursion is because you never actually return a value in that case. In some programming languages, such as C or maybe Java, such code would not even compile. Python appears to tolerate it, though it results in your current behavior.
your function is too long.
I prefer to use string[::-1]
#
def is_palindrome(word):
return word == word[::-1]

Where's the bug in this function to check for palindrome?

Given below is the code to check if a list is a palindrome or not. It is giving correct output for 983. Where am I going wrong?
def palindrome(num):
flag=0
r=num[::-1]
for i in range (0, len(num)-1):
if(r[i]==num[i]):
flag=1
else:
flag=0
return flag
You should return as soon as there is a mismatch. Also, you just need to iterate till half the length:
def function(...):
...
for i in range (0, (len(num) + 1) / 2):
if r[i] != num[i]:
return False
return True
BTW, you don't need that loop. You can simply do:
def palindrome(num):
return num == num[::-1]
This would be easier:
def palindrome(num):
if num[::-1] == num:
return True
else:
return False
Your for loop checks all pairs of characters, no matter if it found mismatch or not. So, in case of string '38113' it will return True, because the flag variable will be set to True after the check for equality of last digit in '38113' and its reversed version '31183' (both equal to 3, while the string isn't a palindrome).
So, you need to return False right after you've found mismatch; if you checked all the characters and didn't find it - then return True, like so:
def palindrome(num):
r = num[::-1]
for i in range (0, len(num)-1):
if(r[i] != num[i]):
return False
return True
Also, as someone pointed out it'll be better to use python's slices - check out the documentation.
Just for the record, and for the ones looking for a more algorithmic way to validate if a given string is palindrome, two ways to achieve the same (using while and for loops):
def is_palindrome(word):
letters = list(word)
is_palindrome = True
i = 0
while len(letters) > 0 and is_palindrome:
if letters[0] != letters[-1]:
is_palindrome = False
else:
letters.pop(0)
if len(letters) > 0:
letters.pop(-1)
return is_palindrome
And....the second one:
def is_palindrome(word):
letters = list(word)
is_palindrome = True
for letter in letters:
if letter == letters[-1]:
letters.pop(-1)
else:
is_palindrome = False
break
return is_palindrome
str1=str(input('enter string:'))
save=str1
revstr=str1[::-1]
if save==revstr:
print("string is pailandrom")
else:
print("not pailadrom")
# We are taking input from the user.
# Then in the function we are reversing the input i.e a using
# slice [::-1] and
# storing in b
# It is palindrome if both a and b are same.
a = raw_input("Enter to check palindrome:")
def palin():
#Extended Slices to reverse order.
b = a[::-1]
if a == b:
print "%s is palindrome" %a
else:
print "%s is not palindrome" %a
palin()
this would be much easier:
def palindrome(num):
a=num[::-1]
if num==a:
print (num,"is palindrome")
else:
print (num,"is not palindrome")
x=input("Enter to check palindrome:")
palindrome(x)
Here in my opinion is the most elegant:
def is_palindrome(s):
if s != '':
if s[0] != s[-1]:
return False
return is_palindrome(s[1:-1])
return True
it's also the same code in the is_palindrome() function:
pip install is-palindrome
>>> from is_palindrome import is_palindrome
>>> x = "sitonapanotis"
>>> y = is_palindrome(x)
>>> y
True
Take care to note the hyphen vs underscore when installing vs. importing
a="mom"
b='mom'[::-1] # reverse the string
if a==b: # if original string equals to reversed
print ("palindrome ")
else:
print ("not a palindrome ")
def palindrome(a):
a=raw_input('Enter :')
b=a[::-1]
return a==b

Why will my program that finds double letters not execute? Python

My intent is to have the program list all strings in a text file that have 3 sets of double letters. Here is the function that is supposed to return True if 3 or more double letter sets are found:
def three_double(s):
doubnum = 0
i=0
while i < len(s)-1:
if s[i] == s[i+1]:
doubnum += 1
elif doubnum >= 3:
return True
else:
i += 1
return False
I'm not sure why it doesn't print anything. Here is the rest of the program.
# Function to read and apply the three_double test to each string in
# an input file. It counts the number of results.
def find_three_double(fin):
count = 0
for w in fin:
w = w.strip()
if three_double(w):
print w
count = count + 1
if count == 0:
print '<None found>'
else:
print count, 'found'
# Bring in a package to access files over the web
import urllib
# Access the file containing the valid letters
words_url = "http://thinkpython.com/code/words.txt"
words_file = urllib.urlopen(words_url)
# Apply the actual test
find_three_double(words_file)
I didn't read your code carefully at first, turns out it isn't related to read() or readlines() as you are iterating in find_three_doubles() function.
In your three_double() function:
while i < len(s)-1:
if s[i] == s[i+1]:
doubnum += 1
elif doubnum >= 3:
return True
else:
i += 1
return False
There are two problems:
You need to increment i by 1 otherwise the while loop will never stop if there is a "double".
You also need to change elif to if here because otherwise some qualified words will not be selected.
Fixed Code:
def three_double(s):
doubnum = 0
i=0
while i < len(s)-1:
if s[i] == s[i+1]:
doubnum += 1
if doubnum >= 3:
return True
i += 1
return False
# Function to read and apply the three_double test to each string in
# an input file. It counts the number of results.
def find_three_double(fin):
count = 0
for w in fin:
w = w.strip()
if three_double(w):
print w
count = count + 1
if count == 0:
print '<None found>'
else:
print count, 'found'
# Bring in a package to access files over the web
import urllib
# Access the file containing the valid letters
words_url = "http://thinkpython.com/code/words.txt"
words_file = urllib.urlopen(words_url)
# Apply the actual test
find_three_double(words_file)
Results:
aggressiveness
aggressivenesses
allottee
allottees
appellee
appellees
barrenness
barrennesses
bookkeeper
bookkeepers
bookkeeping
bookkeepings
cheerlessness
cheerlessnesses
committee
committees
greenness
greennesses
heedlessness
heedlessnesses
heelless
hyperaggressiveness
hyperaggressivenesses
keelless
keenness
keennesses
masslessness
masslessnesses
possessiveness
possessivenesses
rottenness
rottennesses
sleeplessness
stubbornness
stubbornnesses
successfully
suddenness
suddennesses
sullenness
sullennesses
toolless
wheelless
whippoorwill
whippoorwills
woodenness
woodennesses
46 found
itertools.groupby can greatly simplify your program (= less bugs)
from itertools import groupby
import urllib
def find_three_double(words_file):
for word in words_file:
word = word.strip()
if sum(sum(1 for i in g) == 2 for k,g in groupby(word)) == 3:
print word
# Access the file containing the valid letters
words_url = "http://thinkpython.com/code/words.txt"
words_file = urllib.urlopen(words_url)
# Apply the actual test
find_three_double(words_file)
Explanation:
inside the generator expression we see groupby(word). This scans the word and gathers the double letters together.
sum(1 for i in g) is applied to each group. It is equivalent to finding the length of the group. If the length is 2, then this is a double letter so sum(1 for i in g) == 2 evaluates to True
The outer sum() adds up all the True and False values, True is added as 1 and False is added as 0. If there are exactly 3 True values, the word is printed
while i < len(s)-1:
if s[i] == s[i+1]:
doubnum += 1
elif doubnum >= 3:
return True
else:
i += 1
If your first check (s[i] == s[i+1]) is True, then you'll never increment i so the loop continues forever.

Categories