diff --git a/README.md b/README.md index ccc088c..ba3ac52 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ Read documentation of this project [here](docs/index.md). * [Stacks and Queues](#stacks-and-queues) * [Trees and Graphs](#trees-and-graphs) * [Recursion and Dynamic Programming](#recursion-and-dynamic-programming) +* [Sorting and Searching](#sorting_and_searching) ### Arrays and Strings @@ -51,7 +52,14 @@ Read documentation of this project [here](docs/index.md). | Number | Problem | Solution | |:------:|:------------------------:|:----------------------------------------------------------------------------------------:| -| 8.3 | Magic Index | [Distinct elements][8_3_distinct], [Non distinct elements][8_3_non_distinct] | +| 8.3 | Magic Index | [Distinct elements][8_3_distinct], [Non distinct elements][8_3_non_distinct] | + +### Sorting and Searching + +| Number | Problem | Solution | +|:------:|:------------------------:|:----------------------------------------------------------------------------------------:| +| 10.2 | Group Anagrams | [Brute Force][10_2_brute_force], [Improved][10_2_improved] | + [cracking_the_coding_interview]: https://www.amazon.com/Cracking-Coding-Interview-Programming-Questions/dp/0984782850 @@ -65,3 +73,5 @@ Read documentation of this project [here](docs/index.md). [4_1_dfs]: solutions/trees_and_graphs/route_between_nodes.py#L12 [8_3_distinct]: solutions/recursion_and_dynamic_programming/magic_index.py#L16 [8_3_non_distinct]: solutions/recursion_and_dynamic_programming/magic_index.py#L40 +[10_2_brute_force]: solutions/sorting_and_searching/group_anagrams.py#L11 +[10_2_improved]: solutions/sorting_and_searching/group_anagrams.py#L47 diff --git a/solutions/sorting_and_searching/__init__.py b/solutions/sorting_and_searching/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/solutions/sorting_and_searching/group_anagrams.py b/solutions/sorting_and_searching/group_anagrams.py new file mode 100644 index 0000000..a41d0de --- /dev/null +++ b/solutions/sorting_and_searching/group_anagrams.py @@ -0,0 +1,64 @@ +""" +Problem + + Write a method to sort an array of strings so that all the anagrams are + next to each other. +""" +from collections import defaultdict +from typing import List, Dict + + +class BruteForce: + """Brute force implementation of group anagrams""" + + def _is_anagrams(self, string_1: str, string_2: str) -> bool: + if len(string_1) != len(string_2): + return False + character_count: Dict = defaultdict(int) + for character in string_1: + character_count[character] += 1 + for character in string_2: + if ( + (character not in character_count) or + (character_count[character] == 0) + ): + return False + else: + character_count[character] -= 1 + return True + + def sort(self, anagrams: List[str]) -> None: + if not anagrams: + return None + index = 0 + while index < len(anagrams): + for index_of_left in range(index + 1, len(anagrams)): + if self._is_anagrams( + anagrams[index], anagrams[index_of_left] + ): + anagrams[index + 1], anagrams[index_of_left] = ( + anagrams[index_of_left], anagrams[index + 1] + ) + break + index += 2 + return + + +class Improved: + """Improved implementation to group anagrams""" + + def _calculate_score(self, anagram: str) -> int: + score = 0 + for character in anagram: + score += ord(character) + return score + + def sort(self, anagrams): + anagrams_collection = defaultdict(list) + while len(anagrams) != 0: + anagram = anagrams.pop() + score = self._calculate_score(anagram) + anagrams_collection[score].append(anagram) + for _, similar_anagrams in anagrams_collection.items(): + for anagram in similar_anagrams: + anagrams.append(anagram) diff --git a/solutions/sorting_and_searching/tests/__init__.py b/solutions/sorting_and_searching/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/solutions/sorting_and_searching/tests/test_group_anagrams.py b/solutions/sorting_and_searching/tests/test_group_anagrams.py new file mode 100644 index 0000000..700666c --- /dev/null +++ b/solutions/sorting_and_searching/tests/test_group_anagrams.py @@ -0,0 +1,40 @@ +from typing import Tuple + +from .. import group_anagrams + + +def test_brute_force_sort(): + sample_inputs_and_expected_outputs: Tuple = ( + ( + ["aa", "bb", "acca", "bb", "aa", "ccaa"], + ['aa', 'aa', 'acca', 'ccaa', 'bb', 'bb'] + ), + ([], []), + ( + ["a", "b", "c", "a", "b", "c"], + ["a", "a", "c", "c", "b", "b"] + ) + ) + + brute_force = group_anagrams.BruteForce() + for anagrams, expected_anagrams in sample_inputs_and_expected_outputs: + brute_force.sort(anagrams) + assert anagrams == expected_anagrams + + +def test_improved_sort(): + sample_inputs_and_expected_outputs: Tuple = ( + ( + ["aa", "bb", "acca", "bb", "aa", "ccaa"], + ['ccaa', 'acca', 'aa', 'aa', 'bb', 'bb'] + ), + ([], []), + ( + ["a", "b", "c", "a", "b", "c"], + ['c', 'c', 'b', 'b', 'a', 'a'] + ) + ) + improved = group_anagrams.Improved() + for anagrams, expected_anagrams in sample_inputs_and_expected_outputs: + improved.sort(anagrams) + assert anagrams == expected_anagrams