Python count number of chars in string - python

Input :
abbbbccdddaaabbbbeeff
Output :
ab4c2d3a3b4e2f2
I have tried like below,
string = 'abbbbccccd'
strList = list(string)
sum = 0
for i , s in enumerate(string):
# print (strList[i],strList[i+1])
if strList[i] == strList[i+1]:
sum = sum + 1
print(strList[i],'****',sum )
else:
sum = sum + 1
print(strList[i],'****',sum )
sum = 0
But unable to print the last element from list.
Is there any better way to do it without using any inbuilt functions?
Edit : I wanted to understand the logic of printing abb4c2.. that's why I mentioned without any inbuilt functions. Its ok to use inbuilt functions if the logic can be understandable.

In those problems, always keep current state (current character & current count). No need for indexes, simpler logic.
And in the end, don't forget to "flush" the current loop data, else you miss the last iteration.
My proposal:
s = "abbbbccdddaaabbbbeeff"
result = []
current = None
current_count = 0
for c in s:
if current == c:
current_count += 1
else:
if current_count > 1:
result.append(str(current_count))
current_count = 1
current = c
result.append(c)
# don't forget last iteration count
if current_count > 1:
result.append(str(current_count))
print("".join(result))
prints:
ab4c2d3a3b4e2f2
Okay, I know "".join(result) invokes a built-in function, but that's the most efficient way. You don't want to append character by character to create the string from the list.
Once you proved that you're mastering such algorithms, use built-ins like itertools.groupby to do such jobs. It's faster and bug-free (or even better: this other answer)

You could use more_itertools:
from more_itertools import run_length
s = "abbbbccdddaaabbbbeeff"
result = ""
for char, num in run_length.encode(s):
result += f"{char}{num if num != 1 else ''}"
print(result) #returns ab4c2d3a3b4e2f2
EDIT: missed the part about inbuilt functions. This uses an external library. Leaving it here because I find the initial problem very interesting.

You can use dictionaries
a='abbbbccdddaaabbbbeeff'
d=dict()
for i in a:
if i not in d:d[i]=1
else:d[i]+=1
for key,value in d.items():
print(key,value,sep='',end='')
output a4b8c2d3e2f2

Related

Write a function that takes a string as an argument and displays the letters, one per line using loops in python

I'm learning to program and I'm using "how to think like an computer scientist" the above question is an exercise
This is the program without a function
fruit = "banana"
index = 0
while index < len(fruit):
letter = fruit[index]
print(letter)
index = index + 1
I want to put that into a function like
def tranversal(fruit):
index = 0
while index < len(fruit):
letter = fruit[index]
return letter
index += 1
print(tranversal("apple"))
However this is only printing the first letter of "apple" and if I use print statement instead of return I will get None.
I'm very confused and need help !!
Seems like you didn't understand the purpose of the return statement inside a function. You might want to read this answer first to make things clear.
Once you understand the difference between print() and return, you should define what your function needs to do. Does it need to return the answer or is printing it on the screen enough?
Assuming the latter, given that strings are iterable, a more pythonic way to do it would be:
def transversal(fruit):
for letter in fruit:
print(letter)
Note that since the function is not explicitly returning a value if you try something like:
foo = transversal("banana")
the variable foo will hold the value None.
If you want your function to return the answer and not print it, you could append each letter to an empty result string, with separators for each new line and after you are done with that, simply return result. It could be a good exercise, so you should give it a try :).
A simple solution:
print(*'banana', sep='\n')
Output:
b
a
n
a
n
a
With help of the star operator * you can split a list or a string into parts and and pass them as multiple arguments to function. So the expression print(*'abc') is equivalent to print('a', 'b', 'c').
If you use print in the function, then you dont need to use print when calling the function.
def tranversal(fruit):
index = 0
while index < len(fruit):
letter = fruit[index]
print(letter)
index += 1
tranversal("apple")
If you use a return statement inside of the while loop, then you will immediately leave the function (and return the first letter), and the while loop will not be executed for higher indices.
You can use this code snippet
def printAllChar(s):
for i in s:
print(i,end='\n')
//calling here...
printAllChar("ProgRank")
//output here...
P
r
o
g
R
a
n
k
For the purpose of understanding i wanted to do that exercise with a function, while loop and get a return value.
I've gotten help and i appreciate everyone, here is my code:
def `tranversal`(fruit):
result = ""
length = int(len(fruit))
index = 0
while index < length:
result += fruit[index]
index += 1
if index == length:
return "\n".join(result)
print(tranversal("string"))
You need to execute the statement using the function outside the function. Just shift return tranversal("apple") outside the function transversal like this:
def transversal(fruit):
index = 0
letters = ''
while index < len(fruit):
letters += fruit[index] + '\n'
index += 1
return letters
print(transversal("apple"))
Thank you #MykolaZotko for pointing out an error in the code that caused it to only print the first letter.

How to make recursive sorted list function work?

I'm trying to make a list sorting algorithm, without using Python's sorted. I have this so far:
def order(lst):
if lst == [] or len(lst) == 1:
return lst
elif lst[0] < order(lst[1:])[0] or lst[0] == order(lst[1:])[0]:
return [lst[0]] + order(lst[1:])
return order(lst[1:]) + [lst[0]]
However, it has trouble dealing with lists with repeated entries. I'm assuming that this is because the program that you can keep expanding the list based on whether something is greater or less, and if it runs in to something that has an equal value same, it breaks the process. However, I'm not sure how to fix it at all, so is there a better way to do this or do I have to use a different way (using min is my best bet at this point)? Any hints would be appreciated.
def order(lst):
count = 0 #this is the count of how many times we've seen a repeated digit
def helper(lst):
nonlocal count
if len(lst) <= 1:
return lst
lst_without_min = []
for x in lst:
if count < 1 and x == min(lst): #okay, we've already seen the minimum digit once, if it's repeated again keep it in there
count += 1
else:
lst_without_min.append(x)
return [min(lst)] + order(lst_without_min)
return helper(lst)
Here's a working solution that is really long and probably inefficient, but it works.

Writing a recursive function

I am trying to write a recursive function in Python to count the number of pairs of repeated characters in a string. Example: "hellmoo" = 2
To get me started, I first tried to write an iterative version of the program. Here's my attempt:
counter = 0
string = input("Enter string:" )
for i in range(len(string)-1):
if string[i] == string[i+1]:
counter = counter+1
print (counter)
Now, I do not understand how I can write a recursive function from the above iterative program. I have tried to think of my base case as:
if string == "":
return (0)
I'm not sure if I am correct there. Could someone please help me do this? Thanks!
your logic assuming two characters in the string, so I think you need two base cases, for empty string and for one character string. (or one base case for string shorter then 2)
maybe something like this:
def count_doubles(string):
if len(string) < 2:
return 0
else:
if string[0] == string[1]:
return count_doubles(string[1:]) + 1
else:
return count_doubles(string[1:])
>>> count_doubles("hellmoo")
2
p.s.
I don't know why you want to do this with recursion, I dont think it's good idea for this task.
a more pythonic way to do it can be:
>>> string = "hellmoo"
>>> len(filter(lambda x:x[0]==x[1],zip(string[1:],string[:-1])))
2
This is kind of a silly assignment for Python, but in other languages that use a different fundamental data type, like a cons-list instead of an array, it makes sense.
You know how to write counter for a 2-element string.
For a 3-element string, you do the 2-element count on s[:2], then you do the 2-element count on s[1:].
For a 4-element string, you do the 2-element count on s[:2], then you do the 3-element count on s[1:].
For an N-element string, you do the 2-element count on s[:2], then you do the N-1-element count on s[1:].
Of course that leaves out the 0-element and 1-element cases, but you can just add 2 more base cases for that.
Defining a basic recursion with a wrapper:
def recursion(str, idx):
if (idx + 1 == len(str)):
return 0
if (str[idx+1] == str[idx]):
return 1 + recursion(str, idx + 1)
return recursion(str, idx + 1)
def count(str):
if len(str) == 0:
return 0
return recursion(str, 0)
>>> count("hellmoo")
2
>>> count("hellbb")
2
>>> count("hellbbvv")
3
>>> count("hh")
1
>>> count("")
0

Python - packing/unpacking by letters

I'm just starting to learn python and I have this exercise that's puzzling me:
Create a function that can pack or unpack a string of letters.
So aaabb would be packed a3b2 and vice versa.
For the packing part of the function, I wrote the following
def packer(s):
if s.isalpha(): # Defines if unpacked
stack = []
for i in s:
if s.count(i) > 1:
if (i + str(s.count(i))) not in stack:
stack.append(i + str(s.count(i)))
else:
stack.append(i)
print "".join(stack)
else:
print "Something's not quite right.."
return False
packer("aaaaaaaaaaaabbbccccd")
This seems to work all proper. But the assignment says that
if the input has (for example) the letter a after b or c, then
it should later be unpacked into it's original form.
So "aaabbkka" should become a3b2k2a, not a4b2k2.
I hence figured, that I cannot use the "count()" command, since
that counts all occurrences of the item in the whole string, correct?
What would be my options here then?
On to the unpacking -
I've thought of the basics what my code needs to do -
between the " if s.isalpha():" and else, I should add an elif that
checks whether or not the string has digits in it. (I figured this would be
enough to determine whether it's the packed version or unpacked).
Create a for loop and inside of it an if sentence, which then checks for every element:
2.1. If it has a number behind it > Return (or add to an empty stack) the number times the digit
2.2. If it has no number following it > Return just the element.
Big question number 2 - how do I check whether it's a number or just another
alphabetical element following an element in the list? I guess this must be done with
slicing, but those only take integers. Could this be achieved with the index command?
Also - if this is of any relevance - so far I've basically covered lists, strings, if and for
and I've been told this exercise is doable with just those (...so if you wouldn't mind keeping this really basic)
All help appreciated for the newbie enthusiast!
SOLVED:
def packer(s):
if s.isalpha(): # Defines if unpacked
groups= []
last_char = None
for c in s:
if c == last_char:
groups[-1].append(c)
else:
groups.append([c])
last_char = c
return ''.join('%s%s' % (g[0], len(g)>1 and len(g) or '') for g in groups)
else: # Seems to be packed
stack = ""
for i in range(len(s)):
if s[i].isalpha():
if i+1 < len(s) and s[i+1].isdigit():
digit = s[i+1]
char = s[i]
i += 2
while i < len(s) and s[i].isdigit():
digit +=s[i]
i+=1
stack += char * int(digit)
else:
stack+= s[i]
else:
""
return "".join(stack)
print (packer("aaaaaaaaaaaabbbccccd"))
print (packer("a4b19am4nmba22"))
So this is my final code. Almost managed to pull it all off with just for loops and if statements.
In the end though I had to bring in the while loop to solve reading the multiple-digit numbers issue. I think I still managed to keep it simple enough. Thanks a ton millimoose and everyone else for chipping in!
A straightforward solution:
If a char is different, make a new group. Otherwise append it to the last group. Finally count all groups and join them.
def packer(s):
groups = []
last_char = None
for c in s:
if c == last_char:
groups[-1].append(c)
else:
groups.append([c])
last_char = c
return ''.join('%s%s'%(g[0], len(g)) for g in groups)
Another approach is using re.
Regex r'(.)\1+' can match consecutive characters longer than 1. And with re.sub you can easily encode it:
regex = re.compile(r'(.)\1+')
def replacer(match):
return match.group(1) + str(len(match.group(0)))
regex.sub(replacer, 'aaabbkka')
#=> 'a3b2k2a'
I think You can use `itertools.grouby' function
for example
import itertools
data = 'aaassaaasssddee'
groupped_data = ((c, len(list(g))) for c, g in itertools.groupby(data))
result = ''.join(c + (str(n) if n > 1 else '') for c, n in groupped_data)
of course one can make this code more readable using generator instead of generator statement
This is an implementation of the algorithm I outlined in the comments:
from itertools import takewhile, count, islice, izip
def consume(items):
from collections import deque
deque(items, maxlen=0)
def ilen(items):
result = count()
consume(izip(items, result))
return next(result)
def pack_or_unpack(data):
start = 0
result = []
while start < len(data):
if data[start].isdigit():
# `data` is packed, bail
return unpack(data)
run = run_len(data, start)
# append the character that might repeat
result.append(data[start])
if run > 1:
# append the length of the run of characters
result.append(str(run))
start += run
return ''.join(result)
def run_len(data, start):
"""Return the end index of the run of identical characters starting at
`start`"""
return start + ilen(takewhile(lambda c: c == data[start],
islice(data, start, None)))
def unpack(data):
result = []
for i in range(len(data)):
if data[i].isdigit():
# skip digits, we'll look for them below
continue
# packed character
c = data[i]
# number of repetitions
n = 1
if (i+1) < len(data) and data[i+1].isdigit():
# if the next character is a digit, grab all the digits in the
# substring starting at i+1
n = int(''.join(takewhile(str.isdigit, data[i+1:])))
# append the repeated character
result.append(c*n) # multiplying a string with a number repeats it
return ''.join(result)
print pack_or_unpack('aaabbc')
print pack_or_unpack('a3b2c')
print pack_or_unpack('a10')
print pack_or_unpack('b5c5')
print pack_or_unpack('abc')
A regex-flavoured version of unpack() would be:
import re
UNPACK_RE = re.compile(r'(?P<char> [a-zA-Z]) (?P<count> \d+)?', re.VERBOSE)
def unpack_re(data):
matches = UNPACK_RE.finditer(data)
pairs = ((m.group('char'), m.group('count')) for m in matches)
return ''.join(char * (int(count) if count else 1)
for char, count in pairs)
This code demonstrates the most straightforward (or "basic") approach of implementing that algorithm. It's not particularly elegant or idiomatic or necessarily efficient. (It would be if written in C, but Python has the caveats such as: indexing a string copies the character into a new string, and algorithms that seem to copy data excessively might be faster than trying to avoid this if the copying is done in C and the workaround was implemented with a Python loop.)

python look-and-say sequence improved

I would like to introduce look-and-say sequence at first. It goes like a = {1, 11, 21, 1211, 111221 ...
The system is it checks the previous digit and counts the numbers.
1 = one 1 (so = 11)
11 = two 1 (so = 21)
21 = one 2 one 1 (so = 1211)
As a rule of the sequence, no number can go beyond 3, so creating a translation table can fit in. But it is not semantic, I don't like it.
What I want is, a script which evaluates the given value and return a look-and-say-alike string.
However, to go beyond out limits, I want it to even evaluate chars, so it can return 1A2b41.
I have been trying to make it work for hours, the logic went bad and I am having a brainfreeze at the moment.
Here is the script that actually doesn't work(returns false results), but it can give you the idea, at least.
def seq(a):
k,last,result,a = 1,'','',str(a)
for i in range(len(a)):
if last==a[i]:k+=1
else:
result = result+str(k)+a[i]
k=1
last = a[i]
return result
You can use groupby, it's just what you want:
from itertools import groupby
def lookandsay(n):
return ''.join( str(len(list(g))) + k for k, g in groupby(n))
>>> lookandsay('1')
'11'
>>> lookandsay('1A2b41')
'111A121b1411'
>>> lookandsay(lookandsay('1A2b41'))
'311A1112111b111421'
groupby returns consecutive keys and groups from an iterable object. The key is a function computed for each element, or an identity function if not specified (as above). The group is an iterator - a new group is generated when the value of the key function changes. So, for instance, according to the documentation:
# [k for k, g in groupby('AAAABBBCCDAABBB')] --> A B C D A B
# [list(g) for k, g in groupby('AAAABBBCCD')] --> AAAA BBB CC D
I can see two issues with your code:
The result is expanded by k and a[i] although the counter k does not count chars a[i] but chars last. Replace a[i] by last here (you may not want to add anything in the first round).
After the loop you have to add the last value of the counter together with the last character again (this was not yet done), i.e. add another result = result+str(k)+last after the loop.
In total it looks like
def seq(a):
a = str(a)
k,last,result = 1,a[0],''
for i in range(1,len(a)):
if last==a[i]:k+=1
else:
result = result+str(k)+last
k=1
last = a[i]
result = result+str(k)+last
return result
I think part of why you got stumped is your use of meaningless variable names. You described the problem quite well and called it by name, but didn't even use that name for your function.
If you think of the string you start with as "look", and the one you end up with as "say", that is a start. result is probably fine but a and k have confused you. last is, I think, misleading, because it can mean either previous or final.
Also, Python's for is really foreach for a reason -- you're taking each character in the "look" one at a time, so do it explicitly in the loop.
def looksay(look):
look = str(look)
prev, count, say = look[0], 1, ''
for char in look[1:]:
if char == prev:
count += 1
continue
say += str(count) + prev
prev = char
count = 1
return say + str(count) + prev
The spacing is less important, but Python does have a standard coding style, and it does help readability to use it. The less mental time you have to spend parsing your code, the more focus you have for the problem.

Categories

Resources