To answer your question
In both code snippets, you are creating new strings. The fact that you give a different name to the string in the first version doesn't make it more or less efficient than the second version.
An important remark about naming a variable dict
The name dict is the python name used for the builtin class for dictionaries. If you use that name for one of your own variables, even if it's a dictionary, you are running into a lot of trouble. You should avoid at all costs using builtin names for your own variables. Never call your variables dict, list, str, sum, or any name in that list: Python Built-in Functions. Instead, you can call your dictionary d, or dictionary, or better yet, something that explains what this dictinary is used for; for instance char_count or something similar.
An important note about string concatenation
Consider the following code snippet:
s = ''
for n in range(1000000):
s += str(n)
It builds a string by concatenating numbers written in decimal: at the end, s is '01234567891011...999997999998999999'.
But is it efficient? No, it's not. The time complexity for concatenating two strings s1 and s2 using + or += is proportional to the total number of characters in s1 and s2. Here we are concatenating '' with '0', then '0' with '1', then '01' with '2', then '012' with '3', then '0123' with '4', etc. DO you see what is happening? This is the story of Schlemiel the painter. The complexity is quadratic instead of linear.
An more efficient way to concatenate more than two strings in python is to concatenate them all at once using ''.join(...). So instead we should write:
s = ''.join(str(n) for n in range(1000000)).
Using python module collections
Let's look at this code snippet:
d = {}
for char in string:
if char not in d:
d[char] = 1
else:
d[char] += 1
Guess what? You're not the first one to have come across the need for this logic. If the value is not already in the dictionary, then add it; otherwise, update it. Four lines of code for such a mondaine operation! Surely we could automatize it a little more? Actually yes, we can. Instead of using a dict, we can use a defaultdict. The defaultdict will behave exactly like a dict would, but it will handle default values for us when needed:
from collections import defaultdict
d = defaultdict(int)
for char in string:
d[char] += 1
If d[char] doesn't exist when it's needed by +=, then the defaultdict will create it and give it the value returned by int(), which is 0. Everything works out exactly like before; except we don't have to write the if/else logic ourselves. Cool!
...But wait. Why did we need a dictionary in the first place? To count the number of occurrences of the characters in the string. This sounds like a common problem. In fact, it's so common that there is another subclass of dict even more suited than defaultdict for this! It's called Counter and it's also in module collections. You can replace the code above by:
from collections import Counter
d = Counter(string)
and that's it. Yup. No need for a for-loop to fill the dictionary manually. It's all taken care of already.
Useful reading
Documentation
StackOverflow question
Final code
from collections import Counter
def string_compression(s):
char_counts = Counter(s)
return ''.join('{}{}'.format(c,n) for c,n in char_counts.most_common())
print(string_compression('aaaabcbcbb'))
# a4b4c2
If you want to keep the characters in their order of first appearance rather than sorted by decreasing count, you can replace .most_common() with .items() in the code.