Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions 929/memo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# 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)
- https://github.com/mamo3gr/arai60/blob/main/929_unique-email-addresses/memo.md
- 「canonicalize なるほど。ChatGPT曰く、normalize よりも「仕様の決まっている正解にまとめる」ニュアンスが強いらしい。」
- str partirionというメソッドがあるらしい
- 二分割したい場合にはこちらの方が速い
32 changes: 32 additions & 0 deletions 929/sol1.py
Original file line number Diff line number Diff line change
@@ -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)
8 changes: 8 additions & 0 deletions 929/sol2.py
Original file line number Diff line number Diff line change
@@ -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})
28 changes: 28 additions & 0 deletions 929/sol3.py
Original file line number Diff line number Diff line change
@@ -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})
17 changes: 17 additions & 0 deletions 929/sol4.py
Original file line number Diff line number Diff line change
@@ -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})