Conversation
|
|
||
| public: | ||
| vector<vector<string>> groupAnagrams(vector<string>& strs) { | ||
| unordered_map<string, vector<string>> sorted_to_group; |
There was a problem hiding this comment.
変数名はこれで良さそうですか?
unordered_mapとmapが両方出ていますが、違いは理解していますか?
There was a problem hiding this comment.
@liquo-rice
頂いた資料と下記を読みました。setとmapは赤黒木を使って実装されており、検索、削除、挿入に対する計算量は平均もワーストもO(log n)でunordered_setとunordered_mapはハッシュを使われており平均はO(1)だけどワーストはO(n)とございました。https://chromium.googlesource.com/chromium/src/+/master/base/containers/README.md#Map-and-set-selection
mapとsetを使わない方針
if the number of items will be large or unbounded and elements will be inserted/deleted outside of the containers constructor/destructor - they have O(n) performance on inserts and deletes of individual items.
unordered_setとunordered_mapをデフォルトでは使わない理由
In the common case, query performance is unlikely to be sufficiently higher than std::map to make a difference, insert performance is slightly worse, and the memory overhead is high. This makes sense mostly for large tables where you expect a lot of lookups.
commonでないケースは分からなかった(挿入されるデータ量が無限のもの?)のですが、今回の問題だとデータ量は有限であるのでmapを使うことにしました。
There was a problem hiding this comment.
Hash table, red-black treeがどのようなものかについても調べるとよいと思います。
There was a problem hiding this comment.
std::mapをred_black_treeに置き換えましたか?52bc545では使っていないみたいので、念のため。
There was a problem hiding this comment.
@oda @liquo-rice
for (const auto& [key, word_group] : sorted_to_group)この部分動かすために、iteratorを実装する必要がるとわかって今日一日向き合っていたのですがスクラッチで実装できそうにないです。赤黒木のiteratorを実装するにあたって参考になる資料などございますでしょうか?
There was a problem hiding this comment.
Tree traversalの知識が必要になると思います。こちらの問題で練習してみると良いかもしれません。
https://leetcode.com/problems/binary-search-tree-iterator/description/
There was a problem hiding this comment.
@liquo-rice 練習問題ありがとうございます。arai60をあと5問解けばtree関連に入りますのでそこまで先に進めてから練習問題とtree関連を終わらせてから赤黒木の実装の続きをしようと思います🙇
| string key; | ||
| // chatGPTにて確認 | ||
| for (int count : counts) { | ||
| key += '#' + to_string(count); |
There was a problem hiding this comment.
@liquo-rice
文字列結合を効率化するにはRopes Data Structureを使うという記事を見つけました。一読しただけではRopes Data Structureを理解できなかったので、理解してから組み込んでみます🙇
https://stackoverflow.com/questions/611263/efficient-string-concatenation-in-c
https://www.geeksforgeeks.org/ropes-data-structure-fast-string-concatenation/
There was a problem hiding this comment.
Ropes Data Structureは私も何か知らなかったのですが、ostringstreamを使えば良さそうです。
There was a problem hiding this comment.
@liquo-rice
step6.cppに実装しました。ostringstreamは独習c++などには載っていなかったのですが一般的に使うものでしょうか?
また以下の2つを参照しました。
https://ameblo.jp/nana-2007-july/entry-10098557843.html
https://cplusplus.com/reference/sstream/ostringstream/
There was a problem hiding this comment.
ostreamを使って、stringを作成したいときなどに便利だと思います。
There was a problem hiding this comment.
C++ の文字列は、Python や Java などとは違って、mutable です。このため、+= で追記しても文字列全体が作り直されることはありません。
とはいえ、vector の追記のようにリアロケーションが起きることがあるので、場合によってはそこも高速化したい場合がありえます。
Ropes は、木を使って後で結合する方法です。他、Finger Tree などというデータ構造などもありますね。
string は、C++11 からメモリー上で連続することが保証されるようになりました。
https://stackoverflow.com/questions/11752705/does-stdstring-have-a-null-terminator
There was a problem hiding this comment.
immutable な言語は、String を作るのに何らかの仕組みを使います。
たとえば、Java StringBuilder は、Array を用意して * 2 + 2 にして行く方式。
https://hg.openjdk.org/jdk8/jdk8/jdk/file/687fd7c7986d/src/share/classes/java/lang/AbstractStringBuilder.java
C# は、後ろからの単方向リストです。
https://referencesource.microsoft.com/#mscorlib/system/text/stringbuilder.cs
|
|
||
| vector<vector<string>> group_anagrams; | ||
| for (auto [key, word_group] : sorted_to_group) { | ||
| group_anagrams.push_back(word_group); |
There was a problem hiding this comment.
word_groupが2回コピーされているような気がします。コピーを減らせそうですか?
| for (auto str : strs) { | ||
| string anagram = str; | ||
| sort(anagram.begin(), anagram.end()); | ||
| sorted_to_group[anagram].push_back(str); | ||
| } |
There was a problem hiding this comment.
もう少し簡単な質問として、何回どこで何がコピーがなされていると思っているかを教えて下さい。
There was a problem hiding this comment.
@oda @liquo-rice
コピーの回数ですが、ループごとにstrsからstrに1回、strからanagramに1回それぞれコピーされている認識でした。
正直わかっていないので調べてみます。
There was a problem hiding this comment.
@oda @liquo-rice
調べ方がわからなかったのでchatGPTで調べました。その後関連しそうなドキュメント読んでみました。
vectorのpush_backを使うとコピーを作ってから、挿入するのですね。
https://en.cppreference.com/w/cpp/container/vector/push_back
下記に目を通しましたが、sortでは行われていないと思います。
https://en.cppreference.com/w/cpp/algorithm/sort
なので以下の3回でコピーが発生しているともいます。
・strs からstr
・str からanagram
・push_back
There was a problem hiding this comment.
@liquo-rice @oda
discord上で教えていただいたことをもとに修正したものを上げました。step4.cppとなります。
f55e74e
autoは参照に変更し、push_backはコピーを作らないようにしました。
メモ
vectorにはmoveを渡すことができる
https://en.cppreference.com/w/cpp/container/vector/push_back
There was a problem hiding this comment.
@liquo-rice
分からなかったのでこちら読んでみました。ざっくりと以下のように理解しました🙇
a = b
=> bのコピーを作成してaという変数名でアクセスするようにする
a = move(b)
=>aがメモリを確保し、bの内容をaに記録する
元のbは使えなくなる
https://zenn.dev/mafafa/articles/cba24383d46900
There was a problem hiding this comment.
@kazukiii
std::moveについて、何かコメントがあればお願いいたします。
There was a problem hiding this comment.
@Ryotaro25
そうですね、個人的にはもう少し深く理解しておいた方が他のことに応用が効くと思います。
自分は先日、わからない用語を調べながら以下を読みました。
https://cpprefjp.github.io//lang/cpp11/rvalue_ref_and_move_semantics.html
簡単ですが、自分の理解した内容を書いておきます。
まずstd::moveについてですが、これは単に引数を右辺値参照にキャストするだけで、実際にはなにも移動しません。
型Tにコピー演算とムーブ演算が定義されているとして、
T hoge = 左辺値; -> コピー代入演算子が呼び出される
T hoge = 右辺値; -> ムーブ代入演算子が呼び出されるという感じです。
で、push_backに対しても同じ原理で理解できます。
vector<T> hoge;
hoge.push_back(左辺値); -> コピーコンストラクタが呼び出される
hoge.push_back(右辺値); -> ムーブコンストラクタが呼び出されるという感じです。
このムーブコンストラクタ/代入演算子が実際のムーブ処理を行っていて、
これはクラスの設計者が定義するものなので、一度自分で書いてみると理解が深まるかもしれません。
@liquo-rice
補足等ありましたらお願いできますと幸いです。
There was a problem hiding this comment.
ありがとうございます。
@Ryotaro25
Zennの記事やkazukiiiさんの説明にもあるように、std::moveは、rvalue referenceへのキャストになります。練習として、簡単なstd::vectorのようなクラスを自分で作成して、copy/move constructor/assignmentを定義してみたらいいかと思います。
There was a problem hiding this comment.
@kazukiii @liquo-rice
解説ありがとうございます。一度自分で書いてみようと思います🙇
| class Solution { | ||
| private: | ||
| vector<int> count_alphabet(string s) { | ||
| vector<int> counts(26, 0); |
| @@ -0,0 +1,66 @@ | |||
| class my_unordered_map { | |||
There was a problem hiding this comment.
template <typename K, typename V>でできますか?
バケットの数を決めうちにするのではなく、動的に拡張できますか?
| buckets[index].emplace_back(key, vector<string>()); | ||
| return buckets[index].back().second; | ||
| V& operator[](const K& key) { | ||
| // 占有率0.7を超えたら倍の大きさにする |
There was a problem hiding this comment.
https://en.wikipedia.org/wiki/Hash_table
こちらに最適な占有率についての記述があります。
separate chaining: 1 to 3
open addressing: 0.6 to 0.75
There was a problem hiding this comment.
@liquo-rice
資料ありがとうございます。
なっとくアルゴリズムに0.7とあって(理由は記載なし)そのままにしておりました。
読んでみます🙇
| // メンバー関数をconstにする | ||
| size_t hash(const K& key) const { | ||
| size_t hash_code = 0; | ||
| for (char letter : key) { |
There was a problem hiding this comment.
stringだけしか動かないですね。見落としておりました、方法を探してみます。
問題へのリンク
49. Group Anagrams49. Group Anagrams
問題文(プレミアムの場合)
備考
次に解く問題の予告
https://leetcode.com/problems/intersection-of-two-arrays/
フォルダ構成
LeetCodeの問題ごとにフォルダを作成します。
フォルダ内は、step1.cpp、step2.cpp、step3.cpp, ascii.cppとmemo.mdとなります。
memo.md内に各ステップで感じたことを追記します。