-
Notifications
You must be signed in to change notification settings - Fork 0
Create UniqueEmailAddresses.md #11
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,161 @@ | ||
| # step1 何も見ずに解く | ||
| - 以下の要領で生成したアドレスをSetに保持 | ||
| - @の前後で分ける。 | ||
| - localNameを以下の要領で加工する | ||
| - localNameの+以下の部分を切り捨てる | ||
| - .を取り除く | ||
| - localNameとdomainNameを連結する | ||
| - 正規表現一発で書けるのかもしれないが、加工の条件が後で増えたり減ったりしそうなので条件毎に分けて書きたい気持ちになる | ||
| - 問題の制約上必ず「@」が1つだけ含まれるが、未検証でインデックスアクセスするのが不安で一応ガードするようにした | ||
| ## 解答 | ||
|
|
||
| ```java | ||
| class Solution { | ||
| public int numUniqueEmails(String[] emails) { | ||
| Set<String> uniqueEmails = new HashSet<>(); | ||
| // Stream APIで書くとこんな感じ | ||
| // uniqueEmails.addAll( | ||
| // Arrays.stream(emails) | ||
| // .map(e -> canonicalizeEmail(e)).toList() | ||
| // ); | ||
| for (String email : emails) { | ||
| String canonicalizedEmail = canonicalizeEmail(email); | ||
| if (canonicalizedEmail != null) { | ||
| uniqueEmails.add(canonicalizedEmail); | ||
| } | ||
| } | ||
| return uniqueEmails.size(); | ||
| } | ||
|
|
||
| private String canonicalizeEmail(String email) { | ||
| if (!email.contains("@") || email.indexOf("@") != email.lastIndexOf("@")) { | ||
| // あるいはExceptionを投げる? | ||
| return null; | ||
| } | ||
|
|
||
| String[] localNameAndDomainName = email.split("@"); | ||
| String localName = localNameAndDomainName[0]; | ||
| String domainName = localNameAndDomainName[1]; | ||
|
|
||
| // 「+」以下の部分を切り捨てる | ||
| int firstPlusIndex = localName.indexOf("+"); | ||
| if (firstPlusIndex > -1) { | ||
| localName = localName.substring(0, firstPlusIndex); | ||
| } | ||
|
|
||
| // 「.」を取り除く | ||
| localName = localName.replaceAll("\\.", ""); | ||
|
|
||
| return localName + "@" + domainName; | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| # step2 他の方の解答を見る | ||
| - 元の文字列を一文字ずつチェックして、正規化されたアドレスを生成する方法は思い浮かべていなかった | ||
| - step1の入力値チェックは以下のように書いた方が簡潔だった | ||
| - `if (localNameAndDomainName.length != 2) return null;` | ||
| - この「2」は何を表していると一瞬考えないといけない気もするので、現状の方が意図が伝わりやすいか? | ||
| - localNameから「+」以下を切り出すのは以下のように書く方が簡潔 | ||
| - `localName = localName.split("+")[0];` | ||
| - Javaの文字列テンプレートって書いたことないなと思って確認 | ||
| - JDK21でプレビュー公開されてたがJDK23で取り下げられてた | ||
| - `STR."\{localName}@\{domainName}"`という具合にかけたらしい | ||
| - https://nipafx.dev/inside-java-newscast-71/ | ||
| ## 解答 | ||
| - 元の文字列を一文字ずつチェックして、正規化されたアドレスを生成する | ||
| - https://github.com/seal-azarashi/leetcode/pull/14/files | ||
| ```java | ||
| class Solution { | ||
| public int numUniqueEmails(String[] emails) { | ||
| Set<String> uniqueEmails = new HashSet<>(); | ||
| for (String email : emails) { | ||
| String canonicalizedEmail = canonicalizeEmail(email); | ||
| if (canonicalizedEmail != null) { | ||
| uniqueEmails.add(canonicalizedEmail); | ||
| } | ||
| } | ||
| return uniqueEmails.size(); | ||
| } | ||
|
|
||
| private String canonicalizeEmail(String email) { | ||
| String[] localNameAndDomainName = email.split("@"); | ||
| // localNameとDomainNameの2つに分割できない場合 | ||
| if (localNameAndDomainName.length != 2) { | ||
| return null; | ||
| } | ||
| String localName = localNameAndDomainName[0]; | ||
| String domainName = localNameAndDomainName[1]; | ||
|
|
||
| StringBuilder canonicalLocalName = new StringBuilder(); | ||
| for (char c : localName.toCharArray()) { | ||
| if (c == '+') { | ||
| break; | ||
| } | ||
| if (c == '.') { | ||
| continue; | ||
| } | ||
| canonicalLocalName.append(c); | ||
| } | ||
|
|
||
| return canonicalLocalName.append("@").append(domainName).toString(); | ||
| } | ||
| } | ||
| ``` | ||
| - 正規表現を使った書き方 | ||
| - Leetcodeってjava.util直下のクラスはimport不要だけど、java.util.regexとかはimport必要なのか | ||
| - https://github.com/nanae772/leetcode-arai60/pull/15/files | ||
| ```java | ||
| import java.util.regex.Matcher; | ||
| import java.util.regex.Pattern; | ||
|
|
||
| class Solution { | ||
| public int numUniqueEmails(String[] emails) { | ||
| Set<String> uniqueEmails = new HashSet<>(); | ||
| for (String email : emails) { | ||
| String canonicalizedEmail = canonicalizeEmail(email); | ||
| if (canonicalizedEmail != null) { | ||
| uniqueEmails.add(canonicalizedEmail); | ||
| } | ||
| } | ||
| return uniqueEmails.size(); | ||
| } | ||
|
|
||
| private String canonicalizeEmail(String email) { | ||
| // 「ローカル名」「ローカル名の+以下の部分」「ドメイン名」の3領域をマッチ | ||
| Pattern emailPattern = Pattern.compile("^([a-z\\.]+)(\\+[a-z\\.]*)?@([a-z\\.\\+]+)$"); | ||
|
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. この正規表現は、読むのにやや認知負荷がかかるように思いました。 step3 のように、シンプルな Java のコードで書いたほうが、読み手にとって読みやすいかもしれません。
Owner
Author
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. 私もそのように思います。 |
||
| Matcher matcher = emailPattern.matcher(email); | ||
| if (!matcher.matches()) { | ||
| return null; | ||
| } | ||
| String localName = matcher.group(1); | ||
| String domainName = matcher.group(3); | ||
| localName = localName.replaceAll("\\.", ""); | ||
| return localName + "@" + domainName; | ||
| } | ||
| } | ||
| ``` | ||
| # step3 3回ミスなく書く | ||
| ## 解答 | ||
| - Step1と同じ書き方で。面倒になってvalidationは書かず | ||
| ```java | ||
| class Solution { | ||
| public int numUniqueEmails(String[] emails) { | ||
| Set<String> uniqueEmails = new HashSet<>(); | ||
| for (String email : emails) { | ||
| uniqueEmails.add(canonicalizeEmail(email)); | ||
| } | ||
| return uniqueEmails.size(); | ||
| } | ||
|
|
||
| private String canonicalizeEmail(String email) { | ||
| String[] localNameAndDomainName = email.split("@"); | ||
| String localName = localNameAndDomainName[0]; | ||
| String domainName = localNameAndDomainName[1]; | ||
|
|
||
| localName = localName.split("\\+")[0]; | ||
| localName = localName.replaceAll("\\.", ""); | ||
| return localName + "@" + domainName; | ||
| } | ||
| } | ||
| ``` | ||
|
Comment on lines
+141
to
+161
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. 読みやすいです |
||
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.
このあたり「@が0個または2個以上」、つまり@がちょうど1個だということを表しているのだと思いますがemailの@の個数を直接数えられればいいのかなと思いました。
ただ、Javaだとそんなに簡潔にやる方法が無いっぽいですね…一応GPTに聞いてみた感じだと以下のような方法が比較的分かりやすいかなと感じました。
javaの文字コード周りがあまり分かっておらずASCII以外の文字が来た時に上手くいくかはちょっと分かってないのですがご参考になれば幸いです。
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.
ご提案いただいたコードの方が'@'が1つしか含まれていないことをチェックしていると分かりやすいですね!
絵文字とかサロゲートペアを利用する文字以外なら、この実装で大丈夫です。
https://docs.oracle.com/javase/jp/8/docs/api/java/lang/Character.html