Create WordLadder.md#22
Conversation
|
|
||
| #### 2周目の宿題 | ||
| - ダイクストラを適用する(BFSとの違いを考えながら) | ||
| - pythonのジェネレータやyieldを理解する |
There was a problem hiding this comment.
まあ、このへんはオプショナルかもしれません。
C++20 から coroutine が、C++23 から Generator があるらしいです。
https://discord.com/channels/1084280443945353267/1247673286503039020/1263415894030290945
There was a problem hiding this comment.
ありがとうございます。合わせてこちらもとりあえず見てみようと思います
| public: | ||
| int ladderLength(std::string beginWord, string endWord, | ||
| const std::vector<std::string> &wordList) { | ||
| std::set<std::string> available_words(wordList.begin(), wordList.end()); |
There was a problem hiding this comment.
以下のようにすることでcopyが免れます (string ownership は元のwordList)
| std::set<std::string> available_words(wordList.begin(), wordList.end()); | |
| std::set<std::string_view> available_words(wordList.begin(), wordList.end()); |
There was a problem hiding this comment.
ありがとうございます。
string_viewを使用することで、setの各要素は、実際には元のwordListを参照するようにできるのですね。
ただ、元のwordListの要素が消去されたりする場合は、注意が必要ということですね
また、string_viewの特徴として、所有権は参照元が持つことを理解しました。
| std::set<std::string> seen_words; | ||
| seen_words.insert(beginWord); | ||
| std::queue<std::pair<std::string, int>> word_to_visit; | ||
| word_to_visit.push({beginWord, 1}); |
There was a problem hiding this comment.
以下 word_to_visit.pushも同じく
| word_to_visit.push({beginWord, 1}); | |
| word_to_visit.emplace(beginWord, 1); |
There was a problem hiding this comment.
ありがとうございます、stringはオブジェクトのサイズが大きくなるため、emplaceにすべきでした。
| } | ||
|
|
||
| private: | ||
| bool is_adjacent_word(const std::string& word1, const std::string& word2) { |
There was a problem hiding this comment.
| bool is_adjacent_word(const std::string& word1, const std::string& word2) { | |
| bool is_adjacent_word(const std::string& word1, const std::string& word2) const { |
There was a problem hiding this comment.
@austyhooong
ありがとうございます。こちらは、「メンバ変数にアクセスしないような関数でも、メンバ変数を変更しないことを明示するためにconstをつけるべき」という意味合いでしょうか?
There was a problem hiding this comment.
はい、またもし class instance 自身がconst objectの場合 non const member function は呼べなくなります。そのためnon modifying member functionをconstにすることは無難かと思います
There was a problem hiding this comment.
class A{
public:
A(int x) : x_(x){}
int f() { //not int f() const {}
return x_;
}
private:
int x_;
};
int main() {
A a(5);
cout<<a.f()<<endl; //5
const A b(5);
cout<<b.f()<<endl; //error
}ありがとうございます。こちらは知らず、大変参考になります。
実験して、constがついたオブジェクトだとcon constな関数を呼べないことを確認しました
| int diff_count = 0; | ||
| for (int i = 0; i < word1.size(); i++) { | ||
| if (word1[i] != word2[i]) { | ||
| diff_count ++; |
There was a problem hiding this comment.
| diff_count ++; | |
| ++diff_count; |
There was a problem hiding this comment.
@austyhooong ありがとうございます。
google style guideにもそのように記載がありました
https://google.github.io/styleguide/cppguide.html#Preincrement_and_Predecrement
Use the prefix form (++i) of the increment and decrement operators unless you need postfix semantics.
There was a problem hiding this comment.
インクリメントを前置するか後置するかで、Google が前置を好むのは理屈として複雑です。
int のようなものの場合、前置でも後置でも特に差はありません。しかし、イテレーターのようなクラスである場合は、後置はコピーを作らなくてはいけません。最適化によって消えるかもしれませんが消える保証はありません。そこでパフォーマンスが重要でクラスの場合には前置のほうがよい場合があります。
そこまで考えたうえで、そうでない場合に後置のほうがいいことはあまりないので、特段の事情がない限りは前置で統一しましょうというルールになっています。
ここまでの理屈を前提にしたうえで、私が、ここまでしつこく書いているのは、「前置を好む」は常識ではないにしても「上の理屈が通じる」くらいが常識なので、「過程を飛ばして結論だけ覚えていると常識がない」と見えるからです。逆に「結論を知らなくても過程が通じればよほど常識がある」と判断されるでしょう。
There was a problem hiding this comment.
ありがとうございます。
仰る通り、「とりあえず結論だけ覚えておこう」という常識からかけ離れた態度でした。反省します。
今後は、「〇〇の方が良い」と自分で結論付けるなら、その理由を調べたり聞いたりして理解した上でにする、もしその場で理解できない場合は、「〇〇の方が良いらしいが、自分は調べてもはっきりとは分からなかった。」というスタンスを取るように気をつけたいと思います。
There was a problem hiding this comment.
そうですね。分からないといえるのは大事です。
https://en.cppreference.com/w/cpp/iterator/iterator
iterator& operator++() { num = TO >= FROM ? num + 1: num - 1; return *this; }
iterator operator++(int) { iterator retval = *this; ++(*this); return retval; }典型的な前置と後置のインクリメントは、このようになっています。前置はリファレンスが返るのに対して、後置はコピーが返ります。
There was a problem hiding this comment.
ありがとうございます。
後置はインクリメントされる前を返さないといけないため、インクリメント前のものをコピーをしており、前置はインクリメントされた後を返すので、自身への参照を返していると理解しました。
| word_list.insert(beginWord); | ||
| for (const auto& original_word : word_list) { | ||
| for (int index = 0; index < original_word.size(); index++) { | ||
| for (int diff = 0; diff < 26; diff++) { |
There was a problem hiding this comment.
for (chat ch = 'a'; ch <= 'z'; ++ch) { で回したほうが素直だと思いました。
STEP2 のように kLowerCase で回すのもよいと思いました。
なお、英小文字アルファベットが文字コード上で連続しているかどうかについて、過去のレビューコメントを調べることをお勧めいたします。 EBCDIC あたりで検索してみてください。
There was a problem hiding this comment.
ありがとうございます
philip82148/leetcode-swejp#5 (comment)
こちらのコメントにあるのを確認しました
https://ja.wikipedia.org/wiki/EBCDIC
IBM系のコンピュータで利用されている文字コードと理解しました。
環境によって文字コードがいろいろあるということを覚えておこうと思います
| std::vector<std::string> wordList) { | ||
| const size_t word_length = beginWord.size(); | ||
| wordList.push_back(beginWord); | ||
| std::map<std::string, std::vector<std::pair<std::string, std::string>>> |
There was a problem hiding this comment.
key として、 "abc?efg" のような文字列を使ってもよいと思います。
There was a problem hiding this comment.
ありがとうございます、分離する切れ目部分を、共通の文字でおくことで、タプルに分割する必要がなくなると理解しました。このような発想はなく大変参考になります。
以下のように実際に変更可能なことを確認しました。
class Solution {
public:
int ladderLength(std::string beginWord, std::string endWord,
std::vector<std::string> wordList) {
const size_t word_length = beginWord.size();
wordList.push_back(beginWord);
std::map<std::string, std::vector<std::string>>
word_to_keys;
std::map<std::string, std::vector<std::string>>
key_to_words;
bool is_valid_input = false;
for (const auto& word : wordList) {
if (word == endWord) {
is_valid_input = true;
}
for (int i = 0; i < word_length; i++) {
auto key = word.substr(0, i) + "?" + word.substr(i + 1, word_length - i - 1);
word_to_keys[word].push_back(key);
key_to_words[key].push_back(word);
}
}
if (!is_valid_input) {
return 0;
}
// 以下略
This Problem
Word Ladder
Next Problem
Maximum Depth of Binary Tree