Skip to content

Commit 975d538

Browse files
committed
feat(strings): is anagram
1 parent 5f24b77 commit 975d538

File tree

3 files changed

+110
-66
lines changed

3 files changed

+110
-66
lines changed

poetry.lock

Lines changed: 29 additions & 29 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pystrings/anagram/__init__.py

Lines changed: 57 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,64 @@
1-
# *-coding:utf8-*
1+
from typing import Dict
22
from functools import reduce
33
from string import ascii_letters
44

55
from pymath.primes.is_prime import is_prime_with_re
66

77

8+
def is_anagram(s1: str, s2: str) -> bool:
9+
"""
10+
Check if s1 is an anagram of s2.
11+
Args:
12+
s1 (str): first string to check
13+
s2 (str): second string to check
14+
Return:
15+
bool: Whether the strings are anagrams of each other. If they are True is returned, False otherwise
16+
"""
17+
# first normalize the strings by removing white spaces which might result in uneven lengths if s1 and s2 are anagrams
18+
# of each other
19+
s1 = s1.replace(" ", "").lower()
20+
s2 = s2.replace(" ", "").lower()
21+
22+
# check the length of the strings. If the strings are not of the same length, then it's not possible for them to be
23+
# anagrams of each other
24+
if len(s1) != len(s2):
25+
return False
26+
27+
# This dictionary is used to keep track of the character count in the strings to check if the strings are anagrams
28+
# of each other, the character count must be equal in both strings. This enables the algorithm to keep track of this
29+
# count.
30+
ht: Dict[str, int] = dict()
31+
32+
# Loop through each character in the first string to count the number of characters and store them in the dictionary
33+
# this is linear, so, O(n) where n is the number of characters in the string as the loop has to iterate over each
34+
# character
35+
for char in s1:
36+
if char in ht:
37+
ht[char] += 1
38+
else:
39+
ht[char] = 1
40+
41+
# Loops through each character in the second string checking for the existence of that character in the already
42+
# populated dictionary. If a character, exists, the count is decremented, if not, the count is incremented. This
43+
# will be used to show the discrepancy in character count between the two strings
44+
for char in s2:
45+
if char in ht:
46+
ht[char] -= 1
47+
else:
48+
ht[char] = 1
49+
50+
# Finally, check each key in the dictionary. If a given key's count is not equal to 0, then the algorithm exits
51+
# early as it's not possible to have a character count of more than 0 for strings that are anagrams, since the above
52+
# loop should have reduced the character count to 0. This shows a discrepancy, meaning there is an extra character
53+
# in a string that is not in another string
54+
for key in ht:
55+
if ht[key] != 0:
56+
return False
57+
58+
# return true if all the checks above check out.
59+
return True
60+
61+
862
class Anagrams:
963
"""
1064
Anagram class to detect anagrams for letters
@@ -25,43 +79,10 @@ def detect_anagrams(self, word, word_list):
2579
res, word = [], word.lower()
2680
for x in word_list:
2781
if len(word) == len(x.lower()) and word != x.lower():
28-
if self.is_anagram(word, x.lower()):
82+
if is_anagram(word, x.lower()):
2983
res.append(x)
3084
return res
3185

32-
@staticmethod
33-
def is_anagram(s1, s2):
34-
"""
35-
Check if s1 is an anagram of s2
36-
:param s1: String to check
37-
:param s2: string to compare to
38-
:return: Whether the strings are anagrams
39-
:rtype: bool
40-
"""
41-
if len(s1.lower()) != len(s2.lower()):
42-
return False
43-
44-
a_list = list(s2)
45-
pos1 = 0
46-
flag = True
47-
48-
while pos1 < len(s1) and flag:
49-
pos2 = 0
50-
found = False
51-
while pos2 < len(a_list) and not found:
52-
if s1[pos1] == a_list[pos2]:
53-
found = True
54-
else:
55-
pos2 += 1
56-
57-
if found:
58-
a_list[pos2] = None
59-
else:
60-
flag = False
61-
62-
pos1 += 1
63-
64-
return flag
6586

6687
def anagram_count(self, parent, child):
6788
"""
@@ -87,7 +108,7 @@ def anagram_count(self, parent, child):
87108
# if the child's length is the same as the parent length AND the child and parent are not the same
88109
# check if it is an anagram
89110
if len(child) == len(parent) and child != parent:
90-
if self.is_anagram(child, parent):
111+
if is_anagram(child, parent):
91112
return 1
92113
else:
93114
return 0
Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,29 @@
11
import unittest
22

3-
from pystrings.anagram import Anagrams
3+
from pystrings.anagram import Anagrams, is_anagram
4+
5+
6+
class IsAnagramTests(unittest.TestCase):
7+
def test_fairy_tales_and_rail_safety(self):
8+
"""should return true for s1='fairy tales' and s2='rail safety'"""
9+
s1 = "fairy tales"
10+
s2 = "rail safety"
11+
actual = is_anagram(s1, s2)
12+
self.assertTrue(actual)
13+
14+
def test_william_shakespeare_and_i_am_a_weakish_speller(self):
15+
"""should return true for s1='William Shakespeare' and s2='I am a weakish speller'"""
16+
s1 = "William Shakespeare"
17+
s2 = "I am a weakish speller"
18+
actual = is_anagram(s1, s2)
19+
self.assertTrue(actual)
20+
21+
def test_madam_curie_and_radium_came(self):
22+
"""should return true for s1='Madam Curie' and s2='Radium came'"""
23+
s1 = "Madam Curie"
24+
s2 = "Radium came"
25+
actual = is_anagram(s1, s2)
26+
self.assertTrue(actual)
427

528

629
class AnagramTests(unittest.TestCase):

0 commit comments

Comments
 (0)