First of all - it's usually not good idea to print inside the function.
I would start with defining in spoken language what I want accomplish: 'give me groups created on every change of character'. If I have these groups I would: 'give me length of every group of characters'.
I hope that following little walkthrough with explanations helps in learning process.
How to achieve objective(s) with itertools.groupby? First of all - one must understand how groupby works. So let's have a look:
from itertools import groupby
str_ = 'abbccc' # we need 'a1b2c3' out of it
print(list(groupby(str_)))
This will give us:
Output:
[('a', <itertools._grouper object at 0x7fe334639f10>), ('b', <itertools._grouper object at 0x7fe334639ed0>), ('c', <itertools._grouper object at 0x7fe334639f50>)]
As we can see there are character and grouper object pairs. We have character - check, but instead of group of characters we have grouper object. We should consume/unpack this object to see its content, so we try this:
print([(letter, *sequence) for letter, sequence in groupby(str_)])
This will give us:
Output:
[('a', 'a'), ('b', 'b', 'b'), ('c', 'c', 'c', 'c')]
We can observe, that after unpacking we have letters instead of grouper object. This is not exactly what we want but very close. We need number of letters, not just unpacking. As grouper object is iterator it knows nothing about its length. It knows only __next__ and StopIteration. So we should consume this object to learn it's length:
print([(letter, sum(1 for i in sequence)) for letter, sequence in groupby(str_)])
This will give us something which is very close what we want - there is character and number of occurrences:
Output:
[('a', 1), ('b', 2), ('c', 3)]
But we are not there yet. We actually need string. So we adjust this code a little bit so that we will have strings instead of tuples of str and int:
print([f'{letter}{sum(1 for i in sequence)}' for letter, sequence in groupby(str_)])
Which gives us list of strings which start with letter and end with count of letter:
Output:
['a1', 'b2', 'c3']
Now we have just construct required string from list items. Canonical way is to use join(). So compress function could look like something along lines below (and it will pass assertions with flying colors).
In function there is generator expression instead of constructing list like in previous walktrough. It was just simpler to explain what is going on with lists and actually there is no point to waste memory to construct list, we need resulting string:
def compress(string):
pairs = (f'{letter}{sum(1 for i in length)}' for letter, length in groupby(string))
return ''.join(pairs)
assert compress('cooooooooooooooooolkangaroo') == 'c1o17l1k1a1n1g1a1r1o2'
assert compress('aaa') == 'a3'
assert compress('') == ''