Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
320 changes: 320 additions & 0 deletions 05_Graph,BFS,DFS/200_Number_of_Islands.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,320 @@
<問題>
https://leetcode.com/problems/number-of-islands/description/

# step1

5分程度答えを見ずに考えて、手が止まるまでやってみる。
何も思いつかなければ、答えを見て解く。
ただし、コードを書くときは答えを見ないこと。
正解したら一旦OK。
思考過程もメモしてみる。

```c++
class Solution {
private:
int row_size,column_size;
Copy link
Copy Markdown

@austyhooong austyhooong Nov 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

私個人の意見ですが、class memberとlocal variableを区別しやすくするために、class memberには "_"を付けるのが良いかと思います。

ex:
int row_size_, column_size_
or
int _row_size, _column_size

因みに_を先につけると、IDEで_を打つだけで変数を見つけてくれるメリットがあります!

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://google.github.io/styleguide/cppguide.html#Variable_Names

Data members of classes, both static and non-static, are named like ordinary nonmember variables, but with a trailing underscore.

Google Style Guide もそれやってますね。これも趣味の範囲です。(自分一人ならば趣味の問題だが、チームで開発するときには周りに合わせるよ、という態度を取れれば良いということです。)

int num_island=0;
vector<vector<char>> recognized_island; //既に島として認識されている点を1とする。
Comment on lines +15 to +17
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ここ、クラスメンバーにしておくと、numIslands を複数のスレッドから呼んだときにうまく動かなくなります。
スレッドセーフでない、などといいます。スレッドセーフでないときにはコメントが欲しい気持ちはありますね。

HTML Parser とかだったら、HTML 一つにつきインスタンス一つという設計も自然でしょうが、うーん、この程度の機能でスレッドセーフでないのか、という感覚はあります。
https://discord.com/channels/1084280443945353267/1237649827240742942/1293905886506385438

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@oda thread safetyに疎くて申し訳ないのですが、この場合どのように記述することが正解なのでしょうか?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://source.chromium.org/search?q=%2F%2F.*thread.%3Fsafe%20file:%5C.cc$
好きに書けばいいですが、たとえばこんなのです。

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

メンバ変数ではなくて、関数内のローカル変数にするのが良いのかなと思います。メンバ変数で持ってしまうと、Soluionインスタンスが複数のスレッドから同時に呼ばれるとメンバ変数が共有されているため意図せず書き換わってしまいます。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ありがとうございます。
使ってくれる人がわかりやすいように書こうとはしているつもりですが、
意識できていない部分でした....

step4としては、hayashi-ayさんの案のとおり、ローカル変数にして、
スレッドセーフとなるように修正しました。
(ローカル変数だと、それぞれのスレッドに対して別々に用意されるスタックに
格納されるので、同時に別のスレッドからインスタンスが呼びだれてもスレッド競合は起きないと理解しています。)
https://www.divx.co.jp/media/techblog-220627


//start_pointと同じ島である点を調べ上げる。
void check_same_island (pair<int, int> start_point, vector<vector<char>>& grid) {
queue<pair<int, int>> seen;
vector<pair<int ,int >> connected_point = {{1,0}, {-1,0}, {0,1}, {0,1}};
Copy link
Copy Markdown

@austyhooong austyhooong Nov 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

これは定数なので、以下のようにclass member としてrefactorしても良いのかなと思いました

private:
constexpr static int _connected_points[4][2] {
{0, 1}, {0, -1}, {1, 0}, {-1, 0}
};

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ありがとうございます。
vectorにconstexprを使おうとしたのですが、動的配列なので、ダメなのですね...
https://stackoverflow.com/questions/33241909/cannot-create-constexpr-stdvector

vectorに対してconstは使えるとのことなので、constで定数化してみました。
const vector<pair<int ,int >> deltas = {{1,0}, {-1,0}, {0,1}, {0,-1}};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

よく見ると、{0,1}が2つありますね。

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

connected_point という変数名から、特定の位置を意味しているように感じました。ここでは今見ているポイントからの移動方向ということかと思うので、directions とかがわかりやすそうです。また単数形だと配列であることがわかりにくそうです。


seen.push(start_point);
while (!seen.empty()) {
int x_connected, y_connected;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

x, y を使うと座標軸と混ざってややこしいので、個人的には2次元配列では避けるようにしています。

tarinaihitori/leetcode#17 (comment)

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ありがとうございます。
一つ前のレビューでいただいた指摘も含めて、
step4では変数名がわかりやすいように修正しました。

tie(x_connected, y_connected) = seen.front();
recognized_island[x_connected][y_connected] = '1';
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

実際に訪れてからしかrecognized_islandを書き換えていないので、seenに同一のセルが複数個入るケースがありそうです。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

step4で、queueに訪問予定のセルが入ったタイミングで、書き換えるように変更しました

seen.pop();
for (int i = 0; i < size(connected_point); i++) {
int x,y;
x = x_connected + connected_point[i].first;
y = y_connected + connected_point[i].second;
if( x < 0 || y < 0 || x >= row_size || y >= column_size){
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

「範囲外である」ことを表す場合、「範囲内でない」という書き方のほうが個人的には理解しやすいです。

またこれも個人的な感覚かもしれませんが、x について確認してから y について見るといった順番で、かつ1つの変数に対して2つの条件がある場合は、数直線のように小さい方から順に書く順番がわかりやすく感じます。

Suggested change
if( x < 0 || y < 0 || x >= row_size || y >= column_size){
if (!(0 <= x && x < row_size && 0 <= y && y < column_size)) {

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

step4で否定形の形に書き換えました。
変数の向きについては、私的には変数を左にしたほうが読みやすく感じでおります。
seal-azarashi/leetcode#17 (comment)

continue;
}
if (grid[x][y] == '0' || recognized_island[x][y] == '1'){
continue;
}
seen.push({x,y});
}
}
}
public:
int numIslands(vector<vector<char>>& grid) {
row_size = size(grid);
column_size = size(grid[0]);
recognized_island.assign(row_size, vector<char>(column_size, 0));
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0 か '1' が入っているということに違和感ですね。
1 がきっと入ったら混乱しますね。
bool のほうが間違えないのでは。

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ちなみに、'1'は49ですね。
https://www.asciitable.com/

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oda さんと同じ感想を持ちました。bool がよさそうです

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ありがとうございます。
step4でboolに修正しました。
適切な型を使うことを心がけます。


for (int i = 0; i < row_size; i++) {
for (int j = 0; j < column_size; j++){
if (grid[i][j] == '0' || recognized_island[i][j] == '1') {
continue;
}
check_same_island ({i,j}, grid);
++num_island;
}
}
return num_island;
}
};
```


【考えたこと】
- (i,j)からスタートして、幅優先探索で縦横繋がっている点を追っていく。
- (i,j)をfor文で回す。
- 通った点をわかるようにするテーブルを用意した方がよさそう。
これにより一度通った点かどうかがわかり、(i,j)スタートで幅優先探索する必要があるかわかる。
→最初にどういう変数を作ると良いかや、必要な処理を言語化してからコーディングを始めるとよさそうな気がする。


- アルゴリズムは頭なのかでどうやって実施したらいいかわかっていても、コーディングに落とすのが難しく感じている。
- vectorを動的に作成する必要あり.配列の初期値を作る方法を調べるのに時間がかかること多い。
https://qiita.com/alchemist/items/6cd2a86db7377ad8d236

# step2
他の方が描いたコードを見て、参考にしてコードを書き直してみる。
参考にしたコードのリンクは貼っておく。
読みやすいことを意識する。
他の解法も考えみる。

Nがノード数(ここではgridの数)、Mがエッジ数(端を除いて4N)
計算量:O(N)


```c++
class Solution {
private:
int row_size,column_size;
int num_island=0;
vector<vector<char>> recognized_island; //既に島として認識されている点を1とする。

//start_pointと同じ島である点を調べ上げる。
void check_same_island (pair<int, int> start_point, vector<vector<char>>& grid) {
queue<pair<int, int>> seen;
vector<pair<int ,int >> connected_point = {{1,0}, {-1,0}, {0,1}, {0,1}};

seen.push(start_point);
while (!seen.empty()) {
int x_connected, y_connected;
tie(x_connected, y_connected) = seen.front();
recognized_island[x_connected][y_connected] = '1';
seen.pop();
for (int i = 0; i < size(connected_point); i++) {
int x,y;
x = x_connected + connected_point[i].first;
y = y_connected + connected_point[i].second;
if( x < 0 || y < 0 || x >= row_size || y >= column_size){
continue;
}
if (grid[x][y] == '0' || recognized_island[x][y] == '1'){
continue;
}
seen.push({x,y});
}
}
}
public:
int numIslands(vector<vector<char>>& grid) {
row_size = size(grid);
column_size = size(grid[0]);
recognized_island.assign(row_size, vector<char>(column_size, 0));

for (int i = 0; i < row_size; i++) {
for (int j = 0; j < column_size; j++){
if (grid[i][j] == '0' || recognized_island[i][j] == '1') {
continue;
}
check_same_island ({i,j}, grid);
++num_island;
}
}
return num_island;
}
};
```

dscordやネットなどを調べてみましたが、step1から変更点はありません。


【以前に解かれた方のコメントから学んだこと。】

参照:https://github.com/hroc135/leetcode/pull/17#pullrequestreview-2305316186

- 参照透過性:関数を呼び出す位置によって、挙動が変わらないこと。
https://web.sfc.keio.ac.jp/~hattori/prog-theory/ja/functional.html
- 関数名は動詞にするべき。
- 変数名の英語は省略しない。

参照:https://github.com/seal-azarashi/leetcode/pull/17#pullrequestreview-2276239814

> 不等号の式の左右の方向は、比較されるほうを左に持ってくる派と、数直線上に一直線上に並べる派がいるように思います。実際の現場においては、チームのやり方に合わせることをお勧めします。

>また、一般に、変数には肯定的な意味合いを持たせ、式の中で ! を使って否定したほうが読みやすくなると思います。今回の場合とは違うのですが、否定的な意味合いの変数を ! を使って否定すると、二重否定による肯定になり、認知負荷が上がり、読みにくく感じます。

>dfs という名前は、内部実装の分類で名前をつけているわけですが、関数の呼び出し元を読んだ人は、その内部実装を知りたいと思うことは考えにくいのです。
電源コンセントがあったら、100V 15A なのか 200V 20A なのかがまず知りたくて、石炭なのか水力なのかだけ書いてあってもそこじゃないでしょう。
だいたいの場合、関数名は中身を見なくてもだいたい何をしているか分かるようにして、変数名は何に使うかが分かればいいのです。上から読んでいって明らかで、そして、すぐに忘れて良い場合には一文字でもよいです。たとえば、0からある配列の長さまで回していたら添字だろうと推測できますね。

- ヒープ領域
聞いたことはあるが、あまり詳しくないので調べてみた。
プログラムの中で、動的にメモリを確保したり、削除したりできる。
ポインタを使ってアドレスを管理するときにはこの領域を使う。
https://ja.wikipedia.org/wiki/%E3%83%92%E3%83%BC%E3%83%97%E9%A0%98%E5%9F%9F

- スタック領域
ヒープ領域を調べているとこちらも出てきた。
関数を実行したり、ローカル変数を定義するときに使う。
https://uquest.tktk.co.jp/embedded/learning/lecture07-1.html



参照:https://github.com/thonda28/leetcode/pull/15

- 再帰でも実装できる。


参照:https://github.com/TORUS0818/leetcode/pull/19

- 深さ優先探索でも実装しておきたい。
- union find木について名前だけ聞いたことがあるレベルなので、調べてみました。
- Discord
https://discord.com/channels/1084280443945353267/1183683738635346001/1197738650998415500
- https://note.nkmk.me/python-union-find/
- Union:iとjの大親分を同じにする。find:iの一番の大親分を見つける。
- この問題なら、(i,j)に対して上下左右が陸なら、上下左右の親を(i,j)にするという操作を全i,jに対して行い、最後(i,j)の大親分を全て探索して、大親分の数を数える。

参考:
https://github.com/fhiyo/leetcode/pull/20/files




# step3

今度は、時間を測りながら、もう一回、書きましょう。書いてアクセプトされたら文字消してもう一回書く。これを10分以内に一回もエラーを出さずに書ける状態になるまで続ける。3回続けてそれができたらその問題はOK。

実施しました。


```c++
class Solution {
private:
int row_size,column_size;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a space after the comma
int row_size, column_size;

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

丁寧にコードを見て頂きありがとうございます。
step4で修正しました。
以下を少しづつ見ていけたらと思っています。
https://google.github.io/styleguide/cppguide.html

Copy link
Copy Markdown

@liquo-rice liquo-rice Nov 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a trailing underscore to differentiate between member and local variables
e.g. row_size_

int num_island=0;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add spaces around the equal sign
int num_island = 0;

vector<vector<char>> recognized_island; //既に島として認識されている点を1とする。
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

vector<vector<bool>> visited_


//start_pointと同じ島である点を調べ上げる。
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indent and align the comment to the function below
Add a space after "//"

    // start_pointと同じ島である点を調べ上げる。
    void check_same_island (pair<int, int> start_point, vector<vector<char>>& grid) {

void check_same_island (pair<int, int> start_point, vector<vector<char>>& grid) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const vector<vector<char>>& grid

queue<pair<int, int>> seen;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An unclear variable name
suggestion: cells_to_visit

vector<pair<int ,int >> connected_point = {{1,0}, {-1,0}, {0,1}, {0,1}};
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix spacing and don't use 全角スペース
vector<pair<int, int>> connected_point = {{1, 0}, {-1, 0}, {0, 1}, {0, 1}};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An unclear variable name
suggestion: deltas


seen.push(start_point);
while (!seen.empty()) {
int x_connected, y_connected;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unclear intention of _connected in the variable names
int x, y;

tie(x_connected, y_connected) = seen.front();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

auto [x_connected, y_connected] = seen.front();

recognized_island[x_connected][y_connected] = '1';
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1とだけみてもなんのことなのか分からないので、定数化してあげると良いと思います。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ありがとうございます。
こちらの変数については、visited_islandという変数名に変えて、
bool型に変えることで、意味がわかるようにしてみました。

seen.pop();
Copy link
Copy Markdown

@liquo-rice liquo-rice Nov 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move pop() right after the line calling seen.front()

for (int i = 0; i < size(connected_point); i++) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix spacing and don't use 全角スペース
for (int i = 0; i < size(connected_point); i++) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

optional: Prefer to use the prefix increment operator
++i

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use the member size() method
connected_point.size();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for (auto [dx, dy] : deltas) {

int x,y;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a space after the comma
int x, y

x = x_connected + connected_point[i].first;
y = y_connected + connected_point[i].second;
if( x < 0 || y < 0 || x >= row_size || y >= column_size){
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix spacing

if(x < 0 || y < 0 || x >= row_size || y >= column_size) {

continue;
}
if (grid[x][y] == '0' || recognized_island[x][y] == '1'){
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix spacing

if (grid[x][y] == '0' || recognized_island[x][y] == '1') {

continue;
}
seen.push({x,y});
}
}
}
public:
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add an empty line after the function

    }

public:

int numIslands(vector<vector<char>>& grid) {
row_size = size(grid);
column_size = size(grid[0]);
recognized_island.assign(row_size, vector<char>(column_size, 0));

for (int i = 0; i < row_size; i++) {
for (int j = 0; j < column_size; j++){
if (grid[i][j] == '0' || recognized_island[i][j] == '1') {
continue;
}
check_same_island ({i,j}, grid);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix spacing

check_same_island({i, j}, grid);

++num_island;
}
}
return num_island;
}
};
```

# step4
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

スペースの空け方がまだところどころおかしいです。

レビューを受けてコードの修正。

```c++
class Solution {
private:
int _row_size, _column_size;
int _num_island = 0;
Comment on lines +260 to +261
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

これらの変数がオブジェクト内で共有されているのでまだスレッドセーフではないです。

また、numIslands()が複数回呼ばれるとどうなりますか?

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ありがとうございます。

変な質問かもしれず申し訳ないのですが、
メンバ関数のなかで、メンバ変数にアクセスするとスレッドセーフな関数にはならないと思うのですが、
そうなるとメンバ変数の利点は何なのでしょうか...

私としては、引数にせずに、クラスの様々なメンバ関数からアクセスできるから、コードが書きやすくなると思っていました。

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

例えば、複数回の関数呼び出しに渡って状態を保存したいなら必要です。スレッドセーフにするには、ロックを使う方法などがあります。https://github.com/haniwachann/leetcode/pull/3/files/a799a8e7598e51d24022fd17ca1eeee8ad11406e#r1841490546

ここでは、int _row_size, _column_size;gridから取り出せるので、私ならメンバーにしません。_num_islandはそもそもnumIslands()内でしか使われていないです。


// start_pointと同じ島である点を調べ上げる。
void check_same_island (pair<int, int> start_point, const vector<vector<char>>& grid,
vector<vector<bool>> & visited_island ) {
queue<pair<int, int>> cells_to_visit;
const vector<pair<int ,int >> deltas = {{1,0}, {-1,0}, {0,1}, {0,-1}};
cells_to_visit.push(start_point);
visited_island[start_point.first][start_point.second] = 1;
while (!cells_to_visit.empty()) {
auto [row_current, col_current] = cells_to_visit.front();
cells_to_visit.pop();
for (auto [delta_row, delta_col] : deltas) {
int row, col;
row = row_current + delta_row;
col = col_current + delta_col;
if( ! (row >= 0 && row < _row_size && col < _column_size && col >= 0 ) ){
continue;
}
if (grid[row][col] == '0' || visited_island[row][col] == 1) {
continue;
}
cells_to_visit.push({row, col});
visited_island[row][col] = 1;
}
}
}

public:
int numIslands(vector<vector<char>>& grid) {
vector<vector<bool>> visited_island; // 既に島として認識されている点を1とする。
_row_size = grid.size();
_column_size = grid[0].size();
visited_island.assign(_row_size, vector<bool>(_column_size, 0));
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0, 1ではなく、true/falseを使ってください。


for (int i = 0; i < _row_size; ++i) {
for (int j = 0; j < _column_size; ++j){
if (grid[i][j] == '0' || visited_island[i][j] == 1) {
continue;
}
check_same_island ({i, j}, grid, visited_island);
++_num_island;
}
}
return _num_island;
}
};
```

- 定数の宣言に関して
https://ttsuki.github.io/styleguide/cppguide.ja.html#Use_of_constexpr

https://learn.microsoft.com/ja-jp/cpp/cpp/constexpr-cpp?view=msvc-170

https://rinatz.github.io/cpp-book/ch07-05-constructors/

https://stackoverflow.com/questions/27065617/const-vector-implies-const-elements

- スレッドセーフについて
https://www.divx.co.jp/media/techblog-220627