-
Notifications
You must be signed in to change notification settings - Fork 0
Add 49. Group Anagrams.md #12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,177 @@ | ||
# step 1 | ||
考えたこと | ||
- アナグラムとなる文字列同士で共通しているもの->文字の出現回数 | ||
- 出現回数->それに該当する文字列の集合のマッピングを作りたい | ||
- 出現回数の表現 | ||
- 単純に数え上げる, nを文字列の長さとしてO(n) | ||
- アナグラムなら、sortしたら同じ文字列になる、O(n log(n)) or O(n^2)くらいか | ||
- sortを10^4個の文字列でやりたくない | ||
- 出現回数を数えてマッピングを作る。 | ||
```python3 | ||
class Solution: | ||
def groupAnagrams(self, strs: List[str]) -> List[List[str]]: | ||
def count_char_frequency(string: str) -> Tuple[int, ...]: | ||
char_frequency = [0] * 26 | ||
for char in string: | ||
index = ord(char) - ord('a') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. https://docs.python.org/3/library/functions.html#ord There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. asciiでa-zが連続していることは知っていましたが、連続していない文字コードがあることは一切想定していなかったです。 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Python ord は Unicode のコードポイントが返ってくるので連続しているとしていいんです。つまり、結果的には同じコードになるんですが、しかし「Python ord は Unicode のコードポイントだからアルファベット部分は ASCII と同じ、だから連続しているとしてよい」と思って書いたほうがいいでしょう。 |
||
char_frequency[index] += 1 | ||
return tuple(char_frequency) | ||
|
||
frequency_to_strings = {} | ||
for string in strs: | ||
char_frequency = count_char_frequency(string) | ||
if char_frequency not in frequency_to_strings: | ||
frequency_to_strings[char_frequency] = [] | ||
frequency_to_strings[char_frequency].append(string) | ||
return [strings for strings in frequency_to_strings.values()] | ||
``` | ||
n=strs.length, mをstrsの中にある文字列の中で最大の長さとすると、 | ||
- time complexity: O(nm) | ||
- space complexity: O(n) | ||
|
||
`count_char_frequency()`の返り値の型で、流石に26個intを並べるのもどうかと思って`...`にした。 | ||
dictのkeyとなるものがhashableになる様に注意した。 | ||
|
||
sortするパターンも実装した | ||
```python | ||
class Solution: | ||
def groupAnagrams(self, strs: List[str]) -> List[List[str]]: | ||
sorted_string_to_anagrams = {} | ||
for string in strs: | ||
sorted_characters = sorted(string) | ||
sorted_string = ''.join(sorted_characters) | ||
if sorted_string not in sorted_string_to_anagrams: | ||
sorted_string_to_anagrams[sorted_string] = [] | ||
sorted_string_to_anagrams[sorted_string].append(string) | ||
return [anagrams for anagrams in sorted_string_to_anagrams.values()] | ||
``` | ||
n=strs.length, mをstrsの中にある文字列の中で最大の長さとすると、 | ||
- time complexity: O(n m log(m)) | ||
- space complexity: O(n * m) | ||
|
||
# step 2 | ||
文字出現回数のカウントにはCounterを使っても良かった。 | ||
上の二つはdefaultdictを使っても良かった。 | ||
|
||
一つ目の解法のほうがパフォーマンスは良いと思っていたがleetcodeのruntimeをみると、一つ目のほうが二つ目に比べ倍程度時間がかかっていた。 | ||
|
||
- https://github.com/katataku/leetcode/pull/11/files | ||
- `list(dict.values())`としていた | ||
- `sorted_string = ''.join(sorted(string))`としていた。これくらいなら、`sorted(string)`を別変数でおかなくても十分読めるなと感じた | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 一応、str(sorted(string)) が ''.join(sorted(string)) とは異なるものであるという認識があるかの確認はしておきます。 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 違いについて説明せよと言われてもできなそうなので、調べました。 文字列をsortしたリストを引数にした場合、どちらも返す値は変わらないので今回はどっちでもいいのかなぁくらいの感想でした。 sorted(string)
str(sorted(string))
''.join(sorted(string)) |
||
- https://github.com/colorbox/leetcode/pull/26/files | ||
- sortする、数えるどちらも行っていた。 | ||
- 数える場合、英語小文字以外が来た場合の対処をどうするか | ||
- 特殊な値を返す | ||
- 例外処理 | ||
- 英語小文字以外無視 | ||
- プログラムを落とす | ||
|
||
自分の実装は落ちるものだった。この問題では、特殊な値として適するものが思い当たらないので、例外処理を入れるか無視するかが穏当な気がした。 | ||
- https://github.com/tarinaihitori/leetcode/pull/12/files | ||
- [pythonのhashable](https://docs.python.org/3/glossary.html#term-hashable) | ||
- tupleとstrのhashの計算 | ||
- [tuple](https://github.com/python/cpython/blob/260843df1bd8a28596b9a377d8266e2547f7eedc/Objects/tupleobject.c#L318):tuple内の全ての要素に対してhashを計算し、tuple自体のhashを求める | ||
- str | ||
- https://github.com/python/cpython/blob/47c5a0f307cff3ed477528536e8de095c0752efa/Python/pyhash.c#L149 | ||
- https://github.com/python/cpython/blob/2513593303b306cd8273682811d26600651c60e4/Objects/unicodeobject.c#L11691 | ||
|
||
hashがsetされていればそれを返し、されていなければ計算する。 | ||
- [joinがメモリ割り当てを一回で済ませている](https://github.com/python/cpython/blob/main/Objects/stringlib/join.h#L109-L110) | ||
- tupleも一回で済ませているようにみえた | ||
- https://github.com/python/cpython/blob/47c5a0f307cff3ed477528536e8de095c0752efa/Objects/tupleobject.c#L68 | ||
- https://github.com/python/cpython/blob/47c5a0f307cff3ed477528536e8de095c0752efa/Objects/tupleobject.c#L374 | ||
|
||
```python | ||
from collections import defaultdict | ||
|
||
|
||
class Solution: | ||
def groupAnagrams(self, strs: List[str]) -> List[List[str]]: | ||
sorted_string_to_anagrams = defaultdict(list) | ||
for s in strs: | ||
sorted_string = str(sorted(s)) | ||
sorted_string_to_anagrams[sorted_string].append(s) | ||
return list(sorted_string_to_anagrams.values()) | ||
``` | ||
この様に書くと、lowercase以外が入ってきてもうごく。 | ||
|
||
```python | ||
from collections import defaultdict, Counter | ||
import string | ||
|
||
|
||
class Solution: | ||
def groupAnagrams(self, strs: List[str]) -> List[List[str]]: | ||
lowercase_frequency_to_anagrams = defaultdict(list) | ||
for s in strs: | ||
frequency = Counter(s) | ||
lowercase_frequency = tuple( | ||
frequency[lowercase] for lowercase in string.ascii_lowercase | ||
) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 感想: こういう書き方もあるんですね There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. リストの内包表記の部品ですよ。 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. あー、上は誤りですね。ジェネレータ式は文法上内包表記とは別物でした。 |
||
lowercase_frequency_to_anagrams[lowercase_frequency].append(s) | ||
return list(lowercase_frequency_to_anagrams.values()) | ||
``` | ||
上の様に書くと、lowercase以外は無視される。 | ||
関数内でしか使わない変数にしては`lowercase_frequency_to_anagrams`は長い気がする。 | ||
|
||
# step 3 | ||
```python3 | ||
from collections import defaultdict | ||
|
||
|
||
class Solution: | ||
def groupAnagrams(self, strs: List[str]) -> List[List[str]]: | ||
sorted_string_to_anagrams = defaultdict(list) | ||
for s in strs: | ||
sorted_string = str(sorted(s)) | ||
sorted_string_to_anagrams[sorted_string].append(s) | ||
return list(sorted_string_to_anagrams.values()) | ||
``` | ||
|
||
# step 4 | ||
コメントまとめ | ||
- https://docs.python.org/3/library/functions.html#ord | ||
> Python ord は Unicode のコードポイントが返ってくるので連続しているとしていいんです。つまり、結果的には同じコードになるんですが、しかし「Python ord は Unicode のコードポイントだからアルファベット部分は ASCII と同じ、だから連続しているとしてよい」と思って書いたほうがいいでしょう。 | ||
C / C++ だと、この保証はないのですが、しかし、そういう文字コードは稀なので「意図的にサポートを切り捨てた」と思っていればいいです。 | ||
- 切り捨てたものにも自覚的になるべき。というか、意識して切り捨てたのか、たまたま切り捨てた様にみえるのかは一緒に仕事したい基準に大きく関わってきそう。 | ||
- step 1で英語小文字の数をマジックナンバーで置いてしまっている。 | ||
|
||
|
||
あるキーに対応する値は、一つのanagramのグループだと考え、dictの末尾をanagramsからanagramに変更した。 | ||
|
||
|
||
sorted stringをkeyにする | ||
```python | ||
from collections import defaultdict | ||
|
||
|
||
class Solution: | ||
def groupAnagrams(self, strs: List[str]) -> List[List[str]]: | ||
sorted_string_to_anagram = defaultdict(list) | ||
for s in strs: | ||
sorted_string = str(sorted(s)) | ||
sorted_string_to_anagram[sorted_string].append(s) | ||
return list(sorted_string_to_anagram.values()) | ||
``` | ||
|
||
英語小文字の出現回数を数える | ||
```python | ||
from collections import Counter | ||
import string | ||
|
||
class Solution: | ||
def groupAnagrams(self, strs: List[str]) -> List[List[str]]: | ||
def count_lowercase_frequency(s: str) -> Tuple[int, ...]: | ||
char_to_count = Counter(s) | ||
return tuple( | ||
char_to_count[lowercase] for lowercase in string.ascii_lowercase | ||
) | ||
|
||
frequency_to_anagram = {} | ||
for s in strs: | ||
lowercase_frequency = count_lowercase_frequency(s) | ||
if lowercase_frequency not in frequency_to_anagram: | ||
frequency_to_anagram[lowercase_frequency] = [] | ||
frequency_to_anagram[lowercase_frequency].append(s) | ||
return list(frequency_to_anagram.values()) | ||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
としてマジックナンバーを避けると良いと思いました