Counting issue wth Python dictionaries - python

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)

Related

Can someone tell me why after returning a boolean of False my code goes backwards and chooses True also

Can someone tell me why after returning a boolean of False my code goes backwards and chooses True also even though the False was the right output I see this happening in the debugger, I only found this happening with this specific example. So the function of my code is to take a pattern from user and see if it matches the word given by the user, the format of a pattern is "?ead" which would match with the word lead, the question mark is just a placeholder for any character so it can be anything but every other character has to be the same.
n = 0
def match(pattern, word):
global n
if n != len(word): # setting a base case for the recurrsion so it stops at the end of the word
if pattern == "" and word == "":
pass
elif pattern == '' or word == '':
pass
elif len(word) != len(pattern):
return False
if word[n:n+1] == pattern[n:n+1] or pattern[n:n+1] == '?': # goes through each character and compares them for each
n = n +1
match(pattern, word)
print("same")
return True # This is where the code goes back to and returns true after already returning false down below in the else statement.
else:
print("not same")
return False
match("?ut","cat")
The pattern my code fails for "?ut" and the word is "cat", the code sees that the first character is fine since "?" is just a placeholder and it also sees that the second term does not match so it should evaluate to false which it does but then immediately exits and evaluates to true and all this needs to be done with recursion and no loops.
You are trying to do recursion here. It can be done like this:
def match(pattern, word):
#Little debug for you to see what is happening
print (pattern, word)
if len(pattern) != len(word):
return False
if not word and not pattern:
# last round with empty strings, all same this far
return True
if pattern[0] != word[0] and pattern[0] != "?":
return False
# reduce both strings and go to next round
return match(pattern[1:], word[1:])
So you reduce both pattern and word and if you find that they don't match return false. If you get to the end, return True.
You do not return the result of match but return True. You should simplify your cases.
if word[n:n+1] == pattern[n:n+1] or pattern[n:n+1] == '?':
n = n +1
match(pattern, word) # this can return True OR False
print("same")
return True # You always return True
# return match(pattern, word) instead
There is no need for a global "counter" - if you recurse, shorten your pattern/word until nothing is left:
def match(pattern, word):
if len(pattern) != len(word):
return False
# done all I can do
if pattern == "" and word == "":
return True
# no comparison if pattern is ?
if pattern[0] == "?":
# recurse with 1 shorter pattern/word
return match(pattern[1:], word[1:])
# same letters
elif pattern[0] == word[0]:
# recurse with 1 shorter pattern/word
return match(pattern[1:], word[1:])
# different
return False
for pat,wrd in [("?ut","cat"), ("ok","ok"), ("?k","ok"),
("o?","ok"), ("",""), ("tooShort","veryLongWord")]:
print(pat,wrd, "=>", "same" if match(pat,wrd) else "different")
Output:
?ut cat => different
ok ok => same
?k ok => same
o? ok => same
=> same
tooShort veryLongWord => different
Or use the "short" version that does not recurse:
def match(pattern, word):
return len(pattern)==len(word) and all(
(p==w or p == "?" for p,w in zip(pattern, word)))
You should return the result you get back from recursion. Currently you ignore it.
There are some other issues:
Don't use global variables. If you make another call to match, then n will still be the value it had after the first call, and so you'll get an unreliable result from the second call. Instead make it an argument to your function. You can give it a default value.
The condition if pattern == "" and word == "": can never be true, since you already had assured that n != len(word), and as n starts with 0, that if condition would have been false if word was the empty string.
When you pass in the first if blocks, the other code below is still executed, and so you'd make an invalid index reference in the string.
Adding another condition where you delimit strings with single quotes instead of double quotes is not changing anything. You still compare the same strings.
It is overkill to compare slices of one character. Just retrieve the single character you want to compare. So word[n] instead of word[n:n+1]
Don't print the result inside the function. Print at the caller's side.
Correction:
def match(pattern, word, n=0):
if len(word) != len(pattern):
return False
if n >= len(word):
return True
if word[n] != pattern[n] and pattern[n] != '?':
return False
return match(pattern, word, n + 1)
print(match("?ut","cat"))
Not your question, but this kind of recursion is a case of tail recursion, and so it could be written with simple iteration:
def match(pattern, word):
if len(word) != len(pattern):
return False
for i, pat in enumerate(pattern):
if word[i] != pat and pat != '?':
return False
return True
When you are confortable with comprehension syntax you can write that also like this:
def match(pattern, word):
return len(word) == len(pattern) and all(
pat in ('?', ch) for ch, pat in zip(word, pattern)
)

Python character match in a string

Write a function repfree(s) that takes as input a string s and checks whether any character appears more than once. The function should return True if there are no repetitions and False otherwise.
I have tried this but I don't feel this is an efficient way of solving it. Can you suggest an efficient code for this, thanks?
def repfree(s):
newlist = []
for i in range(0, len(s)):
newlist.append(s[i])
newlist2 = set(newlist)
if len(newlist) == len(newlist2):
print("True")
else:
print("False")
One easy way to meet this requirement is to use regular expressions. You may not be allowed to use them, but if you can, then consider this:
def repfree(s):
if re.search(r'^.*(.).*\1.*$', s):
print("True")
else:
print("False")
Believe it or not, this problem can be solved in O(1) time, because every sufficiently large string contains at least one duplicate character. There are only a finite number of different Unicode characters, after all, so a string cannot be arbitrarily long while also using each Unicode character at most once.
For example, if you happen to know that your strings are formed of only lowercase letters, you can do this:
def has_repeated_char(s):
return len(s) > 26 or len(s) != len(set(s))
Otherwise you can replace the 26 with whatever number of characters your string could possibly contain; e.g. 62 for upper- and lowercase letters and digits.
As of February 2020, the whole of Unicode has 137,994 distinct characters (Wikipedia), so if your string length is 150,000 then you can return True without searching.
Try
chars = 'abcdefghijklmnopqrstuvwxyz'
def repfree(s):
for char in chars:
count = s.count(char)
if count > 1:
return False
return True
But, this is a long way as it scans the list 26 times. A much better and Pythonic way would be
import collections
def repfree(s):
results = collections.Counter(s)
for i in results:
if results[i] > 1:
return False
return True
Here, results is a dictionary that contains all the characters of s as key, and their respective frequency as value. Further, it is checked if any value is greater than 1, repetition has occurred.
You could split the string characters to a set and compare the length
def repfree(s):
se = set(string)
print(len(se) == len(s))
You can make your approach more efficient by removing the for loop.
len(s) == len(set(s))
Otherwise, try using any
any(s.count(c) > 1 for c in s)
You could do this way:
Method 1:
def repfree(s):
if len(set(s)) == len(s):
return True
return False
Method 2:
def repfree(s):
return len(set(s)) == len(s)
Why set?
set will return the list of all unique characters in the string in sorted order
Example :
set('shubham')
Output:
{'a', 'b', 'h', 'm', 's', 'u'}
So,if a character in a string appears more than once,it will not be equal to the length of string itself.
def repfree(str):
l=list(str)
for i in range(len(l)):
check=str.count(l[i])
if check>1:
flag=1
else:
flag=0
if(flag==1):
return False
else:
return True
def matched(s):
stack = []
for char in s:
if char == '(':
stack.append(char)
elif char == ')':
if not stack:
return False
stack.pop()
return not stack

Testing whether a string is made up of all lower-case letters, without repeated letters

I am trying to figure out why the following code doesn't work.
The code seems correct to me but it always returns True.
import string
lower_alpha = string.ascii_lowercase
def no_repeat_loweralpha_1 (string):
for i in range(len(string)):
if not string[i] in lower_alpha: return False
if string[i] in string[:i]: return False
else: return True
string = 'ab/!34cda56aaa1..,efgahijk'
print no_repeat_loweralpha_1(string)
# OUTPUT: True
Here is another variant of the code which has the same problem:
def no_repeat_loweralpha_2(string):
for i in range(len(string)):
if string[i] < 'a' or string[i] > 'z' : return False
if string[i] in string[:i]: return False
else: return True
string = 'ab/!34cda56aaa1..,efgahijk'
print no_repeat_loweralpha_2 (string)
# OUTPUT: True
Edit:
Is there a shorter way to write a function that achieves this? How can I write the function using lambda?
A more pythonic version would have been something like this:
return s.isalpha() and s.islower() and len(s) == len(set(s))
Here's another approach. Collect all unique lowercase letters, and check the length against your original:
def no_repeat_lower_alpha(foo):
s = set()
for i in foo:
if i.islower():
s.add(i)
return len(s) == len(foo)
try the following code
def lower_and_duplicate(str_test):
dict_found_chars = {}
for char in str_test:
if not char.islower():
return False
if dict_found_chars.has_key(char):
return False
else:
dict_found_chars[char] = 1
return True
print lower_and_duplicate("aAsdfsdfsdf")
print lower_and_duplicate("aasdfsdfsdf")
print lower_and_duplicate("abcdefgh")
The dict checks for duplicates and we test for lowercase at each step
Shorter version:
all(x in lower_alpha and s.count(x) < 2 for x in s)
If any char is not lowercase or the count is > 1 it will return False
You could remove the string import with:
all( x.isalpha() and x.islower() and s.count(x) < 2 for x in s)

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

IndexError when checking if input is palindrome using while loop

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)))

Categories