diff --git a/929. Unique Email Addresses.md b/929. Unique Email Addresses.md new file mode 100644 index 0000000..c6b18a4 --- /dev/null +++ b/929. Unique Email Addresses.md @@ -0,0 +1,213 @@ +# step 1 +```python +import string + + +class Solution: + def numUniqueEmails(self, emails: List[str]) -> int: + def validate(email: str) -> bool: + if not 1 <= len(email) <= 100: + return False + for char in email: + if ( + char not in string.ascii_lowercase + and char not in ['+', '.', '@'] + ): + return False + if email.count('@') != 1: + return False + local_name, domain_name = email.split('@') + if local_name == '' or domain_name == '': + return False + if local_name.startswith('+'): + return False + if not domain_name.endswith('.com'): + return False + if not domain_name.removesuffix('.com'): + return False + return True + def simplify(email: str) -> str: + local, domain = email.split('@') + simplified_local = local.split('+')[0].replace('.', '') + return '@'.join([simplified_local, domain]) + + unique_emails = set() + for email in emails: + if not validate(email): + raise ValueError( + "numUniqueEmails(): An Invalid email given: ", + f"{email}" + ) + simplified_email = simplify(email) + unique_emails.add(simplified_email) + return len(unique_emails) + +``` + +英語の大文字小文字はメールでは区別されないとかルールがあったと思うが、 +完全に全部知っているとは思えなかったので、入力条件の確認だけ`validate()`関数を用いて確認した。 +@の数が1以外の場合はValueErrorを出した。 + +emailの最大長をm, emailの数をnとすると、 +- time complexity: O(mn) +- space complexity: O(mn) + +正規表現を使うことも考えたが、この程度ならstrのメソッドで済みそうだった。 + +# step 2 +- https://github.com/Hurukawa2121/leetcode/pull/14/files + - 関数名に`cannonicalizeEmail`や`normalizeEmail`を使っていた +- https://github.com/katataku/leetcode/pull/13/files + - 組み込み関数と正規表現で、同じ処理を粉う場合、組み込み関数を使ったほうが、 + 読んでいて認知負荷が低く読みやすく感じます。 + - https://github.com/kazukiii/leetcode/pull/15/files#r1646360391 +- https://github.com/tarinaihitori/leetcode/pull/14/files + - Python は比較的再代入を厭わない傾向があるように思いますが避けてもいいでしょう +- https://github.com/fhiyo/leetcode/pull/17/files + - 読みやすさの観点からは、単純な正規表現であれば使っても良いと思います。 + 一方、複雑な正規表現は、理解に時間がかかるため、あまり読みたくありません…。 + - おそらくこういった場合は、ステートマシンっぽくやって、 + bNF記法をファイル先頭に記しておくとかになるんだろうか。 +- https://github.com/fhiyo/leetcode/pull/17/files + - 正規表現での解法あり。 + +- https://docs.python.org/3/library/stdtypes.html#text-sequence-type-str + - 今回はsplitメソッドのmaxsplitは右端の記号で分割するもの。 +- 文字列を結合するのにjoin, +, f-stringを使う方法があった。 +f-stringか+がそのまま、左から読んで意味が通るのでわかりやすく感じた。 + - >For example, do not rely on CPython’s efficient implementation of + in-place string concatenation for statements in the form a += b or a = a + b. + This optimization is fragile even in CPython (it only works for some types) + and isn’t present at all in implementations that don’t use refcounting. + In performance sensitive parts of the library, the ''.join() form should be + used instead. This will ensure that concatenation occurs in linear time across + various implementations. + https://peps.python.org/pep-0008/#pet-peeves:~:text=For%20example%2C%20do,across%20various%0Aimplementations. + - https://google.github.io/styleguide/pyguide.html#310-strings + - パフォーマンスを気にするなら+は使うなとある + +- https://en.wikipedia.org/wiki/Email_address + - RFC5322(https://datatracker.ietf.org/doc/html/rfc5322) + RFC6854(https://datatracker.ietf.org/doc/html/rfc6854) + - 長かったので、abstractionと、該当してそうな箇所(https://datatracker.ietf.org/doc/html/rfc5322#section-3.4) + だけ流し読みした。 + - こういうとき、どれくらいの深さで読めばいいかよくわからない + (pythonのメソッドとかなら他でも使いそうだなと思えるが、これはemailに限った話なこともあり、、) + +正規化で行うことが、「+」以降を取り除く、「.」をなくすだけだったので、一行にまとめてしまったが、これは分けても良いと感じた。 +かといって、その度に、`local_without_plus_sign`とかやるのは冗長な気がした。真面目にやろうとすると、 +`local_without_plus_and_dot_sign`とかキリがないように思えた。 + +```py +import string + + +class Solution: + def numUniqueEmails(self, emails: List[str]) -> int: + def validate(email: str) -> bool: + if not 1 <= len(email) <= 100: + return False + for char in email: + if ( + char not in string.ascii_lowercase + and char not in ['+', '.', '@'] + ): + return False + if email.count('@') != 1: + return False + local, domain = email.split('@') + if not local or not domain: + return False + if local.startswith('+'): + return False + if not domain.endswith('.com'): + return False + if not domain.removesuffix('.com'): + return False + return True + def normalize(email: str) -> str: + local, domain = email.split('@') + local = local.split('+')[0] + local = local.replace('.', '') + return f"{local}@{domain}" + unique_emails = set() + for email in emails: + if not validate(email): + raise ValueError( + "numUniqueEmails(): Input email is invalid: ", + f"{email}" + ) + unique_emails.add(normalize(email)) + return len(unique_emails) +``` + +# step 3 +入力条件が成立すると仮定して解いた。 + +```python +class Solution: + def numUniqueEmails(self, emails: List[str]) -> int: + def normalize(email: str) -> str: + local, domain = email.split('@') + local = local.split('+')[0] + local = local.replace('.', '') + return f"{local}@{domain}" + unique_emails = set() + for email in emails: + unique_emails.add(normalize(email)) + return len(unique_emails) +``` + +# step 4 + +コメントまとめ +- boolを返す関数は`is_*`がわかりやすい。 + - 変数名に`is_*`がなければ抵抗なく使いたい +- 用語 + - normalize: 内部的な冗長性をなくすための正規化 + - canonicalize: 比較のための(外部への表示としての)正規化 + +```python +import string + + +class Solution: + def numUniqueEmails(self, emails: List[str]) -> int: + def is_valid(email: str) -> bool: + if not 1 <= len(email) <= 100: + return False + if email.count('@') != 1: + return False + for character in email: + if character in string.ascii_lowercase: + continue + if character in ['@', '.', '+']: + continue + return False + local, domain = email.split('@') + if not local or not domain: + return False + if local.startswith('+'): + return False + if not domain.endswith('.com'): + return False + if not domain.removesuffix('.com'): + return False + return True + + def canonicalize(email: str) -> str: + local, domain = email.split('@') + local = local.split('+')[0] + local = local.replace('.', '') + return f"{local}@{domain}" + + unique_emails = set() + for email in emails: + if not is_valid(email): + raise ValueError( + "numUniqueEmails(): Input email is invalid: ", + f"email = {email}" + ) + unique_emails.add(canonicalize(email)) + return len(unique_emails) +``` \ No newline at end of file