diff --git a/problems/929.unique-email-addresses/memo.md b/problems/929.unique-email-addresses/memo.md new file mode 100644 index 0000000..4fdccc2 --- /dev/null +++ b/problems/929.unique-email-addresses/memo.md @@ -0,0 +1,34 @@ +## step1 +- 愚直に解くなら一つずつ前から順に見ていき'.'は無視して'+'は'@'が出てくるまでの文字列を無視して処理した後setに追加していく +- 100*100なら上記の方針でいいと思うO(N^2) +- '.'があるか全て見ないといけないのでこれ以上の高速化は難しそう +- 今回はlowercaseのみだが、他の文字が入ってきた場合どうするかは考えたい + - 登録処理などで使えない文字を無視したまま登録できるとユーザとシステム側で認識がずれる問題があるので、使用できる文字だけで構成されているかの確認は必要 +- 元々+の仕様は知っていたが.の仕様は知らなかった +- 一旦実装する + - 処理した後のアドレスの一般的な呼び方がついていると思い調べたが、なかった + - normalized email address/canonical email address + +- ローカルパートとドメインパートでルールが異なるので分けたい気持ちになったがドメインパートはそのままくっつけるだけなのでコメントで補足に留めた(より複雑なルールに対応する時にローカルとドメインでそれぞれ関数にしてもらうつもり) + +## step2 +- 他の人のコードを読む +- https://github.com/Hiroto-Iizuka/coding_practice/pull/14/files + - partition() + - https://docs.python.org/ja/3/library/stdtypes.html#str.partition + - 区切り文字を含めた3要素のタプルが返却される + - 区切り文字がない場合は真ん中の要素に空文字列が入る + - いきなり`(local_name, _, domain_name) = email.partition("@")`でいいと思った + - 真ん中がいらないなら`split('@')`だが@が入っていない場合問題が出てくるので`local_name, domain_name, *rest = email.split("@")`よりもpartitionの方が良さそうに思った + - for文で書くよりこっちの方がわかりやすいと思った +- https://github.com/mamo3gr/arai60/pull/14/files + - 走査を1回にした場合のような感じでstep1を解いていた + - step1で十分わかりやすく感じた + - `あ、あと、文字列の追記は文字列の再構築が走るので、(CPython は最適化されるみたいですが、)指摘されたらリストに append して join ですね。` + - 短くてわかりやすいくらいの理由で+=で書いていたが理由がなければ .joinで書くようにする(長い文字列を都度再構築させていないかという不安を感じさせない) + - `あくまで個人的な感覚ですが、 try except は重そうという印象があります。実際にどの程度重いかは実装してみないと何とも言えません。個人的には、エラーコード等で代替するなど、現実的なコストで避けられるのであれば避けたいです。この辺りは所属するチームの平均的な書き方に合わせることをお勧めいたします。` + - try-exceptは重そうという感覚を持っておく + + +## step3 +- エラー文考えるの毎回手間でAIに丸投げしているが、チーム全体である程度の足並みは揃えないと書き方がバラバラになりそうだなと思った。 \ No newline at end of file diff --git a/problems/929.unique-email-addresses/step1.py b/problems/929.unique-email-addresses/step1.py new file mode 100644 index 0000000..bafcaa2 --- /dev/null +++ b/problems/929.unique-email-addresses/step1.py @@ -0,0 +1,36 @@ +# +# @lc app=leetcode id=929 lang=python3 +# +# [929] Unique Email Addresses +# + +# @lc code=start +class Solution: + def numUniqueEmails(self, emails: list[str]) -> int: + unique_emails = set() + + for email in emails: + normalized_email = "" + is_after_plus = False + email.partition("@") + for i, c in enumerate(email): + # ドメインパートのルール + if c == "@": + normalized_email.join(email[i:]) + break + + # ローカルパートのルール + if c == "." or is_after_plus: + continue + if c == "+": + is_after_plus = True + continue + + normalized_email.join(c) + + unique_emails.add(normalized_email) + + return len(unique_emails) + + +# @lc code=end diff --git a/problems/929.unique-email-addresses/step2.py b/problems/929.unique-email-addresses/step2.py new file mode 100644 index 0000000..eae8624 --- /dev/null +++ b/problems/929.unique-email-addresses/step2.py @@ -0,0 +1,31 @@ +# +# @lc app=leetcode id=929 lang=python3 +# +# [929] Unique Email Addresses +# + +# @lc code=start +class Solution: + def numUniqueEmails(self, emails: list[str]) -> int: + unique_emails = set() + for email in emails: + split_result = email.split("@") + if len(split_result) != 2: + raise ValueError("email must contain exactly one '@'.") + local_part, domain_part = split_result + + local_part_before_plus = local_part.partition("+")[0] + canonicalized_local_part = local_part_before_plus.replace(".", "") + + if not len(canonicalized_local_part) or not len(domain_part): + raise ValueError("local and domain names must not be empty") + + if not domain_part.endswith(".com"): + raise ValueError("email must end with '.com'.") + + unique_emails.add(f"{canonicalized_local_part}@{domain_part}") + + return len(unique_emails) + + +# @lc code=end diff --git a/problems/929.unique-email-addresses/step3.py b/problems/929.unique-email-addresses/step3.py new file mode 100644 index 0000000..57a2fd1 --- /dev/null +++ b/problems/929.unique-email-addresses/step3.py @@ -0,0 +1,34 @@ +# +# @lc app=leetcode id=929 lang=python3 +# +# [929] Unique Email Addresses +# + +# @lc code=start +class Solution: + def numUniqueEmails(self, emails: list[str]) -> int: + unique_emails = set() + + for email in emails: + split_result = email.split("@") + + if len(split_result) != 2: + raise ValueError('Email must have one "@".') + + local_part, domain_part = split_result + + local_part_before_plus = local_part.partition("+")[0] + canonicalized_local_part = local_part_before_plus.replace(".", "") + + if not canonicalized_local_part or not domain_part: + raise ValueError("local part or domain part is empty.") + + if not domain_part.endswith(".com"): + raise ValueError('Email must end with ".com".') + + unique_emails.add(f"{canonicalized_local_part}@{domain_part}") + + return len(unique_emails) + + +# @lc code=end