I am currently taking back Python after over 6 years I do not use it, and to do that I am solving some small challenges on Hacker Rank. I wrote a code that works, but which is not performant and I would like to have some ideas to optimize it.
Problem statement
Given a certain string, count how many anagrams are possible for that string.
The anagrams are those substrings for which the letters used are the same.
For example, if we take the string mama
, we have 5 possible anagrams:
- The first
m
with the thirdm
- The second
a
with the fourtha
- The first
ma
with the secondma
- The first
ma
with the middleam
- The middle
am
with the lastma
My function should count the number of anagrams for a given string.
Current (working) code
I've come up with the below code which works always as expected (note: I'm adding the __main__
as well so that you can copy-paste and run the code in any IDLE):
(disclaimer: if you want to understand the logic without reading the code, I've added a "logic" section just after the code snippet).
#!/bin/python3
import math
import os
import random
import re
import sys
# Complete the sherlockAndAnagrams function below.
def sherlockAndAnagrams(s):
nb = 0
stringSize = len(s)
for size in range(1, len(s)):
for i in range(0, len(s) - 1):
ss = s[i:i+size]
for j in range(i+1, len(s)):
if j+size <= stringSize:
ss2 = s[j:j+size]
if haveSameLetters(ss, ss2):
nb += 1
return nb
def haveSameLetters(s1,s2):
s1Map = getMapOfLetters(s1)
s2Map = getMapOfLetters(s2)
return areMapEquals(s1Map, s2Map)
def areMapEquals(m1, m2):
if len(m1) != len(m2):
return False
for k,v in m1.items():
if not m2.__contains__(k):
return False
else:
if m2.get(k) != v:
return False
return True
def getMapOfLetters(s):
sMap = {}
for letter in s:
if sMap.__contains__(letter):
sMap[letter] = sMap.get(letter)+1
else:
sMap[letter] = 1
return sMap
if __name__ == '__main__':
s1 = "kkkk"
print("Expect 10, received ", sherlockAndAnagrams(s1))
s2 = "ifailuhkqq"
print("Expect 3, received ", sherlockAndAnagrams(s2))
s3 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
print("Expect 166650, received ", sherlockAndAnagrams(s3))
Logic in words
With this code, I analyze blocks of substrings. First, I analyze each substring of size 1
, then of size 2
, then of size 3
etc. until n-1
(where n
would be the size of the whole string in input).
Let's take the word mama
again. What I do is:
The count starts at 0
.
- I take the first substring of size
1
, which ism
, and I compare it with the seconda
(False
), the thirdm
(True
) and the fourtha
(False
). The count is now1
. - Then I take the second substring of size
1
which isa
, and I compare it with the thirdm
(False
) and the fourtha
(True
). The count is now2
. - Then I take the third substring of size
1
, which ism
, and I compare it with the fourtha
(False
). The count stays at2
. - Then I move to the first substring of size
2
, which isma
, and I compare it with the next substringam
(True
) andma
(True
). The count is now4
. - Then I move to the next substring which is
am
and I compare it with the next substringma
(True
). The count is now5
. - Then I move to the first substring of size
3
, which ismam
, and I compare it with the next substringama
(False
). The count stays at5
.
At this point, the loop is over and I'm left with the count 5
.
Optimization
The code works, but it is pretty consuming. For the third example in my code snippet, a string which contains 100
times the letter a
, the result of the count of possible anagrams is 166,650
and the code needs to make 490,050
iterations before getting to that result.
In the case of that 100 a's
string, it takes 1.324274
seconds to execute.
I am trying to optimize this code. Any thought or suggestion?