---
# System prepended metadata

title: Sprint 2 Module Project 3

---

# Sprint 2 Module Project 3
## Isomorphic Strings
```
"""
Understand

a = "odd"
b = "egg"
output: True ("o" -> "e", "d" -> "g")

a = "oddo"
b = "eggf"
output: False ("o" cannot map to both "e" and "f")

a = "oddh"
b = "egge"
output: False ("o" and "h" cannot both map to "e")

Plan
1. Use a dictionary to keep track of the character mapping from a --> b
2. Whenever you're going to insert a mapping, check if a mapping for
that character already exists.
3. If it already exists and the mapping is not the same, then it can't be isomorphic.
4. If a mapping for that character doesn't yet exist, also check if the character
it's going to be mapped to isn't already mapped by another character (see test case 3)
5. Use a dictionary to track char mapping in a --> b
6. Use a set to track which characters in b have been mapped

Note: I could have used .values() instead of using a set for mappedCharsinB
but that has a slower O(n) runtime for lookup

Runtime: O(length of a or b)
Space: O(length of a or b)
"""

def csIsomorphicStrings(a, b):
    if len(a) != len(b):
        return False
    charMapping = {} # mapping from a->b
    mappedCharsInB = set()
    for i in range(len(a)):
        if a[i] in charMapping: # char in a has already been mapped
            # this char in a doesn't map the same char in b
            # therefore it can't be isomorphic
            if b[i] != charMapping[a[i]]:
                return False
        else: # char in a hasn't been mapped yet
            # check if char in b hasn't been mapped yet
            if not b[i] in mappedCharsInB:
                charMapping[a[i]] = b[i]
                mappedCharsInB.add(b[i])
            else:
                # if char in b has already been mapped, then some other
                # char in a has already been mapped to it
                # therefore it can't be isomorphic
                return False
    return True
```

## Word Pattern
```
"""
Understand

pattern = "abba"
a = "dog cat cat dog"
output: True

pattern = "bb"
a = "dog cat"
output: False

pattern = "ab"
a = "dog dog"
output: False

Plan

This is very similar to the last question, but instead of
comparing character->character
we're now comparing character->word

Runtime: O(max(length of pattern, length of a))
Space: O(max(length of pattern, length of a))
"""

def csWordPattern(pattern, a):
    wordList = a.split(' ')

    if len(pattern) != len(wordList):
        return False

    charToWordMapping = {}
    mappedWordsInB = set()
    for i in range(len(pattern)):
        if pattern[i] in charToWordMapping:
            if wordList[i] != charToWordMapping[pattern[i]]:
                return False
        else:
            if not wordList[i] in mappedWordsInB:
                charToWordMapping[pattern[i]] = wordList[i]
                mappedWordsInB.add(wordList[i])
            else:
                return False
    return True
```

## Group Anagrams
```
"""
Understand

strs = ["dog", "god", "cat", "act", "lol"]
output = [["dog", "god"], ["cat", "act"], ["lol"]]

strs = ["dog", "cat", "lol"]
output = [["dog"], ["cat"], ["lol"]]

strs = []
output = []

Plan
1. Sort the characters for each string in the list
2. Realize that anagrams will have the same ordering of characters once sorted (e.g. "god" and "dog" when sorted are both "dgo")
3. Group anagrams together by using a dictionary e.g. {"dgo" -> ["god", "dog"]}
4. Get all the values in the dictionary, put them into one array and sort the array based on their
length

Runtime: O(n * klogk + nlogn) where k = length of word, n = length of strs
Space: O(number of elements in strs)
"""
from collections import defaultdict

def csGroupAnagrams(strs):
    anagrams = defaultdict(list)

    for word in strs:
        anagramOrdering = str(sorted(word))
        anagrams[anagramOrdering].append(word)

    res = []
    for (key, words) in anagrams.items():
        res.append(words)

    res.sort(reverse=True, key=len)
    return res
```