-
Notifications
You must be signed in to change notification settings - Fork 0
929. Unique Email Addresses #24
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,146 @@ | ||||||||
| # 929. Unique Email Addresses | ||||||||
| - 問題: https://leetcode.com/problems/unique-email-addresses/ | ||||||||
| - 言語: Python | ||||||||
|
|
||||||||
| ## Step1 | ||||||||
| - メールアドレスのローカルパートとドメインパートを問題の条件通りに1文字ずつ走査し正規化する | ||||||||
| - 事前条件として入力文字列の文字種はASCII範囲のアルファベット、数字、`+`、`.`、`@` を保証している | ||||||||
| - `@` は1つだけ | ||||||||
| - 事前条件のバリデーションは書かない方針 | ||||||||
| - 今回はメールアドレス文字列長が最大100文字なので、文字列追記 `+=` でも実行時間としては特に問題ないと判断 | ||||||||
| - ifの順番がセンシティブ | ||||||||
|
|
||||||||
| ### 解答(AC) | ||||||||
| ```py | ||||||||
| class Solution: | ||||||||
| def numUniqueEmails(self, emails: List[str]) -> int: | ||||||||
| normalized_emails = set() | ||||||||
|
|
||||||||
| for email in emails: | ||||||||
| normalized_email = "" | ||||||||
| has_plus_sign = False | ||||||||
| for i, s in enumerate(email): | ||||||||
| if s == "@": | ||||||||
| normalized_email += email[i:] | ||||||||
| break | ||||||||
| if has_plus_sign: | ||||||||
| continue | ||||||||
| if s != "." and s != "+": | ||||||||
| normalized_email += s | ||||||||
| continue | ||||||||
| if s == ".": | ||||||||
| continue | ||||||||
|
Comment on lines
+31
to
+32
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. こちらの条件は無くてもよいかなと思いました |
||||||||
| if s == "+": | ||||||||
| has_plus_sign = True | ||||||||
| continue | ||||||||
|
|
||||||||
| normalized_emails.add(normalized_email) | ||||||||
|
|
||||||||
| print(normalized_emails) | ||||||||
|
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. 最終的なコードからデバッグ出力は消しておいた方がよいかなと思います |
||||||||
| return len(normalized_emails) | ||||||||
| ``` | ||||||||
| - 解答時間: 20:14 | ||||||||
| - 時間計算量: $O(n^2)$ | ||||||||
|
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. 文字列の個数と文字の長さは分けた方が良いのでO(M * N)などの記載が良いと思います |
||||||||
|
|
||||||||
| ## Step2 | ||||||||
| - 典型コメント集: https://docs.google.com/document/d/11HV35ADPo9QxJOpJQ24FcZvtvioli770WWdZZDaLOfg/edit?tab=t.0#heading=h.h73uwlfq793n | ||||||||
| - https://github.com/hayashi-ay/leetcode/pull/25 | ||||||||
| - Python | ||||||||
| - ローカルパートとドメインパートをフラグで管理 | ||||||||
| - 自分のコードでは `normalize` としたが `canonicalize` のほうが正確だと思った | ||||||||
| - 1文字ずつ走査していくのはステートマシン(状態機械)を作っていることと同じ | ||||||||
| - 文字列の追記は文字列の再構築が走る | ||||||||
| - > あ、あと、文字列の追記は文字列の再構築が走るので、(CPython は最適化されるみたいですが、)指摘されたらリストに append して join ですね。 | ||||||||
| - cf. https://discord.com/channels/1084280443945353267/1200089668901937312/1210258224087961650 | ||||||||
| - > https://github.com/python/cpython/blob/bb3e0c240bc60fe08d332ff5955d54197f79751c/Objects/unicodeobject.c#L11768 | ||||||||
| > 文字列の追加 += が最適化される条件はここにあります。 | ||||||||
| > | ||||||||
| > 背景として、Python の文字列がイミュータブルであることは常識なので、このコードを見たら誰もが不安にかられるので、その不安感を共有できていますか、という質問です。 | ||||||||
| > | ||||||||
| > で、それに対して、「そう思っていたんですが実験した範囲では最適化が効くみたいです。」という返答は、なかなかに困って、というのも、次の疑問がわいてきます。「いつでもその最適化は行われるのか。インタープリターのバージョンに依存しないのか、たとえば、バージョンアップで最適化がなくなることはないのか。もしも、最適化がされることが保証されていないならば、そのように書いておいたとして、どういった場合に最適化されないのか。そのような仕様が仮にあるのだとしたら、そのドキュメントへのリンクをコメントで書いておいて欲しい。また、Python のバージョンが変わったときには、その最適化がされることが保証されていないならば、そのドキュメントをもう一回見て、バージョンによって仕様が変わっていないかどうかを確認するプロセスが必ず走るようにして欲しい。」 | ||||||||
| > | ||||||||
| > で、ここまでの疑問にその場で答えられるならば、面接官は、なるほど、そうなんですね、といって、Python に詳しい人だと思うでしょう。 | ||||||||
| > | ||||||||
| > で、仕様レベルで最適化が保証されているのだとしても、最低限コメントとして、「このような場合には最適化されることが保証される。どこどこ参照。」と書いておかないと、今後そのコードを読んでデバッグする人が、「むむ、もしかして、今回のタイムアウトの原因はここではないかな?」といって余計な実験をすることになるわけです。 | ||||||||
| > | ||||||||
| > つまり、「あ、Python の文字列はイミュータブルなので、こうしたほうがいいですね。」は減点なしの評価で、まあ、思わずやっちゃうことあるよね、くらいの感覚です。 | ||||||||
| 「この場合は最適化されることが仕様レベルで保証されているのでその旨のコメントを書き足しましょうか。」も減点はしにくいけれども、それだったら join で書き直しませんか、という気分ですね。 | ||||||||
| > つまり、気にしているのは、オーダーが正しく動くか、ではなくて、半年後に読んだ別の同僚が不安にならないか、環境の変化に対して頑健か、なのです。 | ||||||||
| - cf. https://discord.com/channels/1084280443945353267/1200089668901937312/1210619083385479258 | ||||||||
| - `join` なら $O(n)$ で済むので、`+=` を使うconsが思いつかず特にこだわりがないなら、こちらを使うべきだなと思った | ||||||||
| - メールアドレスに関連するRFC | ||||||||
| - [RFC 1034](https://www.ietf.org/rfc/rfc1034.txt#:~:text=The%20labels%20must%20follow%20the%20rules%20for%20ARPANET%20host%20names.%20%20They%20must%0Astart%20with%20a%20letter%2C%20end%20with%20a%20letter%20or%20digit%2C%20and%20have%20as%20interior%0Acharacters%20only%20letters%2C%20digits%2C%20and%20hyphen.%20%20There%20are%20also%20some%0Arestrictions%20on%20the%20length.%20%20Labels%20must%20be%2063%20characters%20or%20less) | ||||||||
| - [RFC 5322](https://datatracker.ietf.org/doc/html/rfc5322#:~:text=local-part%20%20%20%20%20%20%3D%20%20%20dot-atom%20/%20quoted-string%20/%20obs-local-part) | ||||||||
| - メールアドレス文字列の仕様は 3.4.1. Addr-Spec Specification のあたり | ||||||||
| - > maxsplit rsplit はご存知ですか? | ||||||||
| - `rsplit` は知っていたが、`maxsplit` は知らなかった | ||||||||
| - cf. https://docs.python.org/3.12/library/stdtypes.html#str.split | ||||||||
| - > RFCを読むとlocal-partsに@マークが使える(ダブルクオートで書こうと)ので、今のコードだと有効なメールアドレスでも誤判定してしまいます(他にももっと対応できてないケースがありますが) | ||||||||
| > | ||||||||
| > email.rsplit("@", maxsplit=1)で分割してあげれば@マークが最低1つ存在する場合については確実に有効なドメインが取得できそうです。 | ||||||||
| - cf. https://discord.com/channels/1084280443945353267/1200089668901937312/1209416153982697492 | ||||||||
| - 他には正規表現を使った方法 | ||||||||
| - cf. https://docs.python.org/ja/3/library/re.html#re.sub | ||||||||
|
|
||||||||
| - https://github.com/colorbox/leetcode/pull/28 | ||||||||
| - C++ | ||||||||
| - コピーするか?しないか?の選択肢の判断 | ||||||||
|
|
||||||||
| - https://github.com/seal-azarashi/leetcode/pull/14 | ||||||||
| - Java | ||||||||
| - > いや、全部正規表現にしなくてもよいですよね。元のループを回すコードは要するにステートマシンを書いています。 | ||||||||
| > しかし、そうではなくて、いくつかの関数の組み合わせで書けるはずです。 | ||||||||
| > | ||||||||
| > 1. @ の前後で分ける | ||||||||
| > 2. 前半に対して、+ の前までを取る | ||||||||
| > 3. 前半から . を取り除く | ||||||||
| > 4. 結合する | ||||||||
| - cf. https://github.com/seal-azarashi/leetcode/pull/14/files#r1677225649 | ||||||||
|
|
||||||||
| - https://github.com/t0hsumi/leetcode/pull/14 | ||||||||
| - Python | ||||||||
| - setの内包表記 | ||||||||
| - cf. https://github.com/t0hsumi/leetcode/pull/14/files#r1929803127 | ||||||||
|
|
||||||||
| - https://github.com/plushn/SWE-Arai60/pull/14 | ||||||||
| - Python | ||||||||
| - RFCでのメールアドレス最大文字列帳は254文字 | ||||||||
| - cf. https://github.com/plushn/SWE-Arai60/pull/14/files#r2052171339 | ||||||||
|
|
||||||||
| ### 読みやすく書き直したコード | ||||||||
| ```py | ||||||||
| class Solution: | ||||||||
| def numUniqueEmails(self, emails: List[str]) -> int: | ||||||||
| def canonicalize(email: str) -> str: | ||||||||
| local_part, domain_part = email.split("@") | ||||||||
| local_part = local_part.split("+")[0] | ||||||||
| local_part = local_part.replace(".", "") | ||||||||
|
Comment on lines
+116
to
+117
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. 以下のように一行で書くこともできそうです
Suggested change
|
||||||||
| return local_part + "@" + domain_part | ||||||||
|
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. 以下のようにf-stringで書く方法もあります、個人的には形が分かりやすいのでf-stringの方が好みです
Suggested change
|
||||||||
|
|
||||||||
| unique_emails = set() | ||||||||
| for email in emails: | ||||||||
| unique_emails.add(canonicalize(email)) | ||||||||
|
|
||||||||
| return len(unique_emails) | ||||||||
| ``` | ||||||||
|
|
||||||||
| ## Step3 | ||||||||
| ```py | ||||||||
| class Solution: | ||||||||
| def numUniqueEmails(self, emails: List[str]) -> int: | ||||||||
| def canonicalize(email: str) -> str: | ||||||||
| local_part, domain_part = email.split("@") | ||||||||
|
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.
|
||||||||
| local_part = local_part.split("+")[0] | ||||||||
| local_part = local_part.replace(".", "") | ||||||||
| return local_part + "@" + domain_part | ||||||||
|
|
||||||||
| unique_emails = set() | ||||||||
| for email in emails: | ||||||||
| unique_emails.add(canonicalize(email)) | ||||||||
|
|
||||||||
| return len(unique_emails) | ||||||||
| ``` | ||||||||
| - 解答時間 | ||||||||
| - 1回目: 2:46 | ||||||||
| - 2回目: 2:42 | ||||||||
| - 3回目: 2:48 | ||||||||
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.
文字を表す変数にsを使われたのはなぜでしょうか?
個人的にはsは文字列という印象があるので、c, chあたりにするかなと思いました。
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.
ありがとうございます。確かにこれは
cのほうが良いですね。