OmniAICreator/Qiita-1.07M huggingface dataset:
17 全探索2 Dif:379 ABC193 C：「AtCoder　凡人が『緑』になるための精選50問詳細解説」サンプル

**この記事は拙著「AtCoder　凡人が『緑』になるための精選50問詳細解説」のサンプルです**
価格：100円
kindle：https://www.amazon.co.jp/dp/B09C3TPQYV
booth(pdf)：https://sano192.booth.pm/items/3179185

前：https://qiita.com/sano192/items/677ec26921f08586dd18
次：https://qiita.com/sano192/items/002198d775b77e04cb40

https://atcoder.jp/contests/abc193/tasks/abc193_c

**【目標】**
・計算量を見積もって全探索が間に合うか判断できるようになる
・setを使えるようになる

**【概要】**
少し工夫が必要だが、基本的には「全部試す」で解ける問題。
全探索で解けるかを考えるときはまずどれくらいの計算量なのかを見積もることが必要だ。この問題で計算量の見積もり方を学ぼう。

**【方針】**
1,2,3,...についてa^bで表せるか判定していく、という方法はうまくいかない。
逆にa^bで表せる数を数え、それをNから引くという方法を考えよう。

aを固定して考えると
a=2
b=2:2^2=4
b=3:2^3=8
b=4:2^4=16
…
a=3
b=2:3^2=9
b=3:3^3=27
b=4:3^4=81
…
というように表せる数を順に作ることができる。
a^bがNを超えるまでbを2,3,4,...と増やしていけばよい。

ではa^bを順に作っていく場合、計算量はどうなるか。
制約は1<=N<=10^10。
a=2のときはb=34、つまり2^34=17179869184>10^10となるからb=2~33まで計算すれば十分。
aの範囲は2<=a<=10^5でよい。なぜならaが10^5を超えた場合、bが最小の場合つまりb=2でも10^10を超えてしまうからだ。

つまり計算が必要な範囲は
a=2~10^5
それぞれのaに対して
b=2~33
となり、
計算量は10^5*32程度で間に合う。
(実際にはaが大きくなるとbの最大値は小さくなるので計算量はもっと少ない)

**【実装】**
入力を受け取る。

```python:
N=int(input())
```

次に「作れる数のセット」を作る。名前はable_num。

```python:
able_num=set()
```

setはリストのようなものだが、リストと違い、重複する値を入れることができない。
たとえばable_numに2^4=16が入っている時に4^2=16を追加しても16が2つ入ることはない。

a^bをaが小さい方から順に作っていく。
・a^b<=Nの場合
able_numに追加。
setはappendではなくaddで要素を追加することに注意。
・そうでない(N<a^b)場合
それ以上大きなbを確認する必要はないので次のaに進む=break。

```python:
for a in range(2,10**5+10):
    for b in range(2,1000):
        if a**b<=N:
            able_num.add(a**b)
        else:
            break
```

「for a in range(2,10**5+1):」でよいのでは？(aの範囲は2~10^5でよいのでは？)
と思った人は正しい。aは最大でも10^5まででよいからだ。
しかしギリギリを狙って書くと何か勘違いがあった時に余計なWA(不正解)がでてしまう。そのためTLE(実行時間制限超過)しない程度にaの探索範囲を広めにとっている。

「for b in range(2,1000):」
についても同様。bの範囲は2~33でよいから
「for b in range(2,34):」
で問題ない。しかし余計なWA(不正解)がでないよう、てきとうに大きめな数=1000まで探索範囲を広げている。

最後に「作れる数」の数をNから引いて出力すれば終わりだ。
setもリスト同様要素の数はlen(セット)で参照できる。

```python:
print(N-len(able_num))
```

**【コード全文】**

```python:
N=int(input())

able_num=set()

for a in range(2,10**5+10):
    for b in range(2,100):
        if a**b<=N:
            able_num.add(a**b)
        else:
            break

print(N-len(able_num))
```

**この記事は拙著「AtCoder　凡人が『緑』になるための精選50問詳細解説」のサンプルです**
価格：100円
kindle：https://www.amazon.co.jp/dp/B09C3TPQYV
booth(pdf)：https://sano192.booth.pm/items/3179185

前：https://qiita.com/sano192/items/677ec26921f08586dd18
次：https://qiita.com/sano192/items/002198d775b77e04cb40
2021-08-01 Godot Engine > ボタン押下時にオブジェクトを追加する (乱数と、シーンの生成・破棄）

### 動作環境

```
Windows 10 Pro v21H1
Godot Engine v3.3.2.stable.official
```

### 概要

- Createボタン押下時にオブジェクトを追加する
    - Buttonを追加とした
- Deleteボタン押下時に先頭のオブジェクトから削除する

### 学習内容

- 乱数
- オブジェクトの生成と破棄 (シーンを使用)

### 準備

追加したいButtonを含むシーンを作り、Button.tscnとして保存しておく。

### 実装

```python:Main.gd
extends GridContainer

var rng = RandomNumberGenerator.new()

var charlist = []

func _ready():
	rng.randomize()

func make_child():
	var scene = load("res://Button.tscn")
	var instance = scene.instance()
	charlist.append(instance)
	add_child(instance)
	var xpos = rng.randi_range(0, 1000)
	var ypos = rng.randi_range(0, 500)
	instance.position = Vector2(xpos, ypos)
	print("%d,%d" % [xpos, ypos])

func _on_Button_pressed():
	make_child()

func _on_Button2_pressed():
	if charlist.size() == 0:
		return
	charlist[0].queue_free()
	charlist.remove(0)
```

### 実行例

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32870/4dca5776-8461-eb91-1322-6e74f9ea6b8c.png)


### 参考

- [How do I delete my node once I'm done with it?](https://godotengine.org/qa/15528/how-do-i-delete-my-node-once-im-done-with-it)
    - queue_free()で消す
- [RandomNumberGenerator](https://docs.godotengine.org/ja/stable/classes/class_randomnumbergenerator.html)
- https://docs.godotengine.org/en/latest/tutorials/scripting/nodes_and_scene_instances.html
    - > Instancing scenes
    - > Scenes are templates from which you can create as many reproductions as you'd like. 
    - 追加したいものは「シーン」として複数生成可能ということが分かった
macOS+zsh環境にpyenvをインストールする

## 環境
- macOS Big Sur (version 11.4)
- zsh 5.8 (x86_64-apple-darwin20.0)

## 手順

https://github.com/pyenv/pyenv を元にインストールを進める。
macOS+zshの場合、以下の節のみ拾い読みすればOK：

- [Homebrew on macOS](https://github.com/pyenv/pyenv#homebrew-on-macos) - Homebrewからpyenvをインストール
  - ↑にも記載があるように、途中から [Basic GitHub Checkout](https://github.com/pyenv/pyenv#basic-github-checkout) の 「2. Configure your shell's environment for Pyenv」 → 「For Zsh:」 の部分のみ実行


### brew install

```
brew update
brew install pyenv
```

### zshの設定

オリジナルの手順では `~/.zprofile` に追記しているが、 `~/.zshrc` にしても問題なく設定は極力こちらに寄せたいのでアレンジ

```
echo 'eval "$(pyenv init --path)"' >> ~/.zshrc
```

### 確認

pyenvがインストールされたことの確認

```
pyenv -v
```

以下のように何らかのバージョンが表示されればOK

```
% pyenv -v
pyenv 2.0.4
```

## pyenvでpythonのバージョンを切り替える

インストール可能なバージョンの一覧を表示

```
pyenv install --list
```

インストール（以降、version `3.9.6` の例で進める）

```
pyenv install 3.9.6
```

バージョン切り替え

```zsh
# そのユーザ全体で切り替える時
pyenv global 3.9.6

# そのフォルダ内のみで切り替える時
pyenv local 3.9.6
```

バージョンが切り替わったことの確認

```
python -V
pyenv versions
```

実行例

```zsh
% python -V
Python 3.9.6

% pyenv versions
  system
* 3.9.6 (set by /Users/user/.pyenv/version)
```
行頭に `*` がついているのが選択されているバージョン、それ以外はインストール済みで利用可能なバージョン。

pyenvによるpythonのバージョン切り替えが効きました！

※うまくいかない時は、途中でターミナルアプリの再起動か `source ~/.zshrc` で再読み込みが必要かも（適当にやっちゃった）


## バージョン切り替えが反映されない時の確認項目

### `which python` で `.pyenv` 配下を指しているか

こんな感じになるはず：

```zsh
user@macbook ~ % which python
/Users/user/.pyenv/shims/python
```

`~/.zshrc` に `eval "$(pyenv init --path)"` と記載できていなかった時にハマりました。

ググって出てくるブログ記事には `$(pyenv init -)` の記載が多かったですが、 `$(pyenv init --path)` が正しいみたいです。

不要な行は削除して、正しく書き直しましょう：

```zsh:~/.zshrc 
eval "$(pyenv init --path)"
```


### 意図せずホームディレクトリに `~/.python-version` が生成されていないか

トラシューでガチャガチャやっている時に `pyenv local ...` が実行されてたりすると、多分あるある…。

お掃除しましょう：

```
rm ~/.python-version
```
挿入順を覚えている連想配列

# これは何？

Ruby の Hash には shift があることの紹介。
と思って調べたら、 Python3 の `dict` にも順序があるのがあったので、それも紹介。

# Ruby の Hash

Ruby の Hash は挿入順を覚えている。
挿入順を利用して古いものから順に取り出せる。
以下のような感じ。

```ruby:ruby
 h={foo:111,bar:22,hoge:3}
h[:bar]=44
h[:baz]=55
s=""
while ! h.empty? do
  s += h.shift.inspect
end
p s #=> "[:foo, 111][:bar, 44][:hoge, 3][:baz, 55]"
```

先頭の値を見る `first` と、先頭の値を撤去する `shift` はあるけど、末尾を扱う `last` と `pop` は無い。

```ruby:ruby
{a:1}.first #=> [:a, 1]
{a:1}.last #=> NoMethodError: undefined method `last'
{a:1}.shift #=> [:a, 1]
{a:1}.pop #=> NoMethodError: undefined method `pop'
```

値の追加がないのはわかるけど、末尾の値が扱えないのは不思議。

あと、`Hash` の `first` は引数ありなし両方あるけど、`shift` は引数なし。

```ruby:ruby
{a:1,b:2}.first(2) #=> => [[:a, 1], [:b, 2]]
{a:1,b:2}.shift(2) #=> ArgumentError: wrong number of arguments (given 1, expected 0)
[1,2].shift(2) #=> [1, 2]
```

これも対応してくれればいいのにと思う。

Hash 内には挿入順が記憶されているのに、 `==` での比較では挿入順が度外視されるので

```ruby:ruby
x={a:1,b:2}
y={b:2,a:1}
p(x==y) #=> true
p([x.first, y.first]) #=> [[:a, 1], [:b, 2]]
```

このように、等値であるオブジェクトに同じメソッドを送っても違う結果になったりする。
歴史的経緯からまあそうするしかないかとも思うけどわかりにくいよね。

`Hash` が挿入順を覚えていて、古い順から取り出せるので、ある種の探索アルゴリズムで役に立つ。
見つかったものを `Hash` に挿入しつつ、先頭から取り出して消費するという流れとか。

# Python3 の dict

この記事を書くにあたって調べて初めて知ったんだけど、Python3 の dict にも挿入順が入っている。

```python:Python3
d = { "foo":111, "bar":22, "hoge":3 }
d["bar"] = 44
d["baz"] = 55
s=""
while d:
  s += repr(d.popitem())

print(s) #=> ('baz', 55)('hoge', 3)('bar', 44)('foo', 111)
```

この `popitem()` は、 [3.7 で「LIFO 順序が保証されるようになりました。」](https://docs.python.org/ja/3/library/stdtypes.html?highlight=dict#dict.popitem) とのこと。そりゃ知らなかったわけだ。

`collections.OrderedDict` の `popItem` には引数があって、末尾から取るか先頭から取るかを選べるんだけど、普通の `dict` の `popItem` には引数がない。必ず末尾から。なぜなのか。しかも ruby と逆。


辞書に入っている先頭要素は

```python:Python3
d = { "foo":111, "bar":22, "hoge":3 }
iter(d.items()).__next__() #=> ('foo', 111)
```

と、`items()` と `iter()` を使えば撤去せずに取得できるけど、もっと簡単な方法がありそうな気がする。

一方、`popitem()` で容易に 撤去＋取得 できる末尾の要素だが、これを撤去せずに取得する方法は

```python:Python3
d = { "foo":111, "bar":22, "hoge":3 }
iter(reversed(d.items())).__next__() #=> ('hoge', 3)
```

と、むしろ面倒になる。これももっと簡単な方法がありそうな気がする。

# まとめ

||ruby|Python3|
|:--|:--|:--|
|先頭を撤去せずに取得|`h.first`|`iter(d.items()).__next__()`|
|先頭を撤去して取得|`h.shift`|表外下記|
|末尾を撤去せずに取得| 不可能だと思う |`iter(reversed(d.items())).__next__()`|
|末尾を撤去して取得| 不可能だと思う |`d.popitem()`|

Python3 の「先頭を撤去して取得」は、

1. `firstkey = iter(d).__next__()` で、先頭のキーを取得
1. `firstval = d[firstkey]` で、先頭の値を取得
1. `del[firstkey]` で、先頭の要素を撤去

という三手で実施可能。
[リモートワーク]「テキストコミュニケーションって難しくない？」

#はじめに
私はほぼ毎日リモートワークで奮闘しているのですが、「テキストで相手に自分の意図を伝えることって結構難しくない？」と最近非常に感じております。
なので先輩に指摘していただいたことをもとに、意識する点についてまとめてみたいと思います。
以下、すべて私の日々の業務であるバグ調査でのお話となります。

##「◯◯調査しました！！調査結果は...」
相手に物事を伝える時に、**結果だけを報告する**のはよくありません。
理由は、このように言われた相手は「調査結果を踏まえた上で、あなたがこれからどのように何をしようとしているのか」が全くわからないからです。
この例からわかることは、**相手の次のアクションを明確にする**伝え方をしなければならないことです。
なので、その原因と自分が考える問題の修正方針を併せて報告した上で、**相手に何をしてもらいたいのか**を必ずはっきりさせましょう。
また、**結果を報告する際に抽象的な言い方をしてはいけません**。
私は最近、記述ミスが原因のバグを報告する際に「単なる記述ミスでした。ご確認よろしくお願いします。」と報告をしてしまいました。
「すぐ修正すれば、解消されるバグなのだから詳細に記述しなくても大丈夫だろう」と思ったからです。
しかし、このような報告の仕方は「どこが問題なのか」がわからないため、相手は「何を判断すればよいのか」がわかりません。
よって、どんなに小さな修正でも、具体的かつ丁寧に報告することはとても重要です。


##「このような挙動になってしまいます。こちら確認していただいてもよろしいでしょうか。」
このような場面はおそらくたくさんあると思います。
「こうすればうまくいくはずなのに...なんでだよ」とコードをいじりながら何回トライしてもだめ..本当悲しいですね。
その時に、他の人に相談すると思うのですが、ここで重要なのは**きちんと自分と同じ現象が再現されるように詳細にその再現方法を記しているか**です。
「〇〇画面でこれがこうなって、結果的にこうなってしまいます。」と長々書かれても、わかりにくいですし、何より実際に動作確認した方が断然早いです。
なので、具体的には**「そのパスやどのファイルがこの現象に絡んでいるのか」を記す**ことです。
そうすれば、相手は「この画面ということは、このビューファイルで...」のように考えなくて済みます。
**自分のよくない伝え方が相手の貴重な時間を奪ってしまう可能性がある**ことを、自覚しなければなりません。

##柔らかい言い方を心がける
テキストコミュニケーションは相手に顔が見えないため、自分のテンションを伝えづらいことがデメリットです。
つまり、**テキストのみで相手に自分の印象がよくないと思われてしまう可能性がある**ということです。
実際、私は「この際にはっきりさせておきたいのですが」という言い方をしてしまい、「あの文章怖いよ...」と言われてしまいました(笑)。
なので、この事例の場合「こちらと併せて確認させていただきたいことがあるのですが」のような柔らかい言い方を心がけるだけで、少なくともネガティブな印象を与えることを防ぐことができると思います。
**相手が自分とのコミュニケーションを楽しんでくれるようなテキストは仕事のしやすさにつながる**と思うので、リモートワークをする上で自分のテキストは意外と重要な要素であることを認識した上で、気をつけなければなりません。

#さいごに
テキストコミュニケーションに限らず、**コミュニケーションの基礎は「どれだけ相手を思いやれるか」**です。
これを念頭におけば、上記のことは自然にできているはずです。
当たり前の話ですが、エンジニアだからと言って、プログラミングだけしていればよいということではありません。
エンジニアである前に、一社会人として、コミュニケーションもきちんと取ることができるエンジニアとしてこれから成長していきたいです。
Raspberry Pi初期設定

#手順（メモ）
参考サイト
https://qiita.com/HeRo/items/c1c30d7267faeb304538
https://note.com/t_t_tonbi/n/n65a4af935d50


## 準備
1.bootイメージ作成
2.sshの有効化
3.WiFi設定

## 接続後に行うこと
### Raspberry Pi ソフトウェア構成の設定変更
```
sudo apt-get update
sudo apt-get -y upgrade

```
###Raspberry Pi ソフトウェア構成の設定変更
```
sudo raspi-config
```
1.メモリカードの領域拡張
2.パスワード変更
3.言語設定
4.タイムゾーン
5.キーボード
6.ssh有効化
7.VNC有効化
8.ディスプレイの設定

★今回は記事、USBケーブル１本でRaspberry Pi Zeroの電源とネットワークを兼用させるUSB-OTGの設定方法
参考にUSBで繋いでセットアップしてみた。

https://www.boardgamepark.com/entry/USB-OTG

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/501219/e8ef6195-00e8-35fc-8a3c-b9de3dae19ee.png)
:frowning2:

USBで接続して、設定できるのはいいと思います。
Raspberry Pi ZEROの設定をしてみた。
ASP.NET CoreのStartup.csでappsettings.jsonに書いたCORSのURLを取得する

ASP.NET Coreでappsettings.jsonの内容を読み込む方法として、
Startup.csで対応するクラスを設定してコントローラーで読み込む方法があります。
参考：[dot net core アプリケーションでapplication.jsonを参照する](https://qiita.com/taito-ITO/items/62bb3f41581bedacd6af)

しかし、CORS（クロスオリジン要求：別サーバのクライアントからのAPI呼び出しを許可する）は
StartUp.csで設定するので、CORSのURLをappsettings.jsonに記載する場合は別の取得方法が必要です。

この場合、StartUpのコンストラクタで渡されたIConfigurationに対して
GetSection("任意のキー").Valueで設定値を直接取得することができます。

```appsettings.json
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*",
  "CorsUrl": "https://localhost:12345"
}

```

```Startup.cs
public class Startup
{
    public Startup(IConfiguration configuration) => this.Configuration = configuration;

    public IConfiguration Configuration { get; }
　
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllers();

        services.AddSwaggerGen(c =>
        {
            c.SwaggerDoc("v1", new OpenApiInfo { Title = "MyAPI", Version = "v1" });
        });

        services.AddCors(options =>
        {
            options.AddDefaultPolicy(
                builder =>
                {
                    // appsettings.jsonから"CorsUrl"の値を取得して設定。
                    builder.WithOrigins(this.Configuration.GetSection("CorsUrl").Value)
                           .AllowAnyMethod()
                           .AllowAnyHeader();
                });
        });
    }
}
```
スプレッドシートでCOUNTIFを配列対応させる

# TL;DR
Googleスプレッドシートで`countif`に対して`ArrayFormula`を使って配列対応をさせようとすると失敗する。
GASで配列関数に対応した自作関数を定義してみたら、できた。

# やりたいこと
`countif`で条件に一致するセルの数を集計したい。
1行ごとに関数を入れていけばよいが、新たに行を挿入するときは手動で関数をベタ打ちしなければならない。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/275851/0930a300-9f1c-a56b-43a0-da6ccbea07d3.png)

面倒なので、`ArrayFormula`を使おうとするが…
もともと範囲内のセルを数える関数なので、行ごとの集計ができない。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/275851/ae384d4f-980f-416b-247e-0dd33e5bef3b.png)

そこで、
`nxm`の配列を入れると`n`の列が返ってくるような関数を作りたい！
つまり、行ごとに`countif`してくれる関数がほしい！

# 実装
ツール > スクリプトエディタを起動

以下のコードを入力して保存する。

```js
function COUNTIFARR(arr, str) {
  const re = new RegExp(str);
  return arr.map(a => a.filter(v => re.test(v)).length);
}
```

# 実行結果
うまく動いた！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/275851/0c0e6039-6449-af9a-4759-8ea5617a64bf.png)

## 注意
* 配列を返す関数は、フィルターをかけるとおかしな挙動になるため注意。
* 自作関数はスプレッドシート上ではサジェストされない（=赤線で「不明な関数」と警告が出る）が、気にせず確定すれば動く。
[Rails] VSCodeを入れたらやること

##はじめに
ほとんどの方が開発するために用いるエディタとしてVSCodeを使っていることでしょう。
Railsで開発していく上で私が初めてVSCodeを導入したときに、最低限入れておくべきと思った、拡張機能や、設定を忘れないうちに記事に残しておきたいと思います。

もしおすすめ拡張機能があれば教えていただけると幸いです。
よろしくお願いします。

　##VSCodeの日本語化
我々が親しんでいる日本語で表示させたい人はまずはこれをするべきですね。
英語の方が使いやすいって人や、ストイックな人はする必要はないです。

拡張機能の検索に「Japanese Language Pack for Visual Studio Code」と打ち込むと出てきます。
検索予測で出てくるので全部打ち込む必要はありません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1552346/2ca8bf86-39a7-8374-cbf0-4e6a07427f86.png)

インストールした後、再起動しないと反映されなかったりするので気を付けましょう。

##HTML/CSS自動補完機能
続いてHTMLとCSSの自動補完をしてくれる拡張機能を入れます。

拡張機能の検索フォームで「HTML Snippets」で検索してインストールします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1552346/0d04df0e-f88a-1ed2-c669-eebbb144633d.png)


##Rubyの構文チェック
Rubyの構文チェックをしてくれます。Rubyを使って開発をするのであれば必須でしょう。
拡張機能の検索フォームで「Ruby」と検索すると出てきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1552346/986384c6-cdfa-cf99-0e49-44f6f123bad8.png)

##全角スペースのチェック機能
全角スペースにより、動作しない場合、それを見つけるのは苦労します。
全角スペースになっているところを検出してくれる拡張機能です。

拡張機能の検索フォームで、「zenkaku」と入力すれば出てきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1552346/9fdb8b5a-e8ca-f8f1-cf86-8eec9d43e038.png)

zenkakuを利用するにはまだ設定が必要です。
インストールが完了した後「command + shift + p」の三つのキーを同時に入力し、コマンドパレットという設定ファイルの検索画面を開きます。

そこに「> Enable zenkaku」と入力して選択します。

「command + Q」の二つのキーを同時に入力してVSCodeを終了させます。

続いて常にzenkakuをONにしておくための設定もしておきます。

1. finderでホームディレクトリを開く

2. command + shift + .で隠しディレクトリを表示する

3. 　> .vscode > extensions > mosapride.zenkaku-0.0.3 > と移動する

4. extension.jsを2本指タップしてメニューを表示する

5. 「このアプリケーションを開く」から「テキストエディット」を選択する

6. 5行目の「var enabled = false;」を「var enabled = true;」に変更してcommand + Sで保存する

これで完了です。

##コードのスペルチェック
コードのスペル間違いを指摘してくれる拡張機能を追加します。

拡張機能の検索フォームで「Code Spell Checker」と入力してインストールします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1552346/5199e244-c534-025a-6dcd-dfcea3ed3eb7.png)

##オートセーブ設定
最後に拡張機能ではなくVSCode自体の設定をいじります。
「command + , 」で設定画面を開けます。
一番上のAuto Saveというところのプルダウンから「onFocusChange」を選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1552346/6ecbaa9e-a72c-45ed-2e69-4a02a337f30a.png)


##最後に
あくまでこれは私が最初に行った設定です。他にも便利機能や必須の拡張機能や設定があれば教えて欲しいです！
mysqldumpコマンドまとめ「基礎から実践まで」

## 1. 基本的な使い方
### 1-1. 基本のコマンド
mysqldumpコマンドは指定したDBのデータを標準出力してくれるコマンド。
なので、標準出力のリダイレクト`>`を組み合わせることでファイルに出力させてdumpファイルを作成するのが普通の使い方である。

``` bash
# DBの全てのテーブルを取得
mysqldump -h[db_server_host_name] -u[db_user_name] -p[db_password] [DB_Name] > ./dump_file_name.sql
# 指定したテーブルだけ取得
mysqldump -h[db_server_host_name] -u[db_user_name] -p[db_password] [DB_Name] [table_name] > ./dump_file_name.sql
```
### 1-2. テーブルの構造・データを指定して取得
上記みたいに何も指定しないとテーブルの構造＋データを取得するが、指定して取得する方法もある。やり方は以下。
1. 構造のみ取得したい時は「--no-data」か「-d」オプションをつける
2. データのみ取得したい時は「-t」オプションをつける

これを上記で紹介したDB全体・テーブル指定を応用したら下記のような例のパターンが作れる

``` bash
# db全てのテーブルの構造・データ
mysqldump -u [username] -p [password] -h [host_name] [database_name] > ./dump_file_name.sql
# db全てのテーブルの構造
mysqldump -u [username] -p [password] -h [host_name] -d [database_name] > ./dump_file_name.sql
# db全てのテーブルのデータ
mysqldump -u [username] -p [password] -h [host_name] -t [database_name] > ./dump_file_name.sql

# dbの指定テーブルの構造・データ
mysqldump -u [username] -p [password] -h [host_name] [database_name] [table_name] > ./dump_file_name.sql
# dbの指定テーブルの構造
mysqldump -u [username] -p [password] -h [host_name] -d [database_name] [table_name] > ./dump_file_name.sql
# dbの指定テーブルのデータ
mysqldump -u [username] -p [password] -h [host_name] -t [database_name] [table_name] > ./dump_file_name.sql
```
### 1-3. 離れたサーバーのdumpをローカルで取る方法
~/.ssh/configに保存させたなら下記で簡単に取れる

``` bash
ssh [Host Name] 'mysqldump -h[db_server_host_name] -u[db_user_name] -p[db_password]' > ./dump_file_name.sql
```
sshでサーバー繋げてmysqldumpコマンドを走らせて標準出力したものをローカルのファイルに出力する方法。


## 2. 応用編
本番のDBからdumpを取る時は気をつけなければいけないことがいくつかあるので紹介する。
### 2-1. テーブルロックの問題
まだ公開されていないシステムだといいかもしれないが、既に運用中のシステムのdumpを取る時はテーブルロックに注意しなければならない。
でないとREADロックがかかってしまって、ユーザはdump対象のテーブルにselect以外の処理が出せなくなってしまうという大惨事が怒る😨
結論から言うとdump先のテーブルがinnoDBの時は下記のオプションを追加するだけでいい。

```
--single-transaction
--skip-lock-tables
```
--single-transactionだけでロックはかからないらしいけど、怖いので一応--skip-lock-tablesもつけている。
もし詳しい方いらっしゃったら教えてください🙇‍♂️

### 2-2. データ量が重すぎる問題
規模の大きいサービスだとdump取るのかなりの時間を消耗することは普通にあることなので、できるだけ時間を短縮する必要がある。
mysqldumpはテーブル指定の場合は`--where`オプションが使えるらしい。
あとgzipで圧縮しながらとなると最終的に下記のコマンドに落ち着いた。

```bash
ssh sid_pd 'mysqldump --single-transaction --skip-lock-tables -h[db_server_host_name] -u[db_user_name] -p[db_password] -t [database_name] [table_name] --where="id > 11005 limit 0,50200" | gzip' | cat > "file_name_`date "+%Y%m%d_%H%M%S"`.sql.gz"
```

### 参考にしたサイト
https://masyus.work/articles/if-using-mysqldump-add-single-transaction-skip-lock-table/
https://gihyo.jp/dev/serial/01/mysql-road-construction-news/0062
https://dev.mysql.com/doc/refman/5.6/ja/mysqldump.html#option_mysqldump_single-transaction
Google Material Iconの下の余白をなくす

## 症状
こんな感じで余白ができる
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317993/eb76f4db-ec96-9b91-66ee-817c87084e43.png)

## 解決策
```css
.material-icons {
  vertical-align: middle;
}
```
解決
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317993/79e764a6-44a0-9934-f16d-30ff17afd976.png)
【Python】所得税の算出方法をプログラム化してみた

#所得税の算出方法をプログラム化してみた

フリーランスの税金を調べるついでにコードで遊んでいました。
ずっと勉強しているとアウトプットしたい欲にかられます。
特に使い道はないです。
法改正されてて情報が古くなる可能性はあります。

#算出方法について
オフィシャルを参考に
[国税庁No.2260 所得税の税率](https://www.nta.go.jp/taxes/shiraberu/taxanswer/shotoku/2260.htm)

所得税の算定基礎となる所得金額＝収入－必要経費－各種控除
これに金額に応じた税率が適用されて、さらに控除額が引かれたものが所得税になります。
さらに令和19年までは、ここから復興特別所得税として2.1%が追加で課税されます。

#実装

まずは定番のものをインポートします。
作成後にグラフで確認するので、そのためのフォーマットも追加します。

```Python
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.ticker import ScalarFormatter, PercentFormatter
```

#関数部分
所得から経費を差し引いた数値を入力すれば所得税が返ってきます。

他に必要な情報
p:税率
bin:税率の境界値
deduction:控除額
SpecialTax:復興特別所得税の税率

ここらへんは変更に備えて定型化したい。


```Python
def CalcTax(income):

  p = [0.05, 0.10, 0.20, 0.23 ,0.33 , 0.40, 0.45]
  bin = [i*10000 for i in [0, 195, 330, 695, 900, 1800, 4000, np.inf]]
  deduction = [0, 97500, 427500, 636000, 1536000, 2796000, 4976000]
  SpecialTax = 0.021
  step = len(p)

  ans = 0
  i = 0
  while i <= step:
    if (income >= bin[i]) and (income < bin[i+1]):
      ans = (income*p[i] - deduction[i]) * (1 + SpecialTax)
      break
    i+=1

  return ans 
```

#所得税のビジュアライゼーション
実際に払う所得税の金額とそれが収入に占める金額（ウォレットシェア）
の二つのグラフを出してます。
体裁を整えるのにset_major_formatterを使用

税率は段階的に上がっていますが、
累進課税なので実際の負担額は滑らかに上がっていきますね。



```Python
inc = np.arange(0, 10**8, 10**5)
tax = [CalcTax(i) for i in inc]

fig, (ax1, ax2) = plt.subplots(2, 1, sharex=True)

ax1.plot(inc, tax)
ax1.set_ylabel('tax')
ax1.yaxis.set_major_formatter(ScalarFormatter(useMathText=True))

ax2.plot(inc, tax/inc)
ax2.set_xlabel('income')
ax2.set_ylabel('tax_per_income')
ax2.yaxis.set_major_formatter(PercentFormatter(1.0))

fig.tight_layout()
```

![img.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/380282/7283cc6a-5929-63f4-7c84-b6e9bc3a7bda.png)
13 少数と誤差 Dif:274 ABC189 B：「AtCoder　凡人が『緑』になるための精選50問詳細解説」サンプル

**この記事は拙著「AtCoder　凡人が『緑』になるための精選50問詳細解説」のサンプルです**
価格：100円
kindle：https://www.amazon.co.jp/dp/B09C3TPQYV
booth(pdf)：https://sano192.booth.pm/items/3179185

前：https://qiita.com/sano192/items/f6e495d5d63b46bde374
次：https://qiita.com/sano192/items/d4aba1192a81cf736eae


https://atcoder.jp/contests/abc189/tasks/abc189_b

**【目標】**
・少数の計算は誤差が出る可能性があることを覚える
・少数が出てくる計算を避けられる実装をできるようになる

**【概要】**
B問題にも関わらず、コンテスト中にWA(不正解)を出す人が非常に多かった問題。WA(不正解)の理由は少数の誤差が考慮されていなかったこと。計算の途中で少数が出る問題についてはできる限り整数だけで計算できないか？ということを考えるようにしよう。この問題で少数が出てくる問題をどうやって整数のみの計算に置き換えるか、ということを学ぶ。

**【方針】**
お酒に含まれるアルコール量はV×(P/100)。
素朴にコードに書くと以下のようになる。

```python:
N,X=map(int, input().split())

alcohol=0
for i in range(N):
    V,P=map(int, input().split())
    alcohol+=V*(P/100)
    if X<alcohol:
        print(i+1)
        exit()
print(-1)
```

サンプルも通るから提出・・・するとWA(間違い)となる。

コンピュータは無限小数をそのまま扱えない。内部ではある程度のところで切り捨てや切り上げを行ってしまうため、誤差が出る可能性がある。

上記のコードでは以下のような入力で誤差が生じ、WA(間違い)となってしまう。
1 7
25 28
1杯目はお酒の量が25ml、アルコール度数が28%でアルコール摂取量が25*(28/100)=7mlとなり、ぎりぎり酔わない。つまり答えは-1となるはずだ。

ところがpythonで25*(28/100)を計算すると7.000000000000001となる。
そうなると1を出力してしまい、WA(間違い)となる。

どうすれば少数がでてこないようにできるか。
度数Pを100で割るのではなく、許容量Xを100倍してしまえば全て整数の計算となり、少数をなくすことができる。
つまり
X<V*(P/100)
の両辺を100倍して
100*X<V*P
としてしまえば良い。

**【実装】**
まず入力を受け取る。

```python:
N,X=map(int, input().split())
```

摂取したアルコール摂取量を記録する変数alcoholを用意する。最初は0。

```python:
alcohol=0
```

1番目のお酒から順にアルコールの摂取量を計算して足していく。
摂取量の計算はV*Pとし、これが100*Xを超えたら答えを出力する。
最後まで100*Xを超えなければ-1を出力。

```python:
for i in range(N):
    V,P=map(int, input().split())
    alcohol+=V*P
    if 100*X<alcohol:
        print(i+1)
        exit()

print(-1)
```

**【コード全文】**

```python:
N,X=map(int, input().split())

alcohol=0

for i in range(N):
    V,P=map(int, input().split())
    alcohol+=V*P
    if 100*X<alcohol:
        print(i+1)
        exit()

print(-1)
```

**この記事は拙著「AtCoder　凡人が『緑』になるための精選50問詳細解説」のサンプルです**
価格：100円
kindle：https://www.amazon.co.jp/dp/B09C3TPQYV
booth(pdf)：https://sano192.booth.pm/items/3179185

前：https://qiita.com/sano192/items/f6e495d5d63b46bde374
次：https://qiita.com/sano192/items/d4aba1192a81cf736eae
文脈自由文法からε動作を削除する感覚的説明

オートマトンに関する教科書の数式的な説明がわかりにくいと感じたので、感覚的な自分なりの説明を載せます。情報系の学生向けの記事です。

空白化記号$N_\epsilon$は求まってるとします。

# 1.そのままパスできる生成規則をパスさせる
まず、生成規則の右側で空白化記号をないものはそのまま新しい生成規則のリストに追加。


# 2.空白記号を右側に含む生成規則は場合によっては複数に分岐させる
空白記号が生成規則の右側にあるものは、変更の可能性があるので注目。

そのような特定の生成規則に着目する。
その生成規則の右側にある空白化記号（１つの場合もあれば、複数の場合もある）
その生成規則の右側の空白化記号を削除したものを新しい規則のリストに追加する。
例えば、右側に空白化記号$B,C\in N_{\epsilon}$があった場合

4通り：
何も消さない
Bのみを消す
Cのみを消す
BもCも消す

これらの処理をした各４つの生成規則を、新しい規則のリストに追加する。
もし空白化記号をいくつか消した後に、右辺が$\epsilon$になった場合は、それは新しいリストには加えず、次の消し方の候補にすすむ。

空白化記号を右辺に含むような生成規則について、全てに同様の作業をする

# 3.Sがもし空白化記号の場合の例外処理 (Optional)
まれに$S\in N_\epsilon$の場合は非終端記号の集合$N$と$S$を新しいものにしなければならない。アンパンマンの汚れた顔を、新しい顔に付け替えるのと同じ感覚です。

形式的に$S'\rightarrow \epsilon, S'\rightarrow S$を生成規則に追加します。

これは$\epsilon$なしの定義で、$\epsilon$が生成規則にあったとしても開始記号だけだよ、という条件に合わせるための作業です。

これに伴い、$N$に$S'$に追加した新しい非終端記号の$N'$を回答の最後に書いておきましょう。

これでひと段落。

# 参考文献
オートマトン・言語理論　森北出版
jquery.datatable.jsで重複するセルをハイライト表示する

# デモ

前提として jquery.js と jquery.datatable.js を読み込んでおく必要があります

<p class="codepen" data-height="300" data-default-tab="js,result" data-slug-hash="rNmKMvW" data-user="qwe001" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/qwe001/pen/rNmKMvW">
  </a> by qwe001 (<a href="https://codepen.io/qwe001">@qwe001</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

# 実装

```javascript
/**
 * 重複セルをチェックする
 */
function isDuplicateColumn(item, arr, key)
{
  var ret = false;
  var sameValues = arr.filter(function(elem) {
    return elem[key] === item[key];
  });

  if(sameValues.length > 1){
    ret = true;
  }

  return ret;
}
```

```javascript
    "preDrawCallback": function(settings){
      var api = this.api();
      var items = api.rows().data();

      // 重複セルをチェックする
      items.map(function(item, idx, arr){
        item.is_duplicate_code = isDuplicateColumn(item, arr, "code");
        item.is_duplicate_name = isDuplicateColumn(item, arr, "name");
        return item;
      });
    },
```

データ取得が完了したタイミングで重複チェックを実行。
データに「is_duplicate_{key}」プロパティを埋め込んで、
同じ値がある場合にフラグを立てておきます

```javascript
    "rowCallback" : function(row, item){
      // 商品コード
      if(item.code){
        // 重複セルをハイライト
        if(item.is_duplicate_code){
          $('td.dt-column-code', row).addClass("same");
        }
      }

      // 名前
      if(item.name){
        // 重複セルをハイライト
        if(item.is_duplicate_name){
          $('td.dt-column-name', row).addClass("same");
        }
      }
    },
```

各行ごとの描写処理を行うタイミングでフラグ状態を確認して条件分岐させます。
あとは色を変えるなりお好きに。

もっとスマートな実装をご存知の方がいましたら、
教えて頂けると嬉しいです。
PHP で Zip ファイルをテンポラリディレクトリに展開する

## 概要
PHP で Zip ファイルをテンポラリディレクトリに展開して中身のファイルをごにょごにょするのが地味にめんどくさかったのでメモがてらまとめました。

## テンポラリディレクトリに展開先を用意する
Zip ファイルの展開先として一時的なディレクトリを使用したかったため`sys_get_temp_dir`メソッドを使いました。
`/tmp`など OS が指定する一時的なファイルの置き場所へのパスを取得することができます。
加えて`uniqid`メソッドで取得する一意な値をディレクトリ名とするようにしてみました。

```php
$tmpDir = sys_get_temp_dir() . '/' . uniqid(); 
mkdir($tmpDir); // ディレクトリを作成します
```

このようにテンポラリディレクトリに展開するようにしておけば、展開後に処理が落ちてしまい削除が漏れしてしまった場合でも後で迷いなく削除することができます。
※OS によっては一定期間経つとテンポラリディレクトリのファイルが削除される設定になっているものもあるようです

## Zip ファイルの展開
`ZipArchive`という Zip を扱うクラスを使います。
※環境によってはインストールが必要なようですが私の環境ではデフォルトで使えました

https://www.php.net/manual/ja/class.ziparchive.php

```php
$tmpDir = sys_get_temp_dir() . '/' . uniqid(); 
mkdir($tmpDir);

$zip = new ZipArchive();

$zip->open($filePath); // 展開したい zip ファイルを指定します
$zip->extractTo($tmpDir); // 展開先のディレクトリを指定します
$zip->close();
```
このようにすればテンポラリディレクトリに Zip ファイルが展開されているはずですので後は煮るなり焼くなり好きにしましょう！  


# おまけ
私が今回使ったファイル名を扱うメソッドを備忘録を兼ねて紹介しておきます。
PHP 公式のドキュメント見たほうが早そうですが参考までに ※ドキュメントへのリンクもつけておきます

### ファイル名取得

https://www.php.net/manual/ja/function.glob  

パターンに合ったファイルパスを取得します。フルパスが取得されます。  
正規表現は使えませんが、以下のように書けばあるディレクトリの拡張子が txt のみのファイルのみ取得できるので便利です。

```php
$files = glob("/hoge/*.txt");
```

例えば以下のようなディレクトリの構造だった場合は、

```
/hoge/a.txt
/hoge/b.txt
/hoge/c.log
```

以下のような配列が取得されます。

```php
$files = [
   '/hoge/a.txt',
   '/hoge/b.txt',
];
```

特定の拡張子のみ取得したい場合はたまにあるので使えそうです。  

### ファイルパスのパース
`glob`メソッドを使ってまとめてファイルパスを取得したものの以下のようなパターンもあると思います。

- 実際に使用するのはファイル名だけなのでパスは除いて抜き出したい
- 拡張子も削りたい

そういうときは`pathinfo`メソッドがファイルパスをパースした連想配列を返してくれるので便利です。

https://www.php.net/manual/ja/function.pathinfo.php

```php
$filePath = '/hoge/a.txt';
$pathinfo = pathinfo($filePath);

// 以下のような連想配列が取得できます
[
    [dirname] => /hoge,
    [basename] => a.txt,
    [extension] => .txt,
    [filename] => a,
];
```

フラグを指定すれば連想配列ではなく、指定した要素だけを取得することも可能です。

```php
$filePath = '/hoge/a.txt';

// 'a.txt' のみが返ります
$pathinfo = pathinfo($filePath, PATHINFO_BASENAME);
```

上記と同じことをしてくれる`basename`メソッドがあるためお好みで。

https://www.php.net/manual/ja/function.basename.php

### 感想
素の PHP のメソッドを使ってファイル操作をしましたが`pathinfo`のような気の利いたメソッドもあれば、ディレクトリが空でないと使えない`rmdir`などもあり歴史を感じました。
12 最小公倍数 Dif:172 ABC148 C：「AtCoder　凡人が『緑』になるための精選50問詳細解説」サンプル

**この記事は拙著「AtCoder　凡人が『緑』になるための精選50問詳細解説」のサンプルです**
価格：100円
kindle：https://www.amazon.co.jp/dp/B09C3TPQYV
booth(pdf)：https://sano192.booth.pm/items/3179185

前：https://qiita.com/sano192/items/2193fbb57ab66237ed9e
次：https://qiita.com/sano192/items/fee4234d2942ea6c02a5

https://atcoder.jp/contests/abc148/tasks/abc148_c

【目標】
・最小公倍数を求められるようになる

【概要】
単純に2数の最小公倍数を求めることができれば解ける問題。この問題では最小公倍数を求めるアルゴリズムを実装できれば十分だが、一応【実装】のあとに【証明】として数学的な理屈も説明している。興味がある人は読んでもよいが無理に理解する必要はない。

【方針】
A、Bの両方で割り切れる最小の数＝A、B両方の倍数で最小のもの。
つまりA、Bの最小公倍数を出力すれば終わり。
では最小公倍数はどのように求めるか。

最小公倍数の求め方を知らない場合、自力で求め方を考えず、さっさとgoogleで「最小公倍数　python」と検索し、コードをコピペして解こう。検索すると以下のようなコードが出てくるだろう。

「lcm(x,y)：x,yの最小公倍数を返す」

```python:
import math
def lcm(x,y):
    return (x*y)//math.gcd(x,y)
```

lcmは最小公倍数=least common multipleの頭文字。

import mathという部分は標準ライブラリというものを使っている。
ライブラリがなにかを説明するとややこしいが、競技プログラミングにおいては呪文のようなものだと理解しておけば十分だ。
最初にimport mathを唱えておくとmath.○○という形でsin,cos,tan,log,gcdなどの関数を使うことができる。単にgcd(x,y)と書くとエラーになるので必ずmath.gcd(x,y)と書くこと。

math.gcd(x,y)は「x,yの最大公約数を返す」関数。
gcdは最大公約数=greatest common divisorの頭文字。

つまり

```python:
return (x*y)//math.gcd(x.y)
```

は
(x*y)//(x,yの最大公約数)
を計算して返している。
そしてこれこそがx,yの最小公倍数となる。

「例」
12と18の最小公倍数
12、18の最大公約数は6。計算式に当てはめると
(12×18)/6=36
と求められる。36は12と18の最小公倍数。

【実装】
最小公倍数を求める関数ができていればA、Bを受け取ってA、Bの最小公倍数を出力するだけ。

```python:
import math
def lcm(x,y):
    return (x*y)//math.gcd(x,y)

A,B=map(int, input().split())
print(lcm(A,B))
```

【証明】
なぜ最小公倍数が(x*y)//(x,yの最大公約数)で求まるのか証明を書く。
ガチガチの数学であるため、興味がなければここは飛ばしても構わない。

(証明)
A,Bの最大公約数をG、最小公倍数をLとする。
GがA、Bの最大公約数であるから、互いに素な数a、bを使って以下のように表せる。
A=aG...(1)
B=bG...(2)
LがA、Bの最小公倍数であるから、自然数n、mを使って以下のように表せる。
L=An…(3)
L=Bm..(4)
(3)、(4)に(1)、(2)を代入すると
L=aGn...(5)
L=bGm
ゆえに
aGn=bGm
両辺をGで割り
an=bm
a,bは互いに素であるからn=b,m=aとなる。
((n,m)=(b,a),(2b,2a),(3b,3a),...のどれでも等号は成り立つが、Lが「最小」公倍数であるから(n,m)=(b,a)となる)
n=bを(5)に代入すると
L=aGb
両辺にGを掛けて
GL=aG×bG
(1)、(2)を右辺に代入し、
GL=A×B
L=(A×B)/G
(証明終)

他に素因数分解を一般に当てはめたような証明方法もあるのでリンクを載せておく。上記の証明がしっくり来なければ見てみると良い。
https://manabitimes.jp/math/1032

【コード全文】

```python:
import math
def lcm(x,y):
    return (x*y)//math.gcd(x,y)

A,B=map(int, input().split())
print(lcm(A,B))
```

**この記事は拙著「AtCoder　凡人が『緑』になるための精選50問詳細解説」のサンプルです**
価格：100円
kindle：https://www.amazon.co.jp/dp/B09C3TPQYV
booth(pdf)：https://sano192.booth.pm/items/3179185

前：https://qiita.com/sano192/items/2193fbb57ab66237ed9e
次：https://qiita.com/sano192/items/fee4234d2942ea6c02a5
[JSTQB AL TM]　6. テストツールおよび自動化　を視覚的にまとめてみた

# はじめに
2021年8月のJSTQB　Advanced Level Test Manager(TM)の試験を受験予定のMa5am1nです。
[シラバス](http://jstqb.jp/dl/JSTQB-Syllabus.Advanced_TM_Version2012.J04.pdf)[^1]を読んでいるのですが、どうも文字が多くて頭に入りにくいので、図など視覚的に入ってきやすそうな形式でまとめてみます。

[^1]: 2012年度版 Advanced Level シラバス　日本語版　-テストマネージャ
**※あくまで私個人の解釈で作成したものです。内容が100%シラバスの通りであることは保証できませんので、ご了承ください。**
**※スライドの青文字、青線になっている箇所は、シラバスに記載されていない、私独自の解釈で記載した内容です。**

このページでは、6. テストツールおよび自動化について扱います。


# 6. テストツールおよび自動化
## 6.1 イントロダクション

（特になし）

## 6.2 ツール選択
![JSTQB AL TM （後半）.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1636570/3b71ede4-712f-611d-6965-cbb3d9545b62.png)


### 6.2.1 オープンソースツール
![JSTQB AL TM （後半） (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1636570/a0119b2c-c525-76c4-2c0c-a8251b5fde44.png)
![JSTQB AL TM （後半） (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1636570/f129fb32-3640-c8c4-5c00-3817a318f426.png)


### 6.2.2 カスタムツール
![JSTQB AL TM （後半） (3).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1636570/24819c6b-3ce8-d359-88b0-f9e0b9bbb7dd.png)


### 6.2.3 投資効果（ROI）
![JSTQB AL TM （後半） (4).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1636570/a8c28542-63eb-c163-6917-1a2681f6747c.png)
![JSTQB AL TM （後半） (5).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1636570/8301078f-421d-303c-f128-5d5f5e32aeb0.png)
![JSTQB AL TM （後半） (6).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1636570/d51b5a5b-5c4d-31fa-6482-748bc48c530b.png)
![JSTQB AL TM （後半） (7).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1636570/b8c5dca8-e33c-e8ec-395b-87cbb0b8ce47.png)


### 6.2.4 選択プロセス
![JSTQB AL TM （後半） (8).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1636570/676156b3-2a9d-d1f8-4dd8-a43e5689dbbd.png)
![JSTQB AL TM （後半） (9).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1636570/86522343-f676-27d2-9610-65b3f8098403.png)


## 6.3 ツールのライフサイクル
![JSTQB AL TM （後半） (10).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1636570/01186a2f-d8cd-c7e8-4ea8-f3a3410e63bd.png)


## 6.4 ツールのメトリクス
![JSTQB AL TM （後半） (11).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1636570/166249e4-f543-ac7d-561d-0759aa6d4302.png)
![JSTQB AL TM （後半） (12).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1636570/4d2b7472-74d6-998d-e50a-6153252deaf1.png)
twitterで特定アカウントのツイート一覧を取得する方法


# はじめに
<!-- 発端や概要を記載 -->
ツイートを取得する方法を自分メモ用に残しておきます。
# 準備

#### ライブラリ

```
pip install tweepy
```

#### API取得
[Twitter API を利用するには](https://qiita.com/taishi0202/items/092c267c0c6dc68347c5)
（私はかなり前に取得したAPIを利用しましたが、以下のような記事を参考にすれば取得出来そうです。）

#ツイート取得コード

1. API情報とファイル名を修正してからファイル実行して下さい。
1. コンソールで取得したいアカウントIDが聞かれるので入力して下さい。

```twitter.py

import tweepy
from datetime import timedelta
import csv
import time

#API情報を記載
consumer_key = 'xxxx'
consumer_secret = 'xxxx'
access_token = 'xxxx'
access_token_secret = 'xxxx'

#保存するファイル名を記載
file_name='xxx.csv'

auth = tweepy.OAuthHandler(consumer_key, consumer_secret)
auth.set_access_token(access_token, access_token_secret)
api = tweepy.API(auth)

info_list = []


def main():

    Account = input('Account:@')  # ツイートを抽出するアカウントIDを入力するためのInput関数

    num = 0  # 取得するツイートを計算する

    pages = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]  # ※2
    #pages = [1]
    for page in pages:

        tweets = api.user_timeline(Account, count=200, page=page)  # ※3

        for tweet in tweets:
            info_dic = {'time': '', 'content': ''}
            tweet.created_at += timedelta(hours=9)

            info_dic['time'] = tweet.created_at
            info_dic['content'] = tweet.text
            info_list.append(info_dic)
            num += 1
        time.sleep(2)
    
    with open(file_name, "w", encoding="utf_8_sig") as f:
        fieldnames = ['time', 'content']
        writer = csv.DictWriter(f, fieldnames=fieldnames, lineterminator="\n")
        writer.writeheader()
        for s in info_list:
            writer.writerow({'time': s['time'], 'content': s['content']})


if __name__ == '__main__':

    main()

```
2つの配列を1つのforeach処理でDBに格納する方法

#やりたかったこと
####WordテーブルとNoteテーブルのカラムにformから送られてきた値を格納。

Noteテーブルのカラム
・user_id
・title

Wordテーブルのカラム
・note_id
・question　
・answer



create.blade.php

```php
<form action="{{route('Vocabularys.add')}}" method="POST">
	@csrf
	<div>
		<h3>タイトル</h3>
		<input type="text" name='title'>
	</div>
	<div>
　　　　　//name = 'question'だと最後の1個のデータしか渡せないがname='question[]'とすることで配列にして全部の値をコントローラーに渡すことができる
		<p>Q.<input type="text" name='question[]'></p>
		<p>A.<input type="text" name='answer[]'></p>
		<p>Q.<input type="text" name='question[]'></p>
		<p>A.<input type="text" name='answer[]'></p>
		<p>Q.<input type="text" name='question[]'></p>
		<p>A.<input type="text" name='answer[]'></p>
		<a href="">削除</a>
	</div>
	<div>
		<p>
			<a href="">追加</a>
		</p>
		<p class="create-btn">
			<input type="submit" value="登録">
		</p>
	</div>
</form>
```

contoroller

```php
public function add(Request $request){
　　　　　//ログインしているユーザーのIDを取得
        $userId = Auth::id();
          
        //モデル名::createを使うとモデルと接続されているテーブルにカラムが格納される
　　　　 //そのまま変数の中でcreateを行う。
        $note = Note::create([
            //上記でログインしているユーザーのID取得した$userIdをuser_idカラムに入れる。
            'user_id'=>$userId
            //formから送られたtitleをリクエストで呼び出しtitleカラムに格納
            'title'=>$request->title,
        ]);        

        // create.bladeから配列で受け取った値をそれぞれ変数に代入
        $questionItems = $request->question;
        $answerItems = $request->answer;
        
        // コールバック関数はないのでNull
        foreach (array_map(Null, $questionItems, $answerItems) as [$questionItem,$answerItem,]){

            $word = Word::create([
                'note_id'=> $note->id,
                'question' => $questionItem,
                'answer' => $answerItem,
            ]);
        };

        return redirect('/Vocabularys/index');
    }

```

上記でうまく2つの配列を1つのforeach処理でDBに格納できた。

最初やろうとしたこと

```php
        // create.bladeから配列で受け取った値をそれぞれ変数に代入
        $questionItems = $request->question;
        $answerItems = $request->answer;

　　　　　//①の処理
        //$questionItemsの中の配列の値の分だけ回してWordテーブルに格納
        foreach ($questionItems as $item){
            $word = Word::create([
                'note_id'=> $note->id,
                'question' => $item,
            ]);
        };

        ②の処理
        //$answerItemsの中の配列の値の分だけ回してWordテーブルに格納
        foreach ($answerItems as $item){
            $word = Word::create([
                'answer' => $item,
            ]);
        };
```

実行すると
・①の処理でWordテーブルのanswerカラムがnullだよと怒られた。
同じ処理でquestionカラムとanswerカラムに値を入れないといけないだと！！え？どうしよう。
無知な僕はめちゃめちゃ悩んだ。ググるとarray_map関数に出会った。。

array_mapとは？
https://techacademy.jp/magazine/29657

これでできるんじゃね？
実装するといけた。
使い方合ってるか知らんけどwww

```php
$配列1 = [1,2,3]

function コールバック関数名($n) {
    //$nには$配列1が入る。
    return $n + 1;
}

//hogeが呼ばれたら
$hoge = array_map(コールバック関数名, $配列１)

consol.log($hog);

+1される
[2,3,4]

```


自分のメモ用なのでわかりづらいと思いますが参考にしてください。
11 シミュレーション Dif:168 ABC203 C：「AtCoder　凡人が『緑』になるための精選50問詳細解説」サンプル

**この記事は拙著「AtCoder　凡人が『緑』になるための精選50問詳細解説」のサンプルです**
価格：100円
kindle：https://www.amazon.co.jp/dp/B09C3TPQYV
booth(pdf)：https://sano192.booth.pm/items/3179185

前：https://qiita.com/sano192/items/1eb1ee760594502280fd
次：https://qiita.com/sano192/items/f6e495d5d63b46bde374

https://atcoder.jp/contests/abc203/tasks/abc203_c


**【目的】**
・愚直に実装するとTLE(実行時間制限超過)する問題について、工夫して計算量を減らす方法を身につける。

**【概要】**
一見「やるだけ」の問題に見える。が、お金がつきるまで村を1つずつ進んでいくと制約条件Kが大きすぎてTLE(実行時間制限超過)する。愚直にシミュレーションするのではなく、省略できるところは省略して計算量を減らせるような工夫ができるようになろう。

**【方針】**
持っているお金の分先の村へ進むことができる。

例えば100円を持っている場合
(今いる村の番号+100)番の村
へ行くことができる。

というわけで
(1)1円ずつ手持ちのお金をマイナスしながら進んで行く
(2)村に友達がいたら手持ちのお金をもらった分増やす
(3)手持ちのお金が0になったら終わり
と、愚直に実装するとまんまとTLE(実行時間制限超過)する。
手持ちのお金=Kの制約が10^9と大きすぎるからだ。

考え方を変えて
まずはじめにK円分進む(村Kに着く)
(1)進んだ間の村に友達がいるか確認する
(2)友達がいればもらえるお金分先に進む
として(1)、(2)を繰り返すと考えよう。

例えば以下の入力の場合、
3 10
3 10
45 20
11 10

友達の数：N=3
最初に持っているお金：K=10円
友達1：場所＝村3　お金=10円
友達2：場所＝村45　お金=20円
友達3：場所＝村11　お金=10円

まず友達の情報を村0に近い順に並び替える。
友達1：場所＝村3　お金=10
友達3：場所＝村11　お金=10
友達2：場所＝村45　お金=20

はじめにK円分進む。
最初に持っているお金、10円分進んで村10に着く。

(1)進んだ間の村に友達がいるか確認する
村0～村10までにいる友だちを確認する。
村3に友達1がいるから10円もらえている。

(2)友達がいればもらえるお金分先に進む
村10から10円分進んで村20に着く。

(1)進んだ間の村に友達がいるか確認する
村10～村20までにいる友だちを確認する。
村11に友達3がいるから10円もらえている。

(2)友達がいればもらえるお金分先に進む
村20から10円分進んで村30に着く

(1)進んだ間の村に友達がいるか確認する
村20～村30までにいる友だちを確認する。
この間に友達はいないからお金はもらえない。

進めなくなったため終わり。
村30が最終的にたどり着く村。

この方法なら計算回数を減らせるのでTLE(実行時間制限超過)しない。

**【実装】**
入力を受け取る。
友達の情報は二次元配列で受け取る。

```python:
N,K=map(int, input().split())

friends=[]
for i in range(N):
    A,B=map(int, input().split())
    friends.append([A,B])
```

friendsに[友達のいる村,持っているお金]を格納している。

次に友達の情報を村0に近い順に並び替える。

```python:
friends.sort()
```

今いる村の番号をnow_villageとする。
最初は村0なのでnow_village=0。

```python:
now_village=0
```

まずK円分進む(村Kに着く)
now_villageにKプラスすればOK。

```python:
now_village+=K
```

(1)進んだ間の村に友達がいるか確認する
(2)友達がいればもらえるお金分先に進む
ここから友達の情報を村0に近い順に一人ずつ確認し、進んだ間の村に友達がいたか確認していく。
つまりi番目の友達が今いる村より前の村にいたか確認していく。

```python:
for i in range(N):
    friend_village=friends[i][0]
    friend_money=friends[i][1]

    if friend_village<=now_village:
        now_village+=friend_money
    else:
        break
```

friend_villageはi番目の友達がいる村の番号。
friend_moneyはi番目の友達がくれるお金。

・friend_village<=now_villageの場合
その場所に来るまでに友だちに会っている。
お金を受け取った分更に進む。
・そうでない場合(now_village<friend_village)
i番目の友達、そしてその先の村にいる友だちにも会えないため、そこでfor文を抜ける。

最後にnow_villageを出力して終わり。

```python:
print(now_village)
```

**【コード全文】**

```python:
N,K=map(int, input().split())

friends=[]
for i in range(N):
    A,B=map(int, input().split())
    friends.append([A,B])

friends.sort()

now_village=0

now_village+=K

for i in range(N):
    friend_village=friends[i][0]
    friend_money=friends[i][1]
 
    if friend_village<=now_village:
        now_village+=friend_money
    else:
        break

print(now_village)
```
**この記事は拙著「AtCoder　凡人が『緑』になるための精選50問詳細解説」のサンプルです**
価格：100円
kindle：https://www.amazon.co.jp/dp/B09C3TPQYV
booth(pdf)：https://sano192.booth.pm/items/3179185

前：https://qiita.com/sano192/items/1eb1ee760594502280fd
次：https://qiita.com/sano192/items/f6e495d5d63b46bde374
firebase .env ファイルの雛形

.env ファイルの雛形

REACT_APP_FIREBASE_API_KEY=""
REACT_APP_FIREBASE_AUTH_DOMAIN=""
REACT_APP_FIREBASE_DATABASE=""
REACT_APP_FIREBASE_PROJECT_ID=""
REACT_APP_FIREBASE_STORAGE_BUCKET=""
REACT_APP_FIREBASE_SENDER_ID=""
REACT_APP_FIREBASE_APP_ID=""
REACT_APP_FIREBASE_MEASUREMENT_ID=""
【サーバ構築練習】Mac + Docker + CentOS8

普段、サーバ構築の練習にはEC2を利用しています。

しかし、コンソール画面にログインするのが億劫になってきました。

そこで、ローカルで手軽にサーバ構築できないかと調べました。

結果、Mac + Docker + CentOSでなんとかなりそうなので、
その手順を自分用にまとめます。

#Dockerインストール
まずはDockerをインストール。
https://docs.docker.com/docker-for-mac/install/

次にDocker Hubのアカウントを作成します。
https://hub.docker.com/

Dockerアプリを起動し、
先ほど作成したアカウントでログインしておきます。

あとはターミナルから操作していきます。

#イメージをプル
以下のコマンドでCentOS8のイメージを取得します。

```Bash:
docker pull centos:centos8
```

イメージが取得できたことを確認します。

```Bash:
docker images
```

#コンテナを生成 + 起動

以下のコマンドでCentOS8のコンテナ（hogemoge）を生成し、起動します。

```Bash:
docker run --detach --name hogemoge --privileged --publish=8080:80 centos:centos8 /sbin/init
```

上記設定では、ホストの8080番ポートをコンテナ内の80番ポートに転送しています。

例えば、コンテナ内でApacheをインストールした場合、ブラウザで次のようにアクセスすれば、コンテナ内のApacheに繋がります。
http://localhost:8080

#コンテナにログイン
以下のコマンドでコンテナ（hogemoge）にログインします。

```Bash:
docker exec -it hogemoge bash
```

これで、Dockerを用いてサーバ構築の練習ができるようになりました。

#その他コンテナ操作

コンテナからログアウトする時は以下のコマンド。

```Bash:
exit
```

起動中のコンテナ一覧

```Bash:
docker ps
```

全てのコンテナ一覧

```Bash:
docker ps -a
```

コンテナの停止

```Bash:
docker stop hogemoge
```

コンテナの起動

```Bash:
docker start hogemoge
```

ちなみに、CentOS8ではyumコマンドではなくdnfコマンドを利用するみたいです。

また、自身でイメージを作成し、Docker Hubにプッシュしておくことで、いつでもその時の環境を再現することができるようなので、色んなサーバのテンプレートを用意するのも面白そうだなと思っています。

#参考資料
https://qiita.com/niwasawa/items/85a78aaa8c5234d98fb4
PASSWORD_ROLLOVER_TIMEを試す（Oracle Database 19c/19.12)

Oracle Database 19cはRelease Update (RU)の単位で新機能を追加する傾向があります。
すこし前（7月20日）に登場したRE 19.12に含まれる新機能の一つについて解説します。
マニュアル [New Features Guide](https://docs.oracle.com/en/database/oracle/oracle-database/19/newft/ru-19.12-features.html) で説明されています。

# PASSWORD_ROLLOVER_TIME
Oracle Database ユーザーに対してパスワードの複雑性や有効期限等を制御する機能に PROFILE があります。PROFILE は古い Oracle Database から存在する機能で、長い間新しい機能が追加されませんでした。RU 19.12 では PROFILE リソースとして PASSWORD_ROLLOVER_TIME が追加されました。この設定はパスワードを変更した際に古いパスワードも一定時間（日）有効にする機能です。この機能により、あらかじめデータベース・ユーザーのパスワードを変更しておくことができ、クライアントから接続できない時間をなくすことができます。

# 設定方法
PASSWORD_ROLLOVER_TIME リソースを変更します。

```SQL: プロファイルの作成
SQL> CREATE PROFILE PROFILE1912 LIMIT PASSWORD_ROLLOVER_TIME 1;

プロファイルが作成されました。
```

ユーザーを作成し、プロファイルを指定します。ユーザーは CREATE USER 文で指定したパスワードを使って接続できます。

```SQL: ユーザーの作成と接続
SQL> CREATE USER apuser1 IDENTIFIED BY password1 PROFILE PROFILE1912;

ユーザーが作成されました。

SQL> GRANT CREATE SESSION TO apuser1;

権限付与が成功しました。

SQL> CONNECT apuser1/password1
接続されました。
```

パスワードを変更しますが、クライアントは旧パスワードでも／新パスワードでも接続できます。

```SQL: パスワードの変更と旧パスワードによる接続
SQL> ALTER USER apuser1 IDENTIFIED BY password2;

ユーザーが変更されました。

SQL> CONNECT apuser1/password1
接続されました。
SQL>
SQL> DISCONNECT
Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production
Version 19.12.0.0.0との接続が切断されました。
SQL>
SQL> CONNECT apuser1/password2
接続されました。
SQL>
```
2021-08-01 Godot Engine > Bind: 二つの引数の受け取りと使用 > extra_arg_1を足す

### 動作環境

```
Windows 10 Pro v21H1
Godot Engine v3.3.2.stable.official
```

### Bind

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32870/fa815d7e-1b4a-bdd3-711b-d9ff5b8ddd47.png)

Bindで二つのVector2を定義したが、Scriptの方でどう使うか分からなかった。

```python:Main.gd
func _on_Button_pressed(extra_arg_0):
	print(extra_arg_0)
```


### 自分で引数を足す

extra_arg_0の後ろにextra_arg_1を足すことで処理ができた。

```python:Main.gd
func _on_Button_pressed(extra_arg_0, extra_arg_1):
	print(extra_arg_0)
	print(extra_arg_1)
```

実行例

```
(3, 1)
(4, 1)
```
AtCoderログ：0048 - ABC 212 に参加しました

この記事は 2021年07月31日(土)21:00-22:40 に開催された [AtCoder Beginner Contest 212 (ABC 212)](https://atcoder.jp/contests/abc212) に参加した記録です。

https://atcoder.jp/contests/abc212

# 結果
- 得点：300 (=100+200)
- 時間：13:49 (= 13:49 + 05:00 × 0)
- 順位：5159 / 7893
- パフォーマンス：281
- レーティング：270 → 268 (-2)
- 段級位：9級

![2021-07-31_abc212.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1589302/f19883a3-1081-a680-091c-bc86a424ce3a.png)

# 詳細
|問題|得点|時間|提出コード|復習|
|:--:|:--:|:--:|:--:|:--:|
|[ABC 212 A](https://atcoder.jp/contests/abc212/tasks/abc212_a)|100|03:43|[○](https://atcoder.jp/contests/abc212/submissions/24649481)|[AtCoderログ：0050](https://qiita.com/RoadLynton/items/2d02c750d4977ba2a159)|
|[ABC 212 B](https://atcoder.jp/contests/abc212/tasks/abc212_b)|200|13:49|[○](https://atcoder.jp/contests/abc212/submissions/24658498)|[AtCoderログ：0052](https://qiita.com/RoadLynton/items/6bfd748018f6872da0d6)|
|[ABC 212 C](https://atcoder.jp/contests/abc212/tasks/abc212_c)|300|(6)|---|---|
|[ABC 212 D](https://atcoder.jp/contests/abc212/tasks/abc212_d)|400|---|---|---|
|[ABC 212 E](https://atcoder.jp/contests/abc212/tasks/abc212_e)|500|---|---|---|
|[ABC 212 F](https://atcoder.jp/contests/abc212/tasks/abc212_f)|600|---|---|---|

# 感想
- A, B を完答、C で悩んでいる間に終了となってしまいました。
- A、B は素直に回答して正解。
- C は全検索から無駄を省いている打ちに頭がこんがらがってしまい、TLEとWAを連発してしまいました。
- レーティングもちょっとだけ下がってしまいました。悲しいです。

# リンク
## ABC 212 関連の記事
- [AtCoderログ：0050 - ABC 212 A](https://qiita.com/RoadLynton/items/2d02c750d4977ba2a159)
- [AtCoderログ：0052 - ABC 212 B](https://qiita.com/RoadLynton/items/6bfd748018f6872da0d6)

## 前後の記事
- 前の記事 → [AtCoderログ：0047 - ABC 090 B](https://qiita.com/RoadLynton/items/e7ebc6c16c78829489ef)
- 次の記事 → [AtCoderログ：0049 - AGC 025 A](https://qiita.com/RoadLynton/items/d8b0dec4d519931856f2)
【SAP-C01試験対策】設問をイメージするためにDraw.ioで構成図を作成した

# はじめに
AWSの認定試験を受験するにあたり、設問から構成図をイメージできるようになるために以下の教材で学習したので、内容をまとめます。

https://www.udemy.com/course/webapplication-on-aws/


# ① アイコンをダウンロードして作成

ブラウザで「AWS アイコン」で検索し画面のリンクをクリック

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/951272/48b2c41f-5f6b-27c8-a754-f180f0afe317.png)

「PowerPoint 用ツールキットのダウンロード」をクリック

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/951272/3735e523-f598-ef1f-4ad4-c9f2631210b4.png)

ファイルを解凍してパワーポイント等で構成図を作成する。

# ② 3rd partyのツールを利用して作成

アドレスバーで「draw.io」を入力しエンターキーを押下

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/951272/b974c527-5dc3-33dc-2648-106e9b172224.png)

好きな保存先を選択

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/951272/9e8fdf93-b80a-e5a1-49a8-0dd975811916.png)


好きなファイルを選択し「Create」をクリック(今回は「Blank Diagram」を選択)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/951272/a64be44b-89f0-e873-c943-84e94d098e20.png)

一番左下の「+More Shapes...」を選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/951272/b3c5081c-30b1-b763-1d48-c5872cae42e7.png)

AWSを追加してApplyをクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/951272/9c681e0d-74f5-8e21-88f8-e10dd69c8664.png)

# 試しに作ってみる
[参考講座](https://www.udemy.com/course/webapplication-on-aws/)のプレビューの「本講座で作成するWebアプリケーション」から見れる構成図を真似して作成した。

![sample.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/951272/b180fe92-fa15-a248-47c8-0292e1c5183c.png)


# 終わりに
* 設問から構成図をイメージできるようになったと思います。
* 自分でWebサービスの構成図を作成することで、サービスの構成の理解が深まりました。