OmniAICreator/Qiita-1.07M huggingface dataset:
pythonで複数行サイズ指定読み込み

## pythonで複数行読み込み
pythonでファイルを複数行に分けて読み込みたくて試してみたので備忘録として残しておきます。

<結果>
  サイズ指定は文字数で途中までの文字数を指定するとその行の
最後まで読み込む。
  改行はカウントされない。ゼロは全行読み込む
　

<環境>
バージョン：Python 3.8.5
windows 10

<詳細>
ファイルの内容
> abcdefghijklmnopqrstuvwxyz
12345678901234567890
ABCDEFGHIJKLMNOPQRSTUVWXYZ

``` 1行目の途中を指定.py
def readlines( fname ):
    with open( fname, 'r') as f:
        readData = f.readlines( 20 )
    return readData

if __name__ == '__main__':
    fname = './Dmy.txt'
    readData = readlines( fname )
    print( f' readData = {type(readData)} {readData}')
```
<出力>
> readData = \<class 'list'> ['abcdefghijklmnopqrstuvwxyz\n']

``` 2行目の途中を指定.py
def readlines( fname ):
    with open( fname, 'r') as f:
        readData = f.readlines( 30 )
    return readData

if __name__ == '__main__':
    fname = './Dmy.txt'
    readData = readlines( fname )
    print( f' readData = {type(readData)} {readData}')
```
<出力>
> readData = \<class 'list'> ['abcdefghijklmnopqrstuvwxyz\n', '12345678901234567890\n']

<u>ファイルの内容</u>
> あいうえおかきくけこさしすせそ
12345678901234567890
ABCDEFGHIJKLMNOPQRSTUVWXYZ

``` 1行目の改行前を指定.py
def readlines( fname ):
    with open( fname, 'r') as f:
        readData = f.readlines( 15 )
    return readData

if __name__ == '__main__':
    fname = './Dmy.txt'
    readData = readlines( fname )
    print( f' readData = {type(readData)} {readData}')
```
<出力>
> readData = \<class 'list'> ['あいうえおかきくけこさしすせそ\n']

``` 1行目の改行を指定のつもりだけど２行目の先頭.py
def readlines( fname ):
    with open( fname, 'r') as f:
        readData = f.readlines( 16 )
    return readData

if __name__ == '__main__':
    fname = './Dmy.txt'
    readData = readlines( fname )
    print( f' readData = {type(readData)} {readData}')
```
<出力>
> readData = \<class 'list'> ['あいうえおかきくけこさしすせそ\n', '12345678901234567890\n']

``` 0を指定.py
def readlines( fname ):
    with open( fname, 'r') as f:
        readData = f.readlines( 0 )
    return readData

if __name__ == '__main__':
    fname = './Dmy.txt'
    readData = readlines( fname )
    print( f' readData = {type(readData)} {readData}')
```
<出力>
> readData = \<class 'list'> ['あいうえおかきくけこさしすせそ\n', '12345678901234567890\n', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ']
【git】git で間違えてmaster branch で作業を進めてしまったときに作業ブランチに移る

# はじめに

今日も同じ作業を行うために調べたので、メモします。

- 作業内容をmaster branch に merge した後、ローカルでmaster branch でそのまま作業してcommit を進めてしまった。
- master branch をもとのコミットまで戻り、作業ブランチを作り、そこに作業分を反映させたい

ということでございます。

方針は単純です。

- 間違って進めてしまった分をpatch として保存する
- master branch でのcommit をさかのぼり、新しいブランチを作る。
- 先に保存したパッチを充てる。
- 間違って進めてしまった master branch のコミットを消す。

# 動作確認

間違ってmaster branch でコミットを進めてみます。

- 初めに、file001だけがcommit された状態だとします。

```shell-session
$ git log --oneline
* 3e9769f first commit
```

- その後、file002, file003 をそれぞれmaster branch のまま追加してしまったとします。

```terminal
$ git add file002
$ git commit -m "file002"
$ git add file003
$ git commit -m "file003"
$ git log --oneline --graph --all
* 3b570c0 (HEAD -> master) file003
* dc5ed63 file002
* 3e9769f first commit
```

- ここで「しまった。作業ブランチを切って作業するつもりだったのに。。。」と気が付きます。

まず、差分をpatch として保存します。ただの git diff です。こういう設計は素晴らしい！


```shell
$ git diff HEAD^^ > tmp.path
```

戻って作業ブランチを作ります。

```shell
$ git checkout HEAD^^
HEAD is now at 3e9769f first commit
$ git checkout -b dev/test
Switched to a new branch 'dev/test'
```

- すかさずpatch を当てます。

```shell
$ patch -p1 < tmp.patch
patching file file002
patching file file003
```

- ここで本来行いたかったコミットをします。

```shell
$ git add file002 file003
$ git commit -m "add file002 and file003"
```

- ですが、さきほど誤って進めてしまった master branch のcommit が残っている。

```shell
$ git log --oneline --graph --all
* c7a2497 (HEAD -> dev/test) file002 file003
| * 3b570c0 (master) file003
| * dc5ed63 file002
|/
* 3e9769f first commit
```

- 消そう。まず master branch に戻ろう。

```shell
$ git checkout master
Previous HEAD position was 3e9769f first commit
Switched to branch 'master'
$ git log --oneline --graph --all
* c7a2497 (dev/test) file002 file003
| * 3b570c0 (HEAD -> master) file003
| * dc5ed63 file002
|/
* 3e9769f first commit
```

- コミットを消す。というのは、直前のコミットにリセットする、と指示するらしい。

```shell
$ git reset --hard dc5ed63
HEAD is now at dc5ed63 file002
$ git log --oneline --graph --all
* c7a2497 (dev/test) file002 file003
| * dc5ed63 (HEAD -> master) file002
|/
* 3e9769f first commit
```

- 更にもう一度reset すると、問題のcommit は無くなる。

```shell
$ git reset --hard HEAD^
HEAD is now at 3e9769f first commit
$ git log --oneline --graph --all
* c7a2497 (dev/test) file002 file003
* 3e9769f (HEAD -> master) first commit
```

- これで晴れて作業ブランチに戻ろう

```shell
$ git checkout dev/test
Switched to branch 'dev/test'
$ ls
file001  file002  file003  tmp.patch
```

# まとめ

所望の作業はこれでできた。

- 実は注意点として、問題のコミットを消すときにはちゃんとmaster branch に戻ることが必要でした。戻らずに git resetを行ったら、新作の作業ブランチと問題のコミットの残るmaster branchが接続されてしまいました。そこのところの仕組みが分かっていませんが、（おい！苦笑）、気を利かせてくれたんだと思います。明示的に一歩一歩消すために、master branch の先頭のcommit から一歩一歩戻りました。
- ```git reset --hard``` が ```git reset --soft``` か選択肢がありますが、ここでは、必要な状態は dev/test にすべて保存されているので、作業領域ではhard で消してしまってよい、と考えました。

もっと簡単に行う方法もきっとあるのだと思いますが。まずはこのあたりで。
(2020/10/22)
[/etc/shadow] ファイル　LPIC学習メモ

[/etc/shadow] ファイル

ログインユーザーのパスワード管理に利用。
従来は[/etc/passwd]が使用されていたが、一般ユーザでも閲覧が出てきしまうため、パスワードのデータを一般ユーザから見えないようするための仕組み（車道パスワード）として利用されている。

[/etc/shadow]が利用されている場合は、
[/etc/passwd]ファイルのパスワード欄には
‘x’または暗号化された13桁の文字列が並んでいるが、暗号化の文字列はシステムで判断して[/etc/shadow]も利用するので、システム管理者は二種類の文字が混ざっていても意識しなくても良い。

権限のあるユーザーしか閲覧できないように、パーミッションが設定してあり、ファイル内には以下の項目が設定されている
第1フィールド：ログイン名
第2フィールド：パスワード
第3フィールド：パスワードの最終変更日
第4フィールド：変更が可能になる最短期間
第5フィールド：未変更でも利用可能な最長期間
第6フィールド：パスワード未変更者への警告日
第7フィールド：未ログイン時、アカウントが無効になる日数
第8フィールド：アカウントの失効日
第9フィールド：フラグ（未使用）

第２フィールドには、
設定されたパスワードか[!!]が設定される
[!!]が設定されている場合は、パスワードが未設定状態
備忘録No.1「配列内の重複した値を数えて表示」[Java]


#コード
```java

    public static void main(String[] args) {
        // TODO 自動生成されたメソッド・スタブ

        String [] array = {"y","x","x","y","z","y","x","y","z","z"};
        List<String> list = new ArrayList<>(Arrays.asList(array));
        TreeSet<String> ts = new TreeSet<>(list);

        for(String s : ts) {
            int count = 0;
            for(String ss : list) {
                if(s.equals(ss)) {
                    count++;
                }
            }
            System.out.println(s + " " + count);
        }
    }
```

#出力結果
x 3
y 4
z 3

#ポイント
・出力結果まで重複して表示させないように、コレクションのSetを使った。
・表示する順番をアルファベット順にしたかったので、TreeSetを使った。
・Arrays.asList()で、配列をリストとして返した。
【JavaScript】ドット記法・ブラケット記法について

JavaScriptにはオブジェクトのプロパティにアクセスする方法が2種類あります。
使い方や違いを理解できたのでメモしたいと思います。

#ドット記法
`` .（ドット）``を使ってプロパティにアクセスする記法です。


```js
const obj = {};
obj.name = 'taka'; // objのプロパティをセット
obj.hello = function(){
    console.log(`Hello,${this.name}`);
}

console.log(obj.name); // taka
obj.hello(); // Hello,taka
```
オブジェクトとプロパティを```.（ドット）```で繋げることで、値の取得やメソッド実行ができます。


#ブラケット記法
[ ]（ブラケット）を使ってプロパティにアクセスする方法です。

```js
const obj = {};
obj['name'] = 'taka'; // objのプロパティをセット
obj['hello'] = function(){
    console.log(`Hello,${this.name}`);
}

console.log(obj['name']); // taka
obj['hello'](); // Hello,taka

```
メソッドも[ ]の後に()を付けて実行できます。


#ドット記法とブラケット記法の違い
**ブラケット記法は、プロパティ名に変数を与えることができます。**ドット記法ではできません。

```js
const propName = 'name'; // 変数を用意

const obj = {};
obj[propName] = 'taka'; // プロパティ名に変数を使う
console.log(obj); // {name: "taka"}

obj.propName = "taka"; //ドット記法で変数を使ってもプロパティ名になる
console.log(obj); // {name: "taka",propName: "taka"}
```
そのため、ブラケット記法はループなど動的にプロパティ名を変更したい場合に便利です。

例えば、配列にキーを格納して、オブジェクトの中身を取得した時など。

```js
const key = ['html','css','javascript'];
const obj = {
    html:'骨組み',
    css:'装飾',
    javascript:'動作'
}

key.forEach(function(key){
    let result = key + 'は、Webページの' + obj[key] + 'を作ります。'
    console.log(result);
});
```


#まとめ
ドット記法とブラケット記法の特徴、違いを記述しました。
2種類の方法がありますが、可読性の点から不必要に混在して使わない方が良さそうですね。


#参考
https://ichigo-pantsu.site/javascript-dot-bracket/
https://harakotan.hatenablog.jp/entry/2015/05/17/004707
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Property_Accessors
Goで再帰クロージャを実装

# はじめに
Go言語の勉強version2ということで、アルゴリズムの勉強をGoでやってみたというものです。

## 再帰クロージャ
まず、クロージャとはなのですが、下記に参考にした文を記載致します。

>Goの無名関数は「クロージャ」です。クロージャは日本語では「関数閉包」と呼ばれ、関数と関数の処理に関係する「関数外」の環境をセットにして「閉じ込めた(閉包)」ものです。

ということで、無名関数を表しています。今回はこれを使用し再帰関数を作成してみることから始めました。


1. 下記に階乗を計算する関数[func fact (n int) int]を作成してみました。

```fact.go
package main

import (
	"bufio"
	"fmt"
	"os"
)

func main() {
	r := bufio.NewReader(os.Stdin)
	w := bufio.NewWriter(os.Stdout)
	defer w.Flush()

	var a int

	fmt.Fscan(r, &a)

	result := solveFact(a)
	fmt.Print(result)
}

func solveFact(x int) int {
	var fact func(n int) int
	 fact = func(n int) int{
		if n == 0{ return 1 }
		return n * fact(n - 1)
	}
	return fact(x)
}
```

実行結果

```
❯ go run fact.go
4
24
```

2. フィボナッチ数列を計算する関数[func fib (n int) int]を作成してみました。

```fib.go
package main

import (
	"bufio"
	"fmt"
	"os"
)

func main() {
	r := bufio.NewReader(os.Stdin)
	w := bufio.NewWriter(os.Stdout)
	defer w.Flush()

	var a int

	fmt.Fscan(r, &a)

	result := solveFib(a)
	fmt.Print(result)
}

func solveFib(x int) int {
	var fib func(n int) int
	fib = func(n int) int{
		if n <= 1{return n}
		return fib(n -1) + fib(n - 2)
	}
	return fib(x)
}
```

実行結果

```
❯ go run fib.go
10
55
```

## まとめ
入出力には、fmt.Scan()/fmt.Printf()を使用しています。ただ、入出力量が怖いので、bufioを使用し、バッファリングするようにしています。
Linuxコマンド[ldconfig] LPIC学習メモ

LPIC学習メモ
[ldconfig]

既存ライブラリのアップデートや、新しいフォルダの下に新しいライブラリを配置してのアップデートを行う場合のコマンド。

プログラムソースのライブラリ化のメリット
ソースの中で同一の動作（機能）を実行する記述が複数回存在するとき。
重複しているその機能を単体の機能として出すことで、以下のメリットがあげられる。
・プログラムの中の同一部分を外に出すことで、ソース内の冗長を取り除くことができる。（可視化性がよくなる）
・単体のプログラムにしたことで、呼び出し側のテストの工数を削減できる。
・改修時にも一箇所を直すだけですみ、その機能を呼び出す全てのプログラムに手を入れないで済むことで、改修ミスを減らせる。

ライブラリの種類
・静的ライブラリ　実行ファイルの中に取り込まれる
・動的ライブラリ　実行時にライブラリがメモリに取り込まれて、使用される。

プロフグラム実行時には、共有ライブラリがどこにあるか読み込むリンク/ローダがキャッシュから位置を探索するが、ldconfigコマンドを利用すると、/etc/id.so.confファイルを参照して、適切なシンボリックファイルを作成して、キャッシュに記載してくれる。
このため、共有ライブラリを使用する際にスピードアップが期待できる。（ldconfigを使うメリット）

ただし、デフォルト以外の場所への追加やパスの移動があった場合は、変更を認識してシンボリックファイルが自動的に変更されるわけではないので、/etc/id.so.confファイルを更新してから、ldconfigを再度実行する必要がある。

ldconfigは、ファイル名とヘッダ部分からどのバージョンにリンクを更新するか判断するため、オプションによって制御を行うことが必要となる


オプション
-v
詳細表示モード。 現在のバージョン番号を表示。
各ディレクトリを確認し、リンクを生成する度に内容を表示

-p
現在のキャッシュに保存されているディレクトリのリストと、 ライブラリの候補を表示。

-n
コマンドラインで指定されたディレクトリのみを処理。
デフォルトで共通ライブラリが置かれるディレクトリ、/etc/ld.so.conf に指定があっても、無視される。

-N
キャッシュを再構成しない。リンクは更新。

-X
リンクを更新しない。 キャッシュのみ再構成。

-f conf
文字列conf を /etc/ld.so.conf の代わりに利用。

-C cache
文字列cache を /etc/ld.so.cache の代わりに利用。

-r root
文字列root をカレントディレクトリとして実行。

-l
それぞれのライブラリを手動でリンク。（慣れないうちはミスが発生しやすいので、慣れるまでは使わない方がよい）
OSやPythonのバージョンに関わらずOpenSeesPyを使う

## （モチベーション）
OpenSeesPyを利用しようと思ったところ、環境構築方法がOSによって違っていたり、Pythonバージョンによっては使えなかったり、使えるようになったとしても、VSCodeのインタープリターがOpenSeesPyのメソッドを解釈してくれなかったりと始めるまでに苦労しました。そのため、何かそれらに関わらず簡単に環境構築できる方法を考えました。

## この記事について
* [Docker](https://www.docker.com/)を使ってOSに関わらず環境構築
* [VSCode](https://code.visualstudio.com/)の[Remote Development](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.vscode-remote-extensionpack)を使って、Dockerコマンドとか使わなくても簡単環境構築
* OpenSeesPy？インストールされるようにコード書いといたから、それを使えばOK

## OpenSeesPyとは
[OpenSees](https://opensees.berkeley.edu/)は、Open System for Earthquake Engineering Simulationの略で、地震時における構造物や地盤の構造解析などで利用されているオープンソースのフレームワークです。OpenSeesPyは、それをPythonで利用するためのインタープリターです。

## Dockerとは
Dockerはコンテナ仮想化によって、異なる環境でも同じ環境を構築することができるツールです。構築方法をファイルに記述しておけば、OSはもちろん、指定したバージョンのPythonのインストールやその他のライブラリのインストールなども、それはもうドッカーンと作成して実行してくれます。ドッカーンと。

## 手順
### 必要なもの
Docker Desktop
https://www.docker.com/products/docker-desktop

VS Code
https://code.visualstudio.com/

Remote Development(VS Codeにインストール)
https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.vscode-remote-extensionpack

### Dockerfileを準備
DockerはDockerfileという名前のファイルを準備しておくとそれを元に環境を構築してくれます。今回は以下のように準備しました。主にやっていることは、

1. Pythonインストール（バージョン3.8。linux環境も一緒に入る）
2. pipをインストール
3. pypiの必要パッケージをインストール

```Docker
# Dockerfile
FROM python:3.8

RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app

RUN apt-get update
RUN apt-get install -y --no-install-recommends \
    python3-dev \
    python3-pip

ADD . /usr/src/app/
RUN pip install -r requirements.txt

CMD bash
```

### requirements.txtを準備
Dockerfileに記載した通り、 `pip install -r requiremennts.txt` してパッケージをインストールするので、必要なパッケージを予め書いておきます。今回はOpenSeesPyを始めたいので、openseespyをお忘れなく。（てか、もはやOpenSeesPyに限らない方法なんだが）

```python
# requrements.txt（例）
matplotlib==3.3.2
numpy==1.19.2
openseespy==3.2.2.5
pylint==2.6.0
```

Dockerfileとrequirements.txtは何かフォルダを作って、その中に入れておきます。

### ドッカーン
VS Codeで開いて、VS Codeの左下の青いボタン（以下の図参照）を押します。そして、"Remote-Containers: Open Folder in Container..."を選択し、Dockerfileとrequirements.txtを用意したフォルダ選べば環境の構築完了です！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/187774/88df86b5-478b-9be8-a4d3-e86db39ff677.png)

と言いたいところですが、このままだとまだOpenSeesPyのメソッドをVS Codeが理解してくれません。
開いた後に、.devcontainerというフォルダと、その中にdevcontainer.jsonというファイルが入っていると思います。それに、以下のように記述を追加しておけば今度こそ完成です！

```
# devcontainer.json
{
    ...
	"settings": { 
		...
		"python.linting.pylintArgs": ["--extension-pkg-whitelist=openseespy.opensees"] <- 追加
	},
	"extensions": [
		"ms-python.python" <- 追加
	]

}
```

# 完成したコード
できたものをすでに用意してありますので、差し替えます。（料理番組風）
https://github.com/kakemotokeita/openseespy-docker-vscode

良かったら使ってください。個人的に好みのものも入れてしまっているので、適宜不要なものは消してください。

間違っている点やうまくいかなかった点などあれば優しく教えてください。

ちなみに、OpenSeesは立体の構造解析が可能ですが、質点系の振動解析のみの場合なら、簡単な設定で使えるパッケージを作成しました。良かったら、こちらも使ってください。
https://github.com/adc21/asva

よろしくお願いします。
AWXコンテナで仮想(venv)環境作成

# はじめに
AWXには複数バージョンのAnsibleを使い分けられる機能があります。
![スクリーンショット 2020-10-21 22.57.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274613/2c2dcd44-2495-fdac-bdfa-d91de7e43fc8.png)
↑こんなの。

AWXをKubernetesクラスタ上で動かすときに、複数バージョンのAnsibleを予め仕込んだコンテナを作るにはどうしたらいいんだろう？、と思っていたら[公式ドキュメントに答えが載ってました。](https://github.com/ansible/awx/blob/devel/docs/custom_virtualenvs.md)

備忘を兼ねて、試した記録を残しておきます。

# 環境
k3s v1.18.9+k3s1
AWX 15.0.0

# やりかた
以下のようなYAMLを作ります。

```venv_vars.yaml
---
custom_venvs:
  - name: 2.9.12
    python: python3
    python_ansible_version: 2.9.12
  - name: pip
    python_ansible_version: 2.9.13
    python_modules:
      - pyvmomi
```
各項目の内容は以下のとおり。

| key | 説明 | 必須 |
|:-----------|:------------|:------------:|
| name | 仮想環境の名前（ディレクトリ名） | Yes         |
| python_ansible_version     | Ansibleのバージョン      | Yes       |
| python | Pythonのバージョン（指定のない場合は２系） |          |
| python_modules | 導入したいpipモジュール |          |

あとはデプロイ時のansible-playbookコマンドに以下のようにextra-varsオプションを加えるだけです。

```bash
$ ansible-playbook -i inventory install.yml --extra-vars "@venv_vars.yaml"
```
デプロイ完了後、Podが起動完了するまで以下のような状態で少し時間がかかります。

```bash
$ kubectl get pod -n awx
NAME                          READY   STATUS     RESTARTS   AGE
awx-postgresql-postgresql-0   1/1     Running    0          2d19h
awx-64f65bbdc4-qcbtm          0/3     Init:0/1   0          66s
```

# AWX上の設定
Podが起動したらAWXにログインし、「設定」->「システム」を開きます。
![スクリーンショット 2020-10-21 23.30.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274613/b4b913ee-e76f-1d9e-9802-b6e703b8b5ad.png)
「カスタムの仮想環境パス」に『/opt/custom-venvs<改行>』と入力し、保存します。
行末尾に改行を入れないといけないようなので忘れずに入れましょう。
※このパスは任意のパスに変更可能です。変更したい場合はinventoryファイルのcustom_venv_dirで定義できます。

設定が完了すると、プロジェクトの設定画面に「ANSIBLE 環境」というリストが表示され、作成した仮想環境が選択可能になります。
![スクリーンショット 2020-10-21 23.59.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274613/5c582a61-828c-fad5-9cc3-f8de06c75d50.png)

Ansible 2.9.12の環境を指定した上で、適当に以下のようなPlaybookを作って実行してみると、

```show_version.yaml
---
- name: Sample
  hosts: all
  tasks:
    - name: Show Ansible Version
      debug:
        msg: "{{ ansible_version }}"
```

2.9.12として実行されました。（AWX 15.0.0のデフォルトAnsibleバージョンは2.9.13です）
![スクリーンショット 2020-10-22 0.01.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274613/a1c13cf9-2241-0616-8c64-708cf9c73d6d.png)

# どういう仕組み？
どうやって仮想環境をセットアップしてるのか気になったので調べてみました。
Kubernetes用の[Deployment.ymlテンプレートファイル](https://github.com/ansible/awx/blob/devel/installer/roles/kubernetes/templates/deployment.yml.j2)の中で、

1. custom_venvsの変数定義が存在したら、initContainersでCentOS7のコンテナを起動
2. custom_venv_dirで定義されたディレクトリをEmptyDirとしてマウント
3. その上にvirtualenvで仮想環境を構築
4. 同じディレクトリをawx-web,awx-taskコンテナでもマウント

という流れでインストールを実施しているようです。
これならPodが落ちても仮想環境は再起動時に作られるので安心です。

# おわりに
当初はコンテナのリビルドが必要だと思っていましたが、比較的簡単に実現できる方法が用意されていてよかったです。

また、AWXの設定は以下サイトを参考にさせていただきました。
[[Ansible/AWX] AWX でカスタムの仮想環境（venv）パスを認識させる方法](https://tekunabe.hatenablog.jp/entry/2020/04/05/ansible_awx_custom_venv)
UniTaskでDoTweenを使う In 2020/11

# 使用バージョン
- Unity 2020.1.1f1
- UniTask 2.0.37

# 結論
UniTaskとDoTweenをプロジェクトにインポートして
BuildSettings → PlayerSettings → Playerの
Scripting Define Symbols
に
`UNITASK_DOTWEEN_SUPPORT`
を追加するだけ！
![UniTask1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/49946/b99c225b-9a52-f581-f503-e2df3e6e6167.png)

# 使い方
通常のUniTaskと同じようにasyncのメソッドに対してDoTweenの処理をawaitすればawaitされます

# サンプルコード

```
using System.Collections;
using System.Collections.Generic;
using Cysharp.Threading.Tasks;
using DG.Tweening;
using UnityEngine;

public class MoveTask : MonoBehaviour
{
    // Startはasyncにできる
    async UniTask Start()
    {
        await transform.DOMoveX(1.0f, 1.0f);
        await transform.DOMoveX(-1.0f, 2.0f);
    }
}

```

# サンプルの動作
![UniTask.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/49946/bfd11022-8197-aa27-acde-5df76f5aaf40.gif)
if文を使う時に気をつけたいこと

#はじめに
　毎日、Rubyの問題を解いている中で、if文を使うことが圧倒的に多いので、考え方をまとめておく。

##if文を使う時に考えたいこと
1. 戻り値が真偽値かどうか
2. 条件文の順番
3. elseを追加するかどうか

###1. 戻り値が真偽値かどうか
　条件文には、真偽値が入るようなものでなければならない。
例えば、

```ruby
str = "abc"
if str.include?("a")
  puts "OK"
end

if str
  puts "NG"
end
```
`include?メソッド`は引数にしたものがあれば、`true`をなければ、`false`を返す。
そのため、一つめのif文では`a`があるため、`true`が返り、処理が実行される。
しかし、二つめのif文では条件文のところがただの変数のため、うまくいかない。

<font color=red>条件文がtrueかfalseの判断をするメソッドか考える</font>
###2. 条件文の順番

```ruby
if 条件文1
  #条件文1がtrueのときの処理
elsif 条件文2
  #条件文2がtrueのときの処理
else
  #条件文1と条件文2のどちらにも当てはまらない時の処理
end
```


<font color=red>条件文を書く順番はとっても大事！</font>
上から順番にコードが読まれるため、条件文2は条件文1がtrueのときには読み込まれることがない。

###3. elseを追加するかどうか
どの条件にも当てはまらないときに処理をしたいなら、つける。条件文が一つだけで、その処理をするかどうかだけでよければ、<font color=red>`else`は付けなくても大丈夫。</font>
ハッシュタグ検索で入力される文字をいい感じに置換させるメソッド集

# はじめに
## やりたいこと
- ハッシュタグ検索で入力された全角ハッシュを半角に変換したい
- 全角数字を半角にしたい
- そもそもハッシュを取り除いて検索させたい

## やる理由
- ユーザーがどんな入力の仕方をしてもいい感じに置き換えて出力したい

# メソッド
- trメソッド
- deleteメソッド

## trメソッド
第一引数を第二引数に変換するメソッド

```ruby:sample.rb
str = '一郎'
puts str.tr('一', '二')

# 結果 => 二郎
```
これをモデルに定義したsearchアクションに追記

デフォルトのsearchアクション

```ruby:models/hashtag.rb
def self.search(search)
  if search != ""
    Hashtag.where('name LIKE(?)', "%#{search}%")
  else
    Hashtag.all
  end
end
```

trメソッドを追記したsearchアクション

```ruby:models/hashtag.rb
def self.search(search)
  if search != ""
    Hashtag.where('name LIKE(?)', "%#{search.tr('＃', '#').tr('/０-９/', '/0-9/')}%")
  else
    Hashtag.all   
  end
end
```
全角で入力された#と数字を半角に置換しました。

## deleteメソッド
ハッシュを取り除いた形で検索させる場合

```ruby:models/hashtag.rb
def self.search(search)
  if search != ""
    Hashtag.where('name LIKE(?)', "%#{search.tr('＃', '#').tr('/０-９/', '/0-9/').delete('#')}%")
    Hashtag.all   
  end
end
```

# おわりに

ハッシュタグに半角数字、全角数字がごちゃまぜになるのが嫌だったので、ハッシュタグ入力時に全角数字のバリデーションを組みました。（本当は置換して保存したかったけどやり方わからず断念）

とりあえずこのやり方なら、検索ユーザーがどんなキーワードで入力してもいい感じに拾ってくれるのでよかったかなと思います。

✔︎
Laravel：レイアウト作成における継承

#【概要】

<b>1.[結論](#1結論)</b>

<b>2.[継承とは何か](#2継承とは何か)</b>

<b>3.[どのように記載するのか](#3どのように記載するのか)</b>

<b>4.[開発環境](#4開発環境)</b>


#1.結論
<b>`@extends`はsection,yieldを使用するための土地
`@section`,`@yield`は親子関係であり目的地に行くために道の入り口と出口
</b>です

#2.継承とは何か
継承とは、既に存在しているクラスやレイアウトを受け継いで、そのまま利用することです。つまり同じヘッダーやフッターを使いまわしたいときに１から作る手間が省けることをいいます。


#3.どのように記載するのか

```php:layouts/common.blade.php
<h1>@yield('title')</h1>
 @yeild('main-upper') //---❶
<div class="main-contents">
 @yield('content')
</div>
<div class="footer">
 @yield('footer')
</div>
```
```php:index.blade.php
@extends('layouts.common')//---❷

@section('title',***)//---❸

@section('main-upper')//---❹
　メイン上部の表示です。
@endsection

@section('content')
　内容になります。
@endsection

@section('footer')
　フッターになります。
@endsection
```
*cssは省いております。
❶：まず親(テンプレレイアウトになりうるcommon)になるレイアウトには`@yeild`を使用します。
❷：これがないと`@section`,`@yeild`が使えないのでこれを記載します。()の部分は１個上のディレクトリ名(layouts).最初のフォルダ名(common)になります。
❸：`@section`はもう一つの使い方で第二引数に文字や数字を入れれば簡単に文字列を表示できます。
❹：`@section`を記載したら適用範囲を指定するため`@endsection`で閉じます。

#4.開発環境
PHP 7.4.10
Laravel 8.9
Apache 2.4.41
Mysql 5.6.47
Sequl Pro 1.1.2
Lens(Kubernetes IDE) インストール

# 概要
 - k8sテスト環境構築<br>
<b>Lens(Kubernetes IDE) インストール</b>

# 構築目次
 - [全体目次] (https://qiita.com/prodigy413/items/89fcb8cfb71e80f541d8)

# 環境
 - Rancher: v2.6.3
 - kubernetes(Client): v1.22.4
 - kubernetes(Server): v1.22.4
 - Lens: v5.3.3

# Lens
 - Official Page<br>
https://k8slens.dev/<br>

### インストール
 - Download
Websiteにアクセス > [<b>Download Lens Desktop</b>] > [<b>Linux x64(.deb)</b>]
![26-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561025/374ee9fe-2cbb-4174-d211-1ff50cfe8e7c.png)

 - インストール

~~~
$ sudo apt install ./Lens-5.3.3-latest.20211223.1.amd64.deb
~~~

### Lens 起動

 - 起動

~~~
$ lens
~~~

 - [<b>SKIP</b>]クリック<br>
![27-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561025/6346dc55-7324-0055-040d-453ce098d995.png)

### クラスタ追加
※自動で`~/.kube/config`情報を取得する。

 - [<b>Catalog(APP)</b>] > [<b>Clusters</b>] > 追加するクラスタを選択
![29-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561025/07da97e3-b5af-0de5-16ca-9ed2929c09c6.png)

 - 確認
![30-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/561025/b20675dd-2cbb-192d-6b25-6b9d47a5881b.png)
Go To Eat 北海道キャンペーンの取扱店リストのPDFをCSVに変換

[Go To Eat 北海道キャンペーン](https://gotoeat-hokkaido.jp/general/particStores/)の取扱店リストのPDFをCSVに変換

+ [変換したCSV](https://docs.google.com/spreadsheets/d/e/2PACX-1vTETGU0gOZUn2cKBF6vrsBDF8i8wyzue2m0A9y4BWXCpIfSWjw1yvHwOCdeQVHLzs_Guf1IgSnlg5NR/pub?gid=1076880266&single=true&output=csv)

# 文字が消える

+ tabula・pdfbox・popplerだと「越漢熊煮新騒鶴」の文字が消えてしまう

+ 道北の「熊子ラーメン東旭川店」を検索してもヒットしない「子ラーメン東旭川店」だとヒット

+ pdfminerを使っているcamelot・pdfplumberは「(cid:1279)」という形で書き出されるためPDFの表示を確認して置換ができる

+ 作成ソフトは「cubepdf」、フォントは游ゴシック

![cubepdf.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36151/063e4dbe-e731-2ca1-4b7c-a1a0301faac1.png)

![font.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36151/7ffd4522-b51c-43aa-bcad-5dc05d5e476c.png)

![shin.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36151/44f41f80-4e84-97cf-a105-05ae61d3a427.png)

![kuma.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36151/98dfd6b2-93cc-53ce-3c9c-db34c59344ff.png)

# プログラム

```python
import camelot

import requests
from bs4 import BeautifulSoup

from urllib.parse import urljoin

import pandas as pd

url = "https://gotoeat-hokkaido.jp/general/particStores/"

headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko"
}

r = requests.get(url, headers=headers)
r.raise_for_status()

soup = BeautifulSoup(r.content, "html.parser")

dfs = []

for li in soup.select("ul.cf > li > a"):

    link = urljoin(url, li.get("href"))

    area = li.get_text(strip=True)

    tables = camelot.read_pdf(link, split_text=True, pages="all", strip_text="\n", )

    for table in tables:

        df_tmp = pd.DataFrame(table.data[1:], columns=table.data[0])
        df_tmp.columns = df_tmp.columns.map(lambda s: "".join(s.split()))
        df_tmp["地域"] = area

        dfs.append(df_tmp)

df

df = pd.concat(dfs)

df = df.fillna("").applymap(
    lambda s: s.replace("(cid:1279)", "越")
    .replace("(cid:1535)", "漢")
    .replace("(cid:1791)", "熊")
    .replace("(cid:2303)", "煮")
    .replace("(cid:2559)", "新")
    .replace("(cid:2815)", "騒")
    .replace("(cid:3071)", "鶴")
)

# CJK部首/康熙部首を置換
tbl = str.maketrans(
    "⺃⺅⺉⺋⺎⺏⺐⺒⺓⺔⺖⺘⺙⺛⺟⺠⺡⺢⺣⺦⺨⺫⺬⺭⺱⺲⺹⺾⻁⻂⻃⻄⻍⻏⻑⻒⻖⻘⻟⻤⻨⻩⻫⻭⻯⻲⼀⼁⼂⼃⼄⼅⼆⼇⼈⼉⼊⼋⼌⼍⼎⼏⼐⼑⼒⼓⼔⼕⼖⼗⼘⼙⼚⼛⼜⼝⼞⼟⼠⼡⼢⼣⼤⼥⼦⼧⼨⼩⼪⼫⼬⼭⼮⼯⼰⼱⼲⼳⼴⼵⼶⼷⼸⼹⼺⼻⼼⼽⼾⼿⽀⽁⽂⽃⽄⽅⽆⽇⽈⽉⽊⽋⽌⽍⽎⽏⽐⽑⽒⽓⽔⽕⽖⽗⽘⽙⽚⽛⽜⽝⽞⽟⽠⽡⽢⽣⽤⽥⽦⽧⽨⽩⽪⽫⽬⽭⽮⽯⽰⽱⽲⽳⽴⽵⽶⽷⽸⽹⽺⽻⽼⽽⽾⽿⾀⾁⾂⾃⾄⾅⾆⾇⾈⾉⾊⾋⾌⾍⾎⾏⾐⾑⾒⾓⾔⾕⾖⾗⾘⾙⾚⾛⾜⾝⾞⾟⾠⾡⾢⾣⾤⾥⾦⾧⾨⾩⾪⾫⾬⾭⾮⾯⾰⾱⾲⾳⾴⾵⾶⾷⾸⾹⾺⾻⾼⾽⾾⾿⿀⿁⿂⿃⿄⿅⿆⿇⿈⿉⿊⿋⿌⿍⿎⿏⿐⿑⿒⿓⿔⿕戶黑",
    "乚亻刂㔾兀尣尢巳幺彑忄扌攵旡母民氵氺灬丬犭罒示礻罓罒耂艹虎衤覀西辶阝長镸阝青飠鬼麦黄斉歯竜亀一丨丶丿乙亅二亠人儿入八冂冖冫几凵刀力勹匕匚匸十卜卩厂厶又口囗土士夂夊夕大女子宀寸小尢尸屮山巛工己巾干幺广廴廾弋弓彐彡彳心戈戸手支攴文斗斤方无日曰月木欠止歹殳毋比毛氏气水火爪父爻爿片牙牛犬玄玉瓜瓦甘生用田疋疒癶白皮皿目矛矢石示禸禾穴立竹米糸缶网羊羽老而耒耳聿肉臣自至臼舌舛舟艮色艸虍虫血行衣襾見角言谷豆豕豸貝赤走足身車辛辰辵邑酉釆里金長門阜隶隹雨靑非面革韋韭音頁風飛食首香馬骨高髟鬥鬯鬲鬼魚鳥鹵鹿麥麻黃黍黒黹黽鼎鼓鼠鼻齊齒龍龜龠戸黒",
)

df = df.applymap(lambda s: s.translate(tbl))

df.reset_index(drop=True, inplace=True)

df.index += 1

df.to_csv("gotoeat_hokkaido.csv", encoding="utf_8_sig")
```
データベースに関するアウトプット

#1.自己紹介
初めまして、けんとと申します。今回が初めての執筆になります。
まずは私が何者か簡単に自己紹介します。

* 未経験からのITエンジニア転職を目指している
* プログラミング学習中（言語はPHP）
* 現在会社員
* ３０代男性
* １歳児の父

今後は学習したことのアウトプットをするためにQittaにて執筆することを始めました。

##2.プログラミング学習歴
今年の３月からプログラミング学習を始めていたが、これまではProgateを使用しており
ほぼインプットしかしてこなかった。

現在は[独学エンジニア](URL "https://dokugaku-engineer.com")にて学習中
独学エンジニアでは、講義を通して手を動かしアウトプットすることも充実している
今回は　**データベース**　についてインプットしたことをアウトプットしていきます。

##3.データベースとは
データベースとは多くの情報（データ）を一箇所にまとめて、
すぐに使えるようにしてある場所のこと

データベースに情報を入れておくことで、大量のデータの
**検索**    **登録**   **修正**   **削除**　が簡単にできる

現在の主流はリレーショナルデータベースであり、多くの企業で使われている。

##4.リレーショナルデータベースとは
リレーショナルデータベースとは二次元表を使って情報を管理するデータベースである。
二次元表の場合はプログラミング言語を使用しなくても扱うことができるため、多くの幅広いユーザーが利用することができる。

リレーショナルデータベースのなかでも　**MySQL** は世界中の多くの企業で利用されている。 
**MySQL*　はオープンソースのリレーショナルデータベースである。

##5.SQLとは
SQL（Structured Query Language）の略となり、
リレーショナルデータベースを操作するための言語である

データベースを利用するプログラムがSQL言語を発行
↓
リレーショナルデータベースがファイルからデータを取り出し、プログラムへ返す


SQLが行うことは主に３つ
①データの定義
②データの操作
③データの制御

表に関する重要な用語　
**テーブル**　**カラム**　**行**　**レコード**

##６．感想
インプット→アウトプットを繰り返しを行い、
これを継続することで、知識の定着をはかり、成長速度を上げていきたい
Humhubのカスタムモジュールの作り方（Humhub公式サンプルの用例）

## サンプル紹介
[前回](https://qiita.com/tomofu74/items/9e9de4db430d87f718cc)は、humhubのモジュール作りにあたって、TopNavメニューへのwidgetのAddItemを行なった。カスタムのメニューを増やしたことで、なんらかのUIの入り口を作ることができた。

さて、その入り口をどう使うかは、作成者のアイディア次第だが、とりあえずのサンプルを見てみてはいかがだろうか。
ご紹介するのは、humhubのGithubにある、example-basicだ。TopNavメニューにアイテムを足して表示する機能と、モジュールの管理メニューを表示する機能がついている。

https://github.com/humhub/humhub-modules-example-basic

## 静的なコンテンツの追加
紹介したgithubのコンテンツをスケルトンとして、固定の（静的な）情報のページを作ることは十分可能と思われるので、そういった用途には、調整・編集を行なっていくのも一つの方法だろう。TopNavメニューのアイテムをクリックすると静的なコンテンツを表示する仕組みが紹介されており、場合によってはカスタマイズ開発を大いに助けてくれるだろう。とりあえず、moduleIDをカスタムに変更することから初めてみてはいかがか。

次回は、DBにテーブルを追加し活用する動的なコンテンツを含むモジュールの作り方について触れたいと思う。
rails 6.1から追加されるN+1問題発生を抑止するstrict_loading機能

rails 6.1rcがリリースされたので、新機能の`strict_loading`機能をまとめました。
See: https://weblog.rubyonrails.org/releases/

## 1. `#strict_loading`

オブジェクトにmarkをつけることで、そのオブジェクトのアソシエーション遅延ロードした際に例外`ActiveRecord::StrictLoadingViolationError`を発生させます。

```ruby
$ dev = Developer.strict_loading.first
$ dev.audit_logs.to_a
=> ActiveRecord::StrictLoadingViolationError: Developer is marked as strict_loading and AuditLog cannot be lazily loaded.
```
See: https://github.com/rails/rails/pull/37400

## 2. has_many :projects, strict_loading: true
関連付けのoptionで設定する方法。
指定したアソシエーション全てで、遅延ロードした際に例外`ActiveRecord::StrictLoadingViolationError`を発生させます。

```ruby
class Developer < ApplicationRecord
  has_many :projects, strict_loading: true
end

dev = Developer.first
dev.projects.first
# => ActiveRecord::StrictLoadingViolationError: The projects association is marked as strict_loading and cannot be lazily loaded.
```
See: https://github.com/rails/rails/pull/38541

## 3. self.strict_loading_by_default = true
モデル単位で指定する方法。
指定したモデル全体からのアソシエーション全てで、遅延ロードした際に例外`ActiveRecord::StrictLoadingViolationError`を発生させます。

```ruby
class Developer < ApplicationRecord
  self.strict_loading_by_default = true

  has_many :projects
end

dev = Developer.first
dev.projects.first
# => ActiveRecord::StrictLoadingViolationError Exception: Developer is marked as strict_loading and Project cannot be lazily loaded.
```
See: https://github.com/rails/rails/pull/39491


## その他
### strict_loding検知時の挙動で例外発生だけではなくlog出力が選択できるようになりました。
> `config.active_record.action_on_strict_loading_violation` enables raising or
  logging an exception if strict_loading is set on an association. The default
  value is `:raise` in all environments. It can be changed to `:log` to send
  violations to the logger instead of raising.

```ruby
config.active_record.action_on_strict_loading_violation = :log
```

See
https://github.com/rails/rails/pull/40511
https://edgeguides.rubyonrails.org/configuring.html#configuring-active-record

これにより、development, test環境では例外を発生させ、production環境ではlog出力に留めることが可能になりました。

### WTFでの議論
N＋1を発生させないような工夫についてはコミュニティでも活発に議論されています。
https://discuss.rubyonrails.org/t/n-1-queries-should-be-the-exception-not-the-easy-trap-to-fall-in/74537/14
AI系のデータなど、数GBのファイルをGoogleDriveから落とすときはpydrive2使う。 + pydrive2の即時な設定

AI関連のファイルで数GBのものがあった(BERT)。colabで学習させてGoogleDriveに結果保存していたものをGoogleDriveからローカルやサーバーにダウンロードしたい。`pydrive`使おうとしたがコケた。

## 結論
`pydrive`ではなく`pydrive2`使う。でかいファイルがダウンロードできる

## 内容
ダウンロードファイルがでかすぎて`pydrive`ではコケる。pydrive2では行ける
内部で使っているメソッドが変わっているらしい
githubにisssueたっててpydrive2使えって案内されていた。
GoogleDriveからのダウンロードは使い捨てプログラムなのでStackOverflowなどを見ながら地道にAPI叩いても良いのだとおもう。
でも私はさらにライブラリ使って楽をしたかった。


## 他、設定
任意の場所にダウンロードプログラムがあってpydrive2の設定ファイルも持ちたくないとき下記で設定色々行ける

```python
# client_secretsの場所
GoogleAuth.DEFAULT_SETTINGS['client_config_file'] = os.path.dirname(os.path.abspath(__file__)) + '/resources/client_secrets.json'
# 認証情報をファイルに保存する設定
GoogleAuth.DEFAULT_SETTINGS['save_credentials'] = True
GoogleAuth.DEFAULT_SETTINGS['save_credentials_backend'] = 'file'
# credentialsファイルの保存先
GoogleAuth.DEFAULT_SETTINGS['save_credentials_file'] = os.path.dirname(os.path.abspath(__file__)) + '/resources/' + 'saved_credentials.json'
GoogleAuth.DEFAULT_SETTINGS['get_refresh_token'] = True
```

任意の場所のclient_secretsを使い、credentialsを任意の場所に保存する

## `pydrive2`でフォルダーまるごとGoogleDriveからダウンロードしてくる全コード

どこからかあつめてきたコードをまとめて載せます

```python
import os
from pydrive2.drive import GoogleDrive
from pydrive2.auth import GoogleAuth
GoogleAuth.DEFAULT_SETTINGS['client_config_file'] = os.path.dirname(
    os.path.abspath(__file__)) + '/resources/client_secrets.json'
# 認証情報をファイルに保存する設定
GoogleAuth.DEFAULT_SETTINGS['save_credentials'] = True
GoogleAuth.DEFAULT_SETTINGS['save_credentials_backend'] = 'file'
GoogleAuth.DEFAULT_SETTINGS['save_credentials_file'] = os.path.dirname(
    os.path.abspath(__file__)) + '/resources/' + 'saved_credentials.json'
# 認証情報(credentials)を自動で更新する設定
GoogleAuth.DEFAULT_SETTINGS['get_refresh_token'] = True

os.chdir(os.path.dirname(os.path.abspath(__file__)))

gauth = GoogleAuth()
gauth.CommandLineAuth()

drive = GoogleDrive(gauth)


def download_recursively(save_folder, drive_folder_id):
    # 保存先フォルダがなければ作成
    if not os.path.exists(save_folder):
        os.makedirs(save_folder)

    max_results = 100
    query = "'{}' in parents and trashed=false".format(drive_folder_id)

    for file_list in drive.ListFile({'q': query, 'maxResults': max_results}):
        for file in file_list:
            print(file)
            # mimeTypeでフォルダか判別
            if file['mimeType'] == 'application/vnd.google-apps.folder':
                download_recursively(os.path.join(save_folder, file['title']), file['id'])
            else:
                file.GetContentFile(os.path.join(save_folder, file['title']))


if __name__ == '__main__':
    # lang model
    drive_folder_id = '1-4dxxxxxx'
    save_folder = '../Folder/a'
    download_recursively(save_folder, drive_folder_id)
    # wiki
    drive_folder_id = '19xxxxxxxxxx'
    save_folder = '../Folder/b'
    download_recursively(save_folder, drive_folder_id)
    # label_id
    drive_folder_id = '1aVSxxxxxxxx'
    save_folder = '../Folder/c'
    download_recursively(save_folder, drive_folder_id)
```



以上です。
エクセルのピポットテーブルで複数のデータを比較する方法 

#例えば
・早急に今年の商品価格表と前年の商品価格表の比較し下記の表を作成したい場合
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/870732/e34f557a-14be-15ca-8eed-d5f640abc8a8.png)
#方法
①今年の商品価格表と前年の商品価格表を用意
※分かりやすい様に色付けしてます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/870732/debea5d3-6529-e59d-2cd4-ebdedd30eaa1.png)

②下記図のように成形します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/870732/e11ae13e-91ef-87da-1c03-461b2a5d03aa.png)

③ピポットテーブルで②で成形した箇所を選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/870732/0b9736cc-3360-5b7d-6bdd-e652baac6554.png)

④ピポットテーブルで下記図を作成
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/870732/fa861af6-e6bc-caba-1eba-52ddce258cf7.png)
設定内容
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/870732/b1e4c841-26e6-1d19-11c0-d126820a0dcf.png)

⑤ピポットの内容をコピーし貼り付けし
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/870732/6c29b359-826d-2786-998d-f531d8919a22.png)

⑥差額を列追加
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/870732/50c1e41a-517d-134c-834d-4d3b75974c57.png)

⑦フィルターを利用してランク付け
[ランク内容]
1：今年の商品価格表が空白以外 ＆ 前年の商品価格表が空白
2: 今年の商品価格表が空白以外 ＆ 前年の商品価格表が以外 & 差額が0
3: 今年の商品価格表が空白以外 ＆ 前年の商品価格表が以外 & 差額が0以外
4：今年の商品価格表が空白 ＆ 前年の商品価格表が空白以外
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/870732/5efabdeb-a561-59a2-9ee0-26d3ed78d191.png)

⑧ランクを昇順で並び替え
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/870732/b42997a9-d746-089e-eb7f-021c80290dcd.png)

以上で完了です。

#最後に
VLOOKUPやCOUNTIFを利用する手がありますが、
データ量によっては処理が遅くなりイライラすることがあるかと思います。
上記の方法ですと、すぐに描画されるのでイライラ防止することができます。
NgModule / Angular勉強会 #2

## NgModuleについて
- Angularは `NgModule` と呼ばれるモジュールシステムによってモジュール化されている
  - 似通った機能をモジュール化してまとめておくことがベストプラクティスとなる
  - 他のモジュールをインポートしたり、他のモジュールに公開する機能をエクスポートできる
- すべてのAngularアプリは `AppModule` と呼ばれるルートモジュールを持つ
  - `app.module.ts` という名前で定義される

---

## `NgModule`のメタデータ
- 他のAngularの要素と同じく、`＠NgModule` デコレータによって定義され、いくつかのメタデータを持つ
  - `declarations`: モジュールに属するView要素（`component`など）
  - `exports`: `declarations`のうち他モジュールに公開できるもの
  - `providers`: このNgModuleが公開するサービスを提供するクラス群
  - `bootstrap`: 起動時に表示する`component`（ルートコンポーネント）を指定する

```ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
@NgModule({
  imports:      [ BrowserModule ],
  providers:    [ Logger ],
  declarations: [ AppComponent ],
  exports:      [ AppComponent ],
  bootstrap:    [ AppComponent ]
})
export class AppModule { }
```

---
## ES2015のモジュールと`NgModule`
- ES2015のモジュールと`NgModule`は全く違う概念
- ES2015モジュールでは、「別の1ファイルをインポートすること」がモジュールで、複数ファイルを纏めたモジュールというのは存在しない
- `NgModule`はES2015よりもさらに大きな塊をモジュール化していると言える
    - 当然内部的にES2015モジュールは活用されている
    - 
---

## `NgModule` の遅延ロード
`NgModule` のインポートは一般的にはコンパイル時に解決される。すなわち、生成されるindex.jsファイルにすべてのインポートされたモジュールが含まれることになる。しかしこれにはindex.jsが肥大化し、初期ロード時間が長くなってしまうというデメリットもある。

そこで、特定のパスに遷移したときに初めて読み込まれる、という手法で、初期インポートされるモジュールの肥大化を防ぐことができる。

```ts
export const routes: Routes = [
  { path: '', redirectTo: 'contact', pathMatch: 'full'},
  { path: 'items', loadChildren: () => import('./items/items.module').then(m => m.ItemsModule) },
  { path: 'customers', loadChildren: () => import('./customers/customers.module').then(m => m.CustomersModule) }
];
```


---
## 実際のモジュールを見てみよう
- Demo: https://stackblitz.com/angular/klejyjnynjj

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/447106/61203ab2-0714-fbfe-7e4c-e51d1dd12f5f.png)
hubotでぐるなびAPIからお店を検索する

### 別サイト
に書いていたのだけどサ終してしまったので移行した記事その3
2019/03/07のもの

### ぐるなびAPI
[ぐるなびAPI](https://api.gnavi.co.jp/api/ "ぐるなびAPI")
を使うとお店の検索をしたり口コミ情報を取得したりできる

### ランダムで指定の地域のお店を出すhubotスクリプト
動いたのでメモ

```
API_URL     = 'https://api.gnavi.co.jp/RestSearchAPI/v3/?'
API_KEY       = '[APIキー]'
address     = '[デフォルトの地名（東京都渋谷区とか）]'

module.exports = (robot) ->
  robot.respond /お店 ?(.*)$/i, (msg) ->
    attr = msg.match[1].trim()
    address = attr if attr != ""
    url = "#{API_URL}keyid=#{API_KEY}&hit_per_page=1&offset_page=1&address=#{encodeURIComponent(address)}"

    # 対象の件数をとるために２回通信する
    robot.http(url)
      .get() (err, response, body) ->
        data1 = JSON.parse body
        totalCount = data1.total_hit_count
        offsetPage = Math.floor Math.random() * totalCount
        # v3のAPIだとoffsetが1001以上だとエラーになるので調整
        if totalCount > 1000
          offsetPage = Math.floor Math.random() * 1000

        url = "#{API_URL}keyid=#{API_KEY}&hit_per_page=1&offset_page=#{offsetPage}&address=#{encodeURIComponent(address)}"
        robot.http(url)
          .get() (err, response, body) ->
            data2 = JSON.parse body
            items = data2.rest[0]
            message = """
                      おすすめ: #{items.name}
                      カテゴリ: #{items.category}
                      住所: #{items.address}
                      URL: #{items.url}
                      """
            msg.send message
```

### 感想
APIも結構あるしパラメータがかなり細かく設定できるのでカスタムしがいはありそう
Eloquentのカスタムアトリビュートを含めて一括取得できるtoArrayWithKey()の実装

検索結果に併せてカスタムアトリビュートも組み込みたくて実装を考えました。
only()は内部でgetAttribute()を読んでるのでtoArray()した時のkeysに取得したいカスタムアトリビュートをマージして一括取得したいと思います。

```php
trait CanToArrayWithAppendingKeys{
    public function toArrayWithAppendingKeys(array $keysToBeAppended){
        return $this->only(array_merge(array_keys($this->toArray()), $keysToBeAppended));
    }
}
```


```app/Book.php
class Book extends Model
{
    use CanToArrayWithAppendingKeys;
    public function getAuthorFullNameAttribute(){
        return $this["author_first_name"]." ".$this["author_last_name"];
    }
}
```

Tinker

```php
Book::where("id", 10)->first()->toArrayWithAppendingKeys(["author_full_name"])
//author_full_name付きのBookの検索結果が返る。
```
以上です。
【EC2】scpコマンドでローカルとEC2間でのファイル&フォルダのダウンロード方法

##scpコマンドとは？
scpコマンドはファイルやフォルダをSSHで暗号化しながら転送できるのことコマンドです。

##ダウンロード前に行うこと
###公開鍵（authorized_keys）の設置
公開鍵認証する場合、EC2で作成した公開鍵と秘密鍵のペア（拡張子は.pem）を公開鍵を接続ユーザのホームディレクトリ下~/.ssh/ に設置する必要があります。
###Permissionの設定
公開鍵認証する場合、以下のようにPermissionを適切に設定する必要があります。
きちんと設定していないとscpコマンドの実行時にPermission Errorが発生します。

```
chmod 600 キーの名前.pem
```

これで事前準備は完了です。

##ダウンロード方法

###ローカルファイルをEC2へダウンロード
まずは、ローカルファイルをEC2上へダウンロードする方法を見ていきましょう。

```
$ cd .ssh/
$ scp -i [秘密鍵] [転送するファイルのパス] [EC2ユーザー名]@[パブリックIP]:[EC2上のコピー先のパス]
```
となります。
具体例は以下のようになります。

```
$ cd .ssh/
$ scp -i hello-key.pem ~/hello.html ec2-user@[パブリックIP]:/home/ec2-user
```

###ローカルフォルダをEC2へダウンロード
次に、ローカルフォルダをEC2へダウンロードする方法です。
ローカルフォルダの場合、-iの前に−rをつけることでダウンロードすることができます。

```
$ cd .ssh/
$ scp -r -i [秘密鍵] [転送するフォルダのパス] [EC2ユーザー名]@[パブリックIP]:[EC2上のコピー先のパス]
```

具体例は以下のようになります。

```
$ cd .ssh/
$ scp -r -i hello-key.pem ~/hellodir ec2-user@[パブリックIP]:/home/ec2-user
```

###EC2のファイルをローカルへダウンロード
次に、EC2のファイルをローカルへダウンロードする方法です。
最後にローカルの転送先フォルダのパスを指定します。

```
$ cd .ssh/
$ scp -i [秘密鍵]  [EC2ユーザー名]@[パブリックIP]:[EC2上の転送するファイルのパス] [ローカルの転送先フォルダのパス]
```
具体例は以下のようになります。

```
$ cd .ssh/
$ scp -i hello-key.pem ec2-user@[パブリックIP]:/home/ec2-user/hello.html /Users/home/
```

###EC2のフォルダをローカルへダウンロード
最後に、EC2のフォルダをローカルへダウンロードする方法です。
-rをつける位置に注意しましょう。

```
$ cd .ssh/
$ scp -i [秘密鍵] -r [EC2ユーザー名]@[パブリックIP]:[EC2上の転送するファイルのパス] [ローカルの転送先フォルダのパス]
```

具体例は以下のようになります。

```
$ cd .ssh/
$ scp -i hello-key.pem ec2-user@[パブリックIP]:/home/ec2-user/hello.html /Users/home/
```
##最後に
実行できたら、ターミナル上でダウンロードが開始されます。
ダウンロードが完了したら、sshコマンドを使い、EC2上へダウンロードをできているか確認しましょう。

```
ssh -i [秘密鍵] [EC2ユーザー名]@[パブリックIP]
```
上記のコマンドを実行することでログインできます。
Facebookクローンを作成したメモ

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">facebookクローンを作成。改善の余地は無限大にあるが、ログイン、サインアップ、動画投稿、ユーザーと記事の紐付けなどは実装できた。次は多対多のアソシエーションでお気に入り機能など実装したい。<a href="https://twitter.com/hashtag/RubyonRails?src=hash&amp;ref_src=twsrc%5Etfw">#RubyonRails</a> <a href="https://twitter.com/hashtag/100DaysOfCode?src=hash&amp;ref_src=twsrc%5Etfw">#100DaysOfCode</a> <a href="https://twitter.com/hashtag/%E9%A7%86%E3%81%91%E5%87%BA%E3%81%97%E3%82%A8%E3%83%B3%E3%82%B8%E3%83%8B%E3%82%A2%E3%81%A8%E7%B9%8B%E3%81%8C%E3%82%8A%E3%81%9F%E3%81%84?src=hash&amp;ref_src=twsrc%5Etfw">#駆け出しエンジニアと繋がりたい</a> <a href="https://t.co/TfcSMEKgKs">pic.twitter.com/TfcSMEKgKs</a></p>&mdash; hiro koba (@hirostf) <a href="https://twitter.com/hirostf/status/1325975628366061569?ref_src=twsrc%5Etfw">November 10, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

####前回できなくて今回できるようになったこと

先月書いた記事( https://qiita.com/HIROKOBA/items/478d805496d7db798c2e ）で、ツイッタークローン（と呼んでいます実際には全く別物）の記事を書いてから約１か月。前回できなかったけど今回実装できた機能は下記。

・記事に写真を投稿
・ユーザー写真を登録＋表示
・ログイン、ログアウト機能
・サインアップ機能
・ユーザー別の投稿一覧ページを実装

####今回やりたかったけどできなかったこと
これらは今度作るインスタグラムクローンで実装できれば挑戦してみようと思います。ただしSendGridのアカウントが停止状態になっていて、問い合わせても返信なくて、メール送信機能がいつ実装できるかは不明。

・お気に入り機能
・いいね機能
・エラーメッセージの日本語化
・投稿した際に自動でメッセージが送信される機能

####長期的な目標、使いこなせるようになりたい技術

・デザインが前回とあまり変わらない気がするので、UX/Uを意識して見た目と使いやすさにこだわりたい
・Javascript→Vue, Reactあたりの機能
・Bootstrap
・レスポンシブ対応
・Docker
・Circle CI等のCIツール
・AWS

####次の１か月の目標
・やりたかったけど使えなかった機能（お気に入り機能、いいね機能、エラーメッセージの日本語化、メール送信機能）を習得して、インスタグラムクローンに盛り込みたい
・週に25時間は学習時間を確保したい
【対処法】error: [WinError 2] 指定されたファイルが見つかりません。 ERROR: Command errored out with exit status 1

#この記事で分かること

Pythonでpipからmoduleをインストールする時、タイトルのようなエラーが出た時の対処法が分かります。
結論から記述します。

#結論
[こちら](https://gammasoft.jp/support/pip-install-error/)の記事の原因４の方法を実行すると正常にインストールすることができました。

#発生原因
<b>2020.10.05にリリースされたPython3.9（Windows版）</b>で、特定のモジュールをインストールしようとすると発生します。このパソコンにビルドできる環境（Microsoft Visual C++ 14.0）がないためにエラーが生じるらしいです。
私は『pypinfo』をインストールする際に発生しました。

#対処法
<ol><li>Microsoft Visual C++ 14.0を使えるようにする</li>

<li>Pythonのversionを指定してインストールする</li>
</ol>

1に関して
[マイクロソフトダウンロードページ](https://visualstudio.microsoft.com/ja/downloads/)から、[このように](https://mebee.info/2020/07/18/post-13597/)ダウンロードして、C++ Build Toolを取得していきます。
1の方法は重い上に面倒です。

2に関して

```
$ py -3.6 -m pip install "module"
```
のようにターミナルからPythonのversionを指定してインストールすることが可能です。
今回、私はver3.6でインストールすることができました。
CNC2417の備忘録

<h1>はじめに</h1>
CNC2417を購入したのは2018年頃でしたでしょうか。
今までにレーザー彫刻機としてコースターなど作ったりしてましたが、実のところ曖昧なまま使っている部分が多いです。
2年も経ったけど、購入後はやる気がどっと落ちるので、ある程度使えるようになったらそのままだったりする。

久しぶりに意欲がわいてコントロール基板の動作を調べたので、記録してみる。

<h1>コントロール基板</h1>
私が購入したCNC2417に付属していたコントロール基板には「Laseraxe.CNCC.Mini」と印字されている。
Webで検索すると似たようなものがヒットするが、さらっと調べてもオリジナルがどこなのか不明でした。
マニュアルには各ピンの詳細は書いてませんが、多分ファームウェアのGRBLを理解していれば、そんなに苦労せずに各ピンの内容が理解できるのかもしれません。

下記にコントロール基板と各ピンの内容をまとめました。
※間違いがあるかもしれん。ご了承ください。

![コントロール基板.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/866085/71ca2f37-8234-9002-5fe1-e226a99f9f60.png "コントロール基板")

<dl>
 <dt>① DC12V出力</dt>
 <dd>レーザモジュールの電源用のようだ。<br>
     ⑧DC12V INから直接供給されており、基板上の電源SWの状態にかかわらず常にDC12Vが出力されている。</dd><br>

 <dt>② COOLANT用出力</dt>
 <dd>COOLANT制御用の出力で、GCODEのM7、M8、M9でON/OFFできる。<br>
     電圧レベルはON=5V、OFF=0Vとなっている。<br>
     FloodかMistかはGRBLのバージョン等で変わってくるかもしれない。</dd><br>

 <dt>③ TTL出力(PWM出力)</dt>
 <dd>レーザモジュール等のPWM制御用出力として使用できる。<br>
     電圧レベルはDC0～5Vとなっている。<br>
     周期はGRBLの設定による。<br>
     ブラシレスモータを使用する場合にも使えるかも。</dd><br>

 <dt>④ X軸リミットスイッチ入力</dt>
 <dd>X軸のハードリミットスイッチを接続する。<br></dd><br>

 <dt>⑤ Y軸リミットスイッチ入力</dt>
 <dd>Y軸のハードリミットスイッチを接続する。<br></dd><br>

 <dt>⑥ Z軸リミットスイッチ入力</dt>
 <dd>Z軸のハードリミットスイッチを接続する。<br></dd><br>

 <dt>⑦ USB端子</dt>
 <dd>Arduino NanoのUSB端子である。<br>
     PC等と接続して制御する。</dd><br>

 <dt>⑧ DC12V入力</dt>
 <dd>ステッピングモータ、スピンドルモータ、レーザモジュール等に供給する電源の入力端子である。<br>
     この基板はDC12V専用のようなので、スピンドルモータをDC12V以外で駆動したい場合、別に回路を用意する必要がありそうだ。</dd><br>

 <dt>⑨ Z軸ステッピングモータ用出力</dt>
 <dd>Z軸のステッピングモータを制御するための出力である。</dd><br>

 <dt>⑩ Y軸ステッピングモータ用出力</dt>
 <dd>Y軸のステッピングモータを制御するための出力である。</dd><br>

 <dt>⑪ Y軸ステッピングモータ用出力</dt>
 <dd>Y軸のステッピングモータを制御するための出力である。<br>
     ⑩と同じドライバが使用されており、回転方向が逆になるように配線されている。<br>
     CNC2417のY軸モータは1つしかなく、軸の移動方向はGRBLの設定で変えられるので、⑩と⑪どちらを使用しても構わない。</dd><br>

 <dt>⑫ X軸ステッピングモータ用出力</dt>
 <dd>X軸のステッピングモータを制御するための出力である。</dd><br>

 <dt>⑬ クーリングファン用出力</dt>
 <dd>マニュアルにそう書かれていたが、何の冷却用かは不明。<br>
    ⑧DC12V INから直接供給されており、基板上の電源SWがONの時に常にDC12Vが出力されている。</dd><br>

 <dt>⑭ レーザ/モータ用出力</dt>
 <dd>レーザモジュールやモータを制御するための出力である。<br>
     PWM出力されており、電圧はDC0～12Vとなっている。<br>
     レーザモジュールをこの端子へ接続する場合、レーザモジュールのTTL入力を使用せずに駆動することができるかもしれない。ただし、レーザ低出力時、レーザモジュールのファンが回転しないことがあり、寿命に影響しそう。<br>
     私は今までここにレーザモジュールを接続していたが、ケーブルが作成できるようになったので、①と③に接続するように変更した。</dd><br>

 <dt>⑮ プローブ入力</dt>
 <dd>Zプローブを接続する。</dd><br>

 <dt>⑯ サスペンド入力</dt>
 <dd>ArduinoのA1に接続されており、短絡するとStatusがHoldになる。<br> 
    どうやらこの端子は非常停止としては使用できないようだ。フィードは停止するが、スピンドルは回転したままになる。</dd><br>
</dl>

<h1>その他メモ</h1>
+ アルミフレームに使用できる丁度いいネジはM5のやつ。

<h1>おわり</h1>
おわり。
【JavaScript学習 ①】JavaScriptとは

<h1>JavaScript学習について
<p>　JavaScript学習と称しまして、こちらの記事では、私が学習したJavaScriptの知識を記述するものでございます。自身が初学者なため、記事の内容が幼稚な部分あるかと思いますが、一緒に学び知識を習得していきましょう！
　※内容について、誤った知識やもっとわかりやすい表現等ございましたら、ご指摘いただけますと幸いです。

<h1>JavaScriptとECMAScript
<p>　JavaScriptを学習しているとよく耳にする "ECMAScript" について、皆さんご存知でしょうか？JavaScriptとECMAScriptの違いについて学んでいきます！

　1990年代(インターネット黎明期）、当時ブラウザとして大きなシェアを持っていた "Netscape Navigator" （以降 NN と省略）が、マイクロソフト社がWindowsOSと一緒に配布することによって急激にシェアを伸ばしてきた "Internet Explore" （以降 IE と省略）と対抗するために考え出したものが "JavaScript" になります。
　IEもJavaSciriptを使いたかったのですが、ライセンスなどの問題で使えなかったので新たに "JScript" というものを新しく作成しました。
　そして、この２つのプログラミング言語には、互換性がなかったため、そこで考え出されたものが "ECMAScript" になります。こちらは、 "JavaScript言語のコアの部分をECMAScriptとして仕様策定したもの" になります。

　これによって、ブラウザ間での動きを統一することができ、開発者がハッピーになったというわけです。

<h1>実行する環境によって持っている機能が異なる
<p>　あくまで、JavaScriptの一部がECMAScriptとなります。そのため、例えば、ブラウザ環境の場合、ECMAScriptとWeb APIs、Node.js環境の場合、ECMAScriptとConnobJSを使うことができます。

　ここで重要になってくるのが、見出しにもあるとおり、 "JavaScriptは、実行する環境によって使える機能が変わってくる" ということです。

<h1>まとめ
<p>　JavaScriptとは

- ECMAScriptの仕様に基づいて実装されているプログラミング言語
- 環境によってし変える機能が変わってくる。
ローカルサーバーを立ち上げた時、[/0.0.0.0:9000] Address already in useと出た時の対処法(備忘録)

こんにちは!!

junです

見て頂きありがとうございます。
今日はPlayFrameworkで開発をしていて、[/0.0.0.0:9000] Address already in useとエラーが出てしまったので、
対処法を備忘録として残しておくためにも投稿しようと思います。

なお、この対処法はPlayだけでなくRailsなど、他のアプリ作成で同様のエラーが出ている時も対応できると思います。


```
sudo lsof -i :9000(9000はポート番号 Railsなら3000)
//password
ログインpasswordを要求されるので、入力する

COMMAND PID    USER  FD   TYPE DEVICE                 SIZE/OFF 　NODE 　NAME
java    15138  jun  230u  IPv6 0xf368adbeb419535      0t0  TCP *:cslistener (LISTEN)
```

sudoをつけるのはlsofコマンドを使うためにはroot権限が必要だからです。
root権限を持っているユーザーでログインしていればsudoは付けなくても良いです。

ここでは15138というププロセスIDがリッスン(待ち受けて)していますね。

プロセスIDが確認できたら次にコマンドラインで

```
kill -9 15138
```
と打ってみます。
このコマンドでフリーズしているプロセスを強制的に終了します。

特に応答はないかと思いますが、それで大丈夫です。

再度ローカルサーバーを立ち上げれば無事に接続ができるかと思います。
GooglePlayConsoleのデバイスカタログからデバイスを除外したときの挙動

## 概要
Google Play Consoleのデバイスカタログからデバイスを除外する手順については[公式ドキュメント](https://support.google.com/googleplay/android-developer/answer/7353455?hl=ja)に載っているものの、ユーザーから見たときにどういう挙動になるのか詳しい記事がなかったので備忘録として残しておく。

新しいコンソール(2020/11/10時点)で触った情報です。

個人開発でユーザーの殆どいない[一晩で作ったクソアプリ](https://play.google.com/store/apps/details?id=com.masaibar.tategaki)がちょうどいいところにあったので、手持ちの端末であるPixel4 XLを指定して除外して挙動を見てみた。

## 除外前
ストアで問題なく表示できていることを確認、端末には予めインストールしている。
<img src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68671/7b288076-77a7-ff07-a9b2-1a672e136dfe.png width=300>

## 除外の方法
Google Play Consoleのデバイスカタログから端末を絞り込んで除外する。
ちゃんとユーザーのいるアプリであればこの画面の`アクティブなインストールデバイス数（過去 30 日間）`や`累計平均評価`、`収益（過去 30 日間）`の項目に数字が表示されるので実際に除外する際にはそれらの数値を参考に除外の与える影響度合いを判断していく。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68671/253e5047-73aa-1d5b-ede4-e942e36734d7.png)


除外ボタンを押すと、警告のダイアログが表示される。

「このデバイスモデルにはアプリをインストールできません。すでにアプリがインストールされている場合は引き続きご利用いただけますが、アップデートは行われません。」とのこと。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68671/c77d0695-3a4b-0015-c7b7-e92a7904bb24.png)

## 除外後
除外するとボタンの表示が「追加」に変わる。
こちらを押せば元に戻せる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68671/25d020b8-56c9-f57f-ff53-a0e4138f7a33.png)

Google Playアプリのストレージの消去をすると検索結果から消えていた。
(通常のユーザーはそんなもの消さないので、端末への反映には多少時間がかかる様な挙動になると思われる。)

（分かりづらいが「写真ぷらす」と縦書きプレビュー for Jota+の間に表示されていた。）
<img src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68671/b2681ab9-2882-21b5-7744-24a5f5a3056a.png width=300>

検索からはたどり着けなくなったので直接ストアページを開く。
端末に当該アプリがインストールされている場合はこのように赤字で警告メッセージが表示されるようになる。

<img src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68671/33585c05-97ab-96b9-21e6-313c69b0aebe.png width=300>

アンインストールされている状態だとこの様に表示され、完全にインストールできなくなる。
<img src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68671/5941a989-3bb5-c4b1-0c3c-ab1c8e4067a3.png width=300>

## まとめ
Google Play Consoleから端末を除外すると

- ストアの検索結果に載らないようになる
- アプリ詳細ページでは
    - 未インストールのユーザーはインストールできなくなる
    - インストール済みのユーザーは更新できなくなる
        - 現在のバージョンをそのまま利用し続けることはできる
        - アンインストールしたら復元はできなくなる
  
上記の変更はGoogle Playアプリのストレージの消去を行うことで即時反映された。
実際のユーザーのユースケースであれば、除外から反映まで多少時間がかかると思われる。



また、除外したアプリを戻すことも出来る。