-
Notifications
You must be signed in to change notification settings - Fork 0
125. Valid Palindrome #5
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
8975ba3
da10b19
b318e4d
7cf3621
c4b9879
5cc22f2
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,91 @@ | ||
| # 125. Valid Palindrome | ||
|
|
||
| https://leetcode.com/problems/valid-palindrome/ | ||
|
|
||
| ## Comments | ||
|
|
||
| ### step1 | ||
|
|
||
| * とりあえず最小限のリファレンスで解いてみた。 | ||
| * https://cpprefjp.github.io/reference/cctype/isalnum.html | ||
| * signature が `int isalnum(int ch);` で、int を返すことになっているが、bool じゃないんだ。C の名残? | ||
| * https://cpprefjp.github.io/reference/locale/tolower.html | ||
| * 個人的には、最初のループで alnum でないやつの除去 + 小文字変換をやってから、2 回目のループで palindrome check をしたい。 | ||
| * 除去 + 小文字変換は private な関数に切り出して string_view? を返すとかしたい。 | ||
|
|
||
| ### step2 | ||
|
|
||
| * https://github.com/colorbox/leetcode/pull/7/files | ||
| * `+=` しながら `checker` 文字列を作っている。Python だと文字列コピーが発生する (ただし最適化の実装による) のでやりたくない感じなんだが、C++ だとどうなの? | ||
| * 上記だと `checker` を reverse している。 | ||
| * step1 でのコメントも踏まえて `step2/Solution` はとりあえず書いてみた。関数では文字列コピーして返しているから冗長な感じはする。できればコピーはせずに使いたい気もする (できるのかな?無理そう) | ||
|
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. まず、これは named return value optimization が働いてコピーが自動的に消える可能性があります。
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. std::string to_lower_alnum(std::string& s) {
...
}
bool isPalindrome(string s) {
std::string lower_alnum_string = to_lower_alnum(s);
...のような感じでしょうかね。(N)RVO の話はあまり考慮していませんでした。 もともとここで意図していたのは、 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. void to_lower_alnum(std::string& from, std::string* to);
std::string lower_alnum_string;
to_lower_alnum(s, &lower_alnum_string);こうです。 長い文字列から数文字を消去した文字列を空間をできるだけ再利用して表現したいということですね。 Rope というデータ構造を思い出します。これは、木構造で文字列を管理して編集されていない部分木を共有します。split, concatenate が O(log n) でできます。 一般的に連続したメモリーに対しての処理は速いので普通はコピーしたほうがいいでしょうが、テキストの編集は頻繁に行われてきたのでよく研究されています。 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. 概ね連続したメモリーのコピーは 1 clock で4-8バイトくらいできるのではないかと思います。 結局、切り替えることによってどれくらい実際に速くなるかを見積もって、別のライブラリーを使う必要がでてくるデメリットなどと勘案するという比較衡量をして欲しいですね。
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. ありがとうございます! こちらでもコメントいただいている箇所ですね。先に書き込み先を用意して関数にそこに書き込んでもらう、というのが馴染みないですが、慣れていきたいと思います。
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.
なるほど…このあたりの数字感覚がなかったのですが、どのようなロジックで推論されるものでしょうか。 追記: 後のコメントから辿って多少理解しました。32 or 64 ビット CPU の想定から 4-8 バイトと見ているんですね (ただのコピーなので機械語的にも 1 命令で、特にオーバーヘッドがない処理のはずなので)。
今回であれば 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.
Google のスタイルガイドもともとこうだったんですが、最近はしないようになっているみたいです。NRVO が効く前提なんでしょうか。 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.
Jeff Dean の表(2012年)は Read 1 MB sequentially from memory 250 us としていました。 なんていうか、見積もりは色々な傍証から「こんなものだよな」とやっているのでそんなにロジカルなものではないです。機械語でどうなりそうかを考えてアーキテクチャの知識と照らし合わせています。 |
||
| * https://cpprefjp.github.io/reference/string_view/basic_string_view.html | ||
| * > 文字配列の参照する先頭文字へのポインタと、文字数の2つをメンバ変数として持つ。 | ||
| * とあるので今回のように非連続な文字列になる場合は view 使えないのかな。それなら vector に push して一番最後で string に変換? (Python の append -> join と同じ) | ||
| * 追記: そもそも `std::string` 自体 `vector<char>` 的なものとして実装されているので、Python のような処理は不要。 | ||
| * https://qiita.com/LdCqh1/items/92f286ceb0ab96dc3c09 | ||
| * https://zenn.dev/reputeless/books/standard-cpp-for-competitive-programming/viewer/string | ||
| * > C++ 標準ライブラリは、任意の文字型 Char に対して、便利な文字列処理を提供するためのクラステンプレート std::basic_string<Char> を定義しています。それを char 型に対して特殊化(std::basic_string<char>)したものが std::string です。 | ||
| * `Char` と `char` って C++ だと違うものな?(TODO) | ||
|
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. ここでの Char は一般的に Char と書けば通じるものではありません。 |
||
| * 軽く検索してみたがよくわからん | ||
| * `union` (TODO) とかあるんだ。。 | ||
| * `size_t` という型がある | ||
|
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. sizeof の返す型。vector size もこの型のことが多いです。 |
||
| * https://cpprefjp.github.io/reference/cstddef/size_t.html | ||
| * https://rinatz.github.io/cpp-book/ch07-12-unions/ | ||
| * 共用体というやつか。聞いたことある。 | ||
| * > 構造体の大きさがメンバーのすべての型の大きさの総和にパディングなどを足したものであったのに対して、 共用体ではメンバーの型の大きさの最大値にパディングなどを足したものとなります。 結果としてメモリーを節約することができるので、複数の型のどれかを格納したいという場合にはよく用いられます。 | ||
|
|
||
| ```cpp | ||
| // Cited from https://zenn.dev/reputeless/books/standard-cpp-for-competitive-programming/viewer/string#1.3-std%3A%3Astring-%E3%81%AE%E5%86%85%E9%83%A8%E6%A7%8B%E9%80%A0-(3) | ||
| struct string | ||
| { | ||
| char* m_ptr; // 確保したメモリの先頭を指すポインタ | ||
| size_t m_size; // 格納している文字列の長さ | ||
|
|
||
| static constexpr size_t LocalCapacity = 15; // 動的確保不要で格納できる文字列の最大の長さ | ||
|
|
||
| union | ||
| { | ||
| char m_local_buffer[LocalCapacity + 1]; // 文字列の長さが LocalCapacity 以下の場合、動的確保の代わりに使う配列 | ||
| size_t m_allocated_capacity; // 動的確保した配列に格納できる文字列の最大の長さ | ||
| } m_storage; | ||
| }; | ||
| ``` | ||
|
|
||
| * contd. | ||
| * > 2.2 文字列リテラルは std::string 型ではない | ||
| * まじか。ややこしい。 | ||
| * > 3.3 別の std::string から構築する | ||
| * C++ だとこれはコピーになるのか。値渡しをしているということだと思うけど、`std::string` は mutable なのかな。 | ||
|
|
||
| ```cpp | ||
| std::string s = "abc"; | ||
| std::string t = s; // s の値をコピーする | ||
| ``` | ||
|
|
||
| * contd. | ||
| * > std::string 型のデフォルトコンストラクタは、要素数が 0 である空の std::string を構築します。 | ||
| * なるほど、`int` だと宣言だけした場合未定義動作になると思うけど、`std::string` は定義されているのか。 | ||
| * > C++20 から、範囲ベースの for 文に初期化式を追加できるようになりました。 | ||
| * `for (int i = 0; const auto& ch : s)` | ||
| * へーこんなのもできるんだ | ||
| * イテレータ、逆イテレータ | ||
| * `std::views::reverse` | ||
| * `std::string_view` とは異なる | ||
| * パイプを使うんだ。この文法知らん。アダプタ?(TODO)。まあシェルの模倣だろう。 | ||
| * https://onihusube.hatenablog.com/entry/2022/04/24/010041 | ||
| * 実際どう実装されているのかはよくわからん。 | ||
| * https://cpprefjp.github.io/reference/ranges/drop_view.html | ||
| * drop が drop する個数と view を引数に取る関数で、view の方はパイプで渡される、という意味合いかと思ったけど合ってるのかな。違う気がする。 | ||
| * 上記 (https://onihusube.hatenablog.com/entry/2022/04/24/010041) の後半で解説されている気もするが、ちょっと時間かかりそうなので一旦先へ進む (TODO) | ||
| * > 11. std::string への追加 | ||
| * `char` の配列として実装されているなら末尾への追加は O(1) だろうと思っていたがやはりそうだった。ただ `push_back` と `+=` の違いについては特に言及がなかった (もちろん `push_back` は `char` を取り、`+=` は string を取るという違いはあるが) | ||
| * https://cpprefjp.github.io/reference/string/basic_string/op_plus_assign.html | ||
| * ここには `append(str) と等価。` と記載がある。 | ||
| * https://cpprefjp.github.io/reference/string/basic_string/append.html | ||
| * いずれにせよ、C++ は末尾に追加する分には Python みたいなことは気にしなくてよいらしい。`push_back` と `+=` の違い (char を末尾に追加する場合) はまだよくわからないが、とりあえず `push_back` を使っておこう。 | ||
|
|
||
| ### step3 | ||
|
|
||
| * `std::char` にしたら怒られた。 | ||
| * 関数に分けるパターンを採用 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| #include <cctype> | ||
|
|
||
| class Solution { | ||
| public: | ||
| bool isPalindrome(string s) { | ||
| int left = 0; | ||
| int right = s.size() - 1; | ||
| while (left < right) { | ||
| if (!std::isalnum(s[left])) { | ||
| ++left; | ||
| continue; | ||
| } | ||
| if (!std::isalnum(s[right])) { | ||
| --right; | ||
| continue; | ||
| } | ||
| char c_left = std::tolower(s[left]); | ||
| char c_right = std::tolower(s[right]); | ||
| if (c_left != c_right) { | ||
| return false; | ||
| } | ||
| ++left; | ||
| --right; | ||
| } | ||
| return true; | ||
| } | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| #include <cctype> | ||
|
|
||
| class Solution { | ||
| private: | ||
| string to_lower_alnum(string s) { | ||
|
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. string を引数に値渡しで渡すと、コピーが発生します。stringをコピーする場合、元の文字列が格納できるサイズのメモリをヒープに確保し、文字列をコピーします。できればこのストは避けたいです。 const string& と const 参照渡ししたほうが良いと思います。
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. ありがとうございます。こちらの oda さんのコメントと合わせて確認しました。 |
||
| string lower_alnum_string = ""; | ||
| for (char c : s) { | ||
| if (std::isalnum(c)) { | ||
| lower_alnum_string.push_back(std::tolower(c)); | ||
| } | ||
| } | ||
| return lower_alnum_string; | ||
| } | ||
|
|
||
| public: | ||
| bool isPalindrome(string s) { | ||
| string lower_alnum_string = to_lower_alnum(s); | ||
| int left = 0; | ||
| int right = lower_alnum_string.size() - 1; | ||
| while (left < right) { | ||
| if (lower_alnum_string[left] != lower_alnum_string[right]) { | ||
| return false; | ||
| } | ||
| ++left; | ||
| --right; | ||
| } | ||
| return true; | ||
| } | ||
| }; | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,27 @@ | ||||||
| class Solution { | ||||||
| private: | ||||||
|
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. Google guideではpublicを先に宣言することがお勧めされております! 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. 一応、プロダクションでは分割コンパイルをするというのを意識しておきましょう。
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. ありがとうございます。このあたりを確認しました。分割コンパイルについての意識はまだ低いのでもう少し C++ に慣れたら、どういうヘッダファイルが想定されるか考えてみようと思います (production のコードでちょくちょく読んでいるので読む方の認識はあるのですが、書くほうがまだ今ひとつ)。
|
||||||
| std::string to_lower_alnum(std::string s) { | ||||||
|
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
それかmodern C++ ではstring_viewを渡すことが多いかと思います。
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. あ、なるほど、string_view はこういうので使えるんですね。 |
||||||
| std::string lower_alnum_string; | ||||||
|
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. もしsのサイズが大きれば、ここは先にcapacityをreserveするのも良いかと思います。 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. reserve の時間見積もりはこのあたりにあります。一行を加える管理コストに足るメリットがあるかという良し悪しを評価するという方向に頭を動かしたかということが大事です。
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. reserve をそもそも知らなかったのですがこれですね。 「計算速度の見積もり」の項は何度か見ているものの、すべて身についている感じはしないので折に触れて確認していきたいと思います。 |
||||||
| for (char c : s) { | ||||||
| if (std::isalnum(c)) { | ||||||
| lower_alnum_string.push_back(std::tolower(c)); | ||||||
| } | ||||||
| } | ||||||
| return lower_alnum_string; | ||||||
| } | ||||||
|
|
||||||
| public: | ||||||
| bool isPalindrome(string s) { | ||||||
| std::string lower_alnum_string = to_lower_alnum(s); | ||||||
| int left = 0; | ||||||
| int right = lower_alnum_string.size() - 1; | ||||||
| while (left < right) { | ||||||
| if (lower_alnum_string[left] != lower_alnum_string[right]) { | ||||||
| return false; | ||||||
| } | ||||||
| ++left; | ||||||
| --right; | ||||||
| } | ||||||
| return true; | ||||||
| } | ||||||
| }; | ||||||
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++ では文字列は mutable です。