From 2832a7b3ff7ae844dad8cd1d03d8a33cce8f80c6 Mon Sep 17 00:00:00 2001 From: tom4649 Date: Sat, 14 Mar 2026 15:39:54 +0900 Subject: [PATCH 1/2] 929. Unique Email Addresses --- 929/memo.md | 14 ++++++++++++++ 929/sol1.py | 32 ++++++++++++++++++++++++++++++++ 929/sol2.py | 8 ++++++++ 929/sol3.py | 28 ++++++++++++++++++++++++++++ 929/sol4.py | 17 +++++++++++++++++ 5 files changed, 99 insertions(+) create mode 100644 929/memo.md create mode 100644 929/sol1.py create mode 100644 929/sol2.py create mode 100644 929/sol3.py create mode 100644 929/sol4.py diff --git a/929/memo.md b/929/memo.md new file mode 100644 index 0000000..07d6d34 --- /dev/null +++ b/929/memo.md @@ -0,0 +1,14 @@ +# 929. Unique Email Addresses + +- [リンク](https://leetcode.com/problems/unique-email-addresses/description/) +- 組み込み関数を使わずに書いたらかなり遅い(sol1.py) +- 調べて組み込み関数を使うとかなり高速になった(sol2.py) +- 有限ステートマシン(sol3.py) + - [https://discord.com/channels/1084280443945353267/1200089668901937312/1207996784211918899](https://discord.com/channels/1084280443945353267/1200089668901937312/1207996784211918899) +- - 「あ、あと、文字列の追記は文字列の再構築が走るので、(CPython は最適化されるみたいですが、)指摘されたらリストに append して join ですね。[https://github.com/python/cpython/blob/bb3e0c240bc60fe08d332ff5955d54197f79751c/Objects/unicodeobject.c#L11768](https://github.com/python/cpython/blob/bb3e0c240bc60fe08d332ff5955d54197f79751c/Objects/unicodeobject.c#L11768) 文字列の追加 += が最適化される条件はここにあります。 背景として、Python の文字列がイミュータブルであることは常識なので、このコードを見たら誰もが不安にかられるので、その不安感を共有できていますか、という質問です。 で、それに対して、「そう思っていたんですが実験した範囲では最適化が効くみたいです。」という返答は、なかなかに困って、というのも、次の疑問がわいてきます。「いつでもその最適化は行われるのか。インタープリターのバージョンに依存しないのか、たとえば、バージョンアップで最適化がなくなることはないのか。もしも、最適化がされることが保証されていないならば、そのように書いておいたとして、どういった場合に最適化されないのか。そのような仕様が仮にあるのだとしたら、そのドキュメントへのリンクをコメントで書いておいて欲しい。また、Python のバージョンが変わったときには、その最適化がされることが保証されていないならば、そのドキュメントをもう一回見て、バージョンによって仕様が変わっていないかどうかを確認するプロセスが必ず走るようにして欲しい。」 で、ここまでの疑問にその場で答えられるならば、面接官は、なるほど、そうなんですね、といって、Python に詳しい人だと思うでしょう。 で、仕様レベルで最適化が保証されているのだとしても、最低限コメントとして、「このような場合には最適化されることが保証される。どこどこ参照。」と書いておかないと、今後そのコードを読んでデバッグする人が、「むむ、もしかして、今回のタイムアウトの原因はここではないかな?」といって余計な実験をすることになるわけです。 つまり、「あ、Python の文字列はイミュータブルなので、こうしたほうがいいですね。」は減点なしの評価で、まあ、思わずやっちゃうことあるよね、くらいの感覚です。 「この場合は最適化されることが仕様レベルで保証されているのでその旨のコメントを書き足しましょうか。」も減点はしにくいけれども、それだったら join で書き直しませんか、という気分ですね。 つまり、気にしているのは、オーダーが正しく動くか、ではなくて、半年後に読んだ別の同僚が不安にならないか、環境の変化に対して頑健か、なのです。」 + - [https://github.com/hayashi-ay/leetcode/pull/25/changes](https://github.com/hayashi-ay/leetcode/pull/25/changes) +- regexp と、ループと、文字列操作関数をそれぞれ使えるようにしておいてください。 + - [https://discord.com/channels/1084280443945353267/1192736784354918470/1192805716763889724](https://discord.com/channels/1084280443945353267/1192736784354918470/1192805716763889724) + - 真似してかいた(sol4.py) + - https://github.com/hayashi-ay/leetcode/pull/25/changes + - 正規表現: [https://docs.python.org/ja/3/library/re.html](https://docs.python.org/ja/3/library/re.html) diff --git a/929/sol1.py b/929/sol1.py new file mode 100644 index 0000000..a3116a8 --- /dev/null +++ b/929/sol1.py @@ -0,0 +1,32 @@ +class Solution: + def numUniqueEmails(self, emails: List[str]) -> int: + def normalize_email(email): + idx_at = None + idx_plus = None + idxs_dot = [] + for i, c in enumerate(email[::-1]): + if c == "@": + idx_at = len(email) - i - 1 + if idx_at is not None and c == "+": + idx_plus = len(email) - i - 1 + if idx_at is not None and c == ".": + idxs_dot.append(len(email) - i - 1) + if idx_at is None: + raise ValueError + normalized_email = email + if idx_plus is not None: + normalized_email = ( + normalized_email[:idx_plus] + normalized_email[idx_at:] + ) + for idx_dot in idxs_dot: + if idx_plus is not None and idx_plus < idx_dot: + continue + normalized_email = ( + normalized_email[:idx_dot] + normalized_email[idx_dot + 1 :] + ) + return normalized_email + + unique_emails = set() + for email in emails: + unique_emails.add(normalize_email(email)) + return len(unique_emails) diff --git a/929/sol2.py b/929/sol2.py new file mode 100644 index 0000000..3f631dd --- /dev/null +++ b/929/sol2.py @@ -0,0 +1,8 @@ +class Solution: + def numUniqueEmails(self, emails: List[str]) -> int: + def normalize_email(email: str) -> str: + local, domain = email.split("@", 1) + local = local.split("+", 1)[0].replace(".", "") + return f"{local}@{domain}" + + return len({normalize_email(email) for email in emails}) diff --git a/929/sol3.py b/929/sol3.py new file mode 100644 index 0000000..58ec508 --- /dev/null +++ b/929/sol3.py @@ -0,0 +1,28 @@ +class Solution: + def numUniqueEmails(self, emails: List[str]) -> int: + def normalize(email): + LOCAL, LOCAL_IGNORE, DOMAIN = 0, 1, 2 + out = [] + state = LOCAL + for c in email: + if state == LOCAL: + if c == ".": + continue + if c == "+": + state = LOCAL_IGNORE + continue + if c == "@": + state = DOMAIN + out.append("@") + continue + out.append(c) + + elif state == LOCAL_IGNORE: + if c == "@": + state = DOMAIN + out.append("@") + else: + out.append(c) + return "".join(out) + + return len({normalize(email) for email in emails}) diff --git a/929/sol4.py b/929/sol4.py new file mode 100644 index 0000000..c6ab27d --- /dev/null +++ b/929/sol4.py @@ -0,0 +1,17 @@ +import re +from typing import List + + +class Solution: + def numUniqueEmails(self, emails: List[str]) -> int: + pattern = re.compile(r"^([a-z0-9.]+)(?:\+[^@]*)?@([a-z0-9.+]+)$") + + def normalize(email): + match = pattern.fullmatch(email) + if not match: + raise ValueError + local = match.group(1).replace(".", "") + domain = match.group(2) + return f"{local}@{domain}" + + return len({normalize(email) for email in emails}) From 014c55d17a9a387e4e07bd2318d7bc91f1df4b26 Mon Sep 17 00:00:00 2001 From: tom4649 Date: Sat, 14 Mar 2026 16:06:04 +0900 Subject: [PATCH 2/2] Add memo --- 929/memo.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/929/memo.md b/929/memo.md index 07d6d34..db72d08 100644 --- a/929/memo.md +++ b/929/memo.md @@ -12,3 +12,7 @@ - 真似してかいた(sol4.py) - https://github.com/hayashi-ay/leetcode/pull/25/changes - 正規表現: [https://docs.python.org/ja/3/library/re.html](https://docs.python.org/ja/3/library/re.html) +- https://github.com/mamo3gr/arai60/blob/main/929_unique-email-addresses/memo.md + - 「canonicalize なるほど。ChatGPT曰く、normalize よりも「仕様の決まっている正解にまとめる」ニュアンスが強いらしい。」 + - str partirionというメソッドがあるらしい + - 二分割したい場合にはこちらの方が速い