OmniAICreator/Qiita-1.07M huggingface dataset:
AWS-CDK ver.2でAppRunnerにコンテナをデプロイしてみた

SuiteCRMをコンテナで本格的に活用し始めました。

ある程度形になってきたのでテスト用の環境をAWS上に構築しようと思い、早速AppRunnerを利用してみました。
AppRunnerはコンテナイメージまたはコンテナリソースを指定してやれば簡単にコンテナサーバを稼働させることができるAWSサービスです。

https://aws.amazon.com/jp/apprunner/

AutoScalingなども自動設定され、スピーディーにコンテナ環境を稼働させることができます。

# 通常のAppRunnerへのコンテナのデプロイ方法
AWSコンソール上で作業する場合は以下の流れになります。

1. DockerImageを作成してECRにアップロードする
1. 　AppRunnerでECRのレジストリを指定してデプロイ開始

以上です😊簡単ですね。

# CDKでデプロイしようと思った理由
AppRunnerでのコンテナデプロイは私の場合6分ほどかかりました。
一度デプロイに失敗するとその度に各種パラメータを再設定してデプロイ、５分待つ・・・
またエラー😢
を何度か繰り返していました。

環境変数も５つほどあったのでエラーのたびにこの変数も設定し直すのがだんだん面倒になってきました。

ってことでCDKでIaC化することにしました。

# CDKでのIaC化手順
## 1. 何はともあれCDKのインストール
CLIはすでに入れていたので、CDKをインストール
以下の記事を参考にしました。
https://aws.amazon.com/jp/getting-started/guides/setup-cdk/module-one/

## 2. TypeScriptコードの作成
以下の記事を参考にさせていただきました。
https://qiita.com/doge_boi/items/0ce599a92b88c20324b8

私の場合はすでにECRにコンテナイメージをアップロード済みだったので、そちらを参照するようにしました。
また、一度AppRunnerを走らせていたのでIAMロールもそちらを利用しました。

CDK　Ver.2の情報は確かにまだまだ少ないですね。
本家CDKのドキュメントもちょこちょこ読み込んで作成していきました（英語です）
https://docs.aws.amazon.com/cdk/api/v2/

ソースコードは以下の通り。

```app-runner-stack.ts
import {
	Stack,
	StackProps,
	aws_apprunner as apprunner,
	aws_iam as iam,
	aws_ecr as ecr
} from 'aws-cdk-lib'
import {Construct} from 'constructs';

export class AppRunnerStack extends Stack{
	constructor(scope: Construct,id:string,props?:StackProps){
		super(scope,id,props)

		// ECR
		const repository = ecr.Repository.fromRepositoryName(this,'AppRunnerRepository',[コンテナレジストリ名])

		// AppRunner
		const cfnService = new apprunner.CfnService(this, 'AppRunnerService', {
		  sourceConfiguration: {
		    authenticationConfiguration: {
		      accessRoleArn:[AppRunnerからECRにアクセスするロールのARN],
		    },
		    autoDeploymentsEnabled: false,
		    imageRepository: {
		      imageIdentifier: repository.repositoryUriForTag('latest'),
		      imageRepositoryType: 'ECR',
		      imageConfiguration: {
		        port: '8080',
		        runtimeEnvironmentVariables: [
							{name:'SUITECRM_DATABASE_PASSWORD',value:'xxxx'},
							{name:'SUITECRM_DATABASE_USER',value:'xxxx'},
							{name:'SUITECRM_DATABASE_PORT',value:'3306'},
							{name:'SUITECRM_DATABASE_HOST',value:'xxxx'},
							{name:'SUITECRM_DATABASE_NAME',value:'xxxx'}
						],
  		    },
		    },
		  },
		  healthCheckConfiguration: {
				interval:5,
				healthyThreshold:2,
		  },
		});
	}
}
```
[]で囲んでいるところは適宜書き換えてください。
また、healthCheckConfigurationはデプロイ時間を短縮するために追加していますので必須ではありません。
詳しくは以下の記事を参照のこと。

https://toris.io/2021/04/speeding-up-amazon-ecs-container-deployments/

# まとめ
ECRレポジトリの作成やIAMロールの作成も行いたい場合はもう少し上記のソースコードを書き換えていただく必要があります。

ついにこれでコンテナ＆CDKデビューです（わーい）
今回作成したTypeScriptファイルを少し書き換えるだけで本番環境のデプロイコードも作成できそうです。

AppRunnerはコンテナを停止しただけでは課金は止まらないので、利用しないコンテナはcdk destroyコマンドできちんと削除してくださいね。
ノベルゲームの進捗機能を作ろうとしてハマった話

## そもそもどういうのを作ったのか
![スクリーンショット 2022-11-16 15.35.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1073363/00ca3581-43a9-11f5-9f66-03aaaa2a44df.png)
こんなのを作ってました。よくあるセリフをクリックしていくと途中で分岐が現れてその選んだ分岐の結果によってGood EndだとかBad Endだとか変わるやつです。

![スクリーンショット 2022-12-22 23.16.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1073363/55c816f5-9add-81c7-7ed0-88cd090c6d9e.png)
ハマった部分はここですねぇ
大学の実習の講義の一環でWEBサービスを立ち上げてそこにノベルゲームを実装してました。

## 何がハマったのか
先ほども説明したようにこのゲーム、分岐があるんですね。つまりこのような問題があります。

* 分岐があり、なおかつ選択した分岐によって画面に表示されるセリフの量が変化する
* 何%進んだが現在時点で表示させるので、セリフの総数が確定しない状態で進捗状況を表示させないといけない

めちゃくちゃ省略して簡易的な図で表すとこんな感じになります。
![スクリーンショット 2022-12-22 23.32.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1073363/c1680b53-d5ea-ed2e-baaa-bc46f28b3acc.png)

## 解決策としてかんがえたこと
色々考えました。
考えたこととしては
1. 100 / 総セリフ数　で一つのセリフをクリックしたらどれだけ進むかを決める
1. ゴリ押しで特定のセリフにいったら進捗をプラスしていい感じに最後になったら100%になるように手動で調整する

こんな感じです。
ただ...
1→セリフの総数が確定していない都合上不可能
2→現実的ではあるものの、新しくセリフや分岐が追加された時にバグり散らかす可能性があり拡張性に欠ける

といった問題がありました...

## 発見した解決策
色々な方法を考えていたところ、1つのことを思い出しました。
大学で木構造やその探索について学ぶ講義があったんですよね。
それを踏まえて先ほどの図をみてみると...
![スクリーンショット 2022-12-22 23.32.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1073363/c1680b53-d5ea-ed2e-baaa-bc46f28b3acc.png)
回転させて少し図を付け加えてもっとわかりやすくしてみると...
![多分木.png](https://gyazo.com/af55e35091959df9424db6f5c3452075.png)

はい、多分木の構造になっているんですよね
この多分木を「行きがけ順」で探索を行なっていきます。

## 具体的には...
手順を説明していきます。
1. まず「根」となるノードに100という値を付与します
1. 次にその根から分岐している「葉」の部分(葉が一つのセリフとなる)と分岐の集合という2種類に分け、 100 / (葉の数 + 1) ←1が分岐の集合 といった計算をし、その出た値を葉と葉ではないノードで均等に分割していきます
1. 次は葉ではないノードを訪れ、さらにその子ノードを先ほどでた値を分岐分の数で割る...ということはせず分岐先にそれぞれ先ほど出た値をそのまま付与させます(理由としてはユーザは数ある分岐の一つしか訪れず、分けると結果的に100%にならないからです)
1. あとは2の手順を繰り返します

言葉だけだと分かりにくいと思うので図で説明するとこんな感じです。
黒が根ノードと分岐、赤がセリフ、緑が分岐をまとめているノードになっています。
![多分木説明.png](https://gyazo.com/42793e6456bb2e1df2d80c646b43941a.png)
注意点としてはユーザは必ず緑のノードを訪れる前にその緑のノードに対する兄弟ノードを全て訪れます。

## 何がハマったか(その2)
このアルゴリズムを思いつくまでにかなり時間がかかりましたが、コードを書くのも苦労しました。
こういう探索系は再帰をほぼ必ずと言っていいほど書かないといけないと思うんですが、僕再帰を書くのがとても苦手なのでかなり頑張りました。おかげて再帰は鍛えられました。

これがコードです。今見返すとと少し冗長な部分がありますね...(三項演算子で事足りる部分があったりだとか)
![コード説明.png](https://gyazo.com/ed579ef5b4cea9b7f148f6c5083d587c.png)

## 感想
なんとなく受けてた大学の講義ってひょんなところで役に立つんだなぁと思いました。長年の伏線を回収した感じがします。
VRM1.0を使うメモ（URP対応含む）

# はじめに

VRM1.0を使ってアバターを読み込む時のメモ書きです。

# メモ

## VRMの導入

https://github.com/vrm-c/UniVRM/releases/tag/v0.108.0 に従う。

パッケージマネージャーを使うのが楽。`Packages/manifest.json`の`dependencies`に次を追記してEditorを開けば自動インポートが実行される。

ややこしいが、`com.vrmc.univrm`が0.x系、`com.vrmc.vrm`が1.x系。混在可能。

```javascript
// Packages/manifest.json
{
  "dependencies": {
    // ...
    "com.vrmc.vrmshaders": "https://github.com/vrm-c/UniVRM.git?path=/Assets/VRMShaders#v0.108.0",
    "com.vrmc.gltf": "https://github.com/vrm-c/UniVRM.git?path=/Assets/UniGLTF#v0.108.0",
    // VRM 0.x系を使う場合
    "com.vrmc.univrm": "https://github.com/vrm-c/UniVRM.git?path=/Assets/VRM#v0.108.0",
    // VRM 1.x系を使う場合
    "com.vrmc.vrm": "https://github.com/vrm-c/UniVRM.git?path=/Assets/VRM10#v0.108.0",
    // ...
  }
}
```


## VRMを読み込んで扱う

`Vrm10.LoadPathAsync`または`Vrm10.LoadBytesAsync`を使えばOK。

```cs:pathを渡す場合
Vrm10Instance instance = await Vrm10.LoadPathAsync(@"vrmへのパス", ct: token);
```

```cs:byte[]を渡す場合
byte[] data = await File.ReadAllBytesAsync(@"vrmへのパス");
Vrm10Instance instance = await Vrm10.LoadBytesAsync(data, ct: token);
```

`Vrm10Instance`からVRMの各種コンポーネントへのアクセスが可能。

```cs
// Vrm10Instanceから各種情報にアクセス可能
instance.gameObject;
instance.Vrm.Meta;
instance.Runtime;

// ちなみに Vrm10Instance は MonoBehaviourを継承してます
```

## Universal Render PipelineでVRMを使う

URPの環境下でVRMを読み込む場合はマテリアルの設定を変更する必要あり。
とくに`UnityEditor`上で設定する必要はなく、ロード時にインタフェースへ実装を渡せば
自動でやってくれる。

```cs
var instance =
    await Vrm10.LoadPathAsync(path: @"vrmへのパス",
        // materialGeneratorを指定することでURP対応のマテリアルで読み込んでくれる
        materialGenerator: new UrpVrm10MaterialDescriptorGenerator(),
        ct: token);

```

## VRM1.x系ライブラリでVRM0.xのデータを読み込む

VRM 1.0はVRM 0.x系のデータをマイグレーションして読み込むことができる。
ロード時に`canLoadVrm0X`へ`true`を指定すればよい（なおデフォルト`true`）

```cs
var instance =
    await Vrm10.LoadPathAsync(path: @"vrmへのパス", 
        canLoadVrm0X: true,
        ct: token);
```
JavaScript で「 〇〇 と同じ 」的な チェックボックス によるフォームの 自動入力 を実装する

例えば複数人で泊まるホテルを予約する時、宿泊者の情報を入力する際に2人目以降の住所記入欄に「代表者と同じ」みたいなチェックボックス があったりしますよね。でチェック入れたら1人目のフォームに入れてる住所が勝手に入力されるみたいな。それやります。

## 仕様
「代表者（1人目）と同じ情報を入力する」というチェックボタンを入れると2人目の入力フォームに1人目のフォームの値がコピペ される。
その後2人目のフォームを編集した場合チェックボックスのチェックが外れる

## 実装
以下のようなHTMLファイルでフォームを作成していることを想定します。

~~~html
<!-- フォーム例 -->

<!-- 1人目 -->
<div class="first">
    <input type="text" id="name1" required>
    <input type="text" id="address1" required>
</div>

<!-- 2人目 -->
<!-- チェックボックス  -->
<input id="is_same_as_1" type="checkbox" /><label for="is_same_as_1">代表者（1人目）と同じ情報を入力</label>

<div class="second">
    <input type="text" id="name2">
    <input type="text" id="address2">
</div>
~~~

私が実際に実装したのはDjangoのHTMLで、フォームはinputタグではなく別のファイルで定義したものを引用する形でした。
その場合は、divタグで囲うなどでもして無理やりidを与えるか、生成されたフォームを直接右クリック→「検証」を押して自動生成されたidを調べるだけでも大丈夫です。

## JavaScript でフォームの自動入力
htmlファイルのbodyタグの最後にscriptタグで以下のコードを書きます。

~~~html
<body>

...

<script>
  $(function () {
    // チェックボックスを指定
    let is_same_as_1 = document.getElementById('is_same_as_1');

    // チェックを入れたときコピペを実行
    $('#is_same_as_1').click(function(){
      let name1 = $('#name1').val();
      let address1 = $('#address1').val();
      if (is_same_as_1.checked == true){    
        $("#name2").val(name1);
        $("#address2").val(address1);  
      }
    });

    // 2人目のフォームを編集したときチェックを外す
    $('#name2').change(function() {
      $("#is_same_as_1").prop("checked", false);
    });
    $('#address2').change(function() {
      $("#is_same_as_1").prop("checked", false);
    });
  });
</script>

</body>
~~~
終わり
【 Django 】 ManyToMany の中間テーブルを手動で定義して実際に動かすまで

## ManyToMany Field を使ったモデル
多対多（n対n）で紐づくモデルの設計方法は以前の投稿で書きました。

https://qiita.com/haruki-lo-shelon/items/6aa725e6a403bfde68be

今回は、ManyToManyField で自動生成される中間テーブルをあえて自分で作成して、独自の中間フィールドを追加して動かすまでを備忘録として残します。

## 中間テーブルの手動生成
以前のモデルを利用します。

本来は ManyToMany を設定しただけで中間テーブルが自動で生成されるのですが、それだと独自に中間フィールドを設けられないので、手動で中間テーブルを作成します。

以下のようにコードを追加します。

~~~python:model.py
from django.db import models

class Publication(models.Model):
    """
    出版物モデル
    """

    title = models.CharField(max_length=30)

    class Meta:
        ordering = ['title']

    def __str__(self):
        return self.title

class Article(models.Model):
    """
    記事モデル
    """

    headline = models.CharField(max_length=100)
                   # ↓ここで多対多の設定
    publications = models.ManyToManyField(
        Publication,
        related_name="articles",
        through="PublicationArticleRelation", # ←中間テーブル名の指定を追加
    )

    class Meta:
        ordering = ['headline']

    def __str__(self):
        return self.headline

# ここから中間テーブルの定義（追加）

class PublicationArticleRelation(models.Model):
    """
    出版物と記事の中間モデル
    """

    publication = models.ForeignKey(
        Publication,
        related_name="publication_relation",
        verbose_name="出版物",
        on_delete=models.CASCADE,
    )
    article = models.ForeignKey(
        Article,
        related_name="article_relation",
        verbose_name="記事",
        on_delete=models.CASCADE,
    )
    
    # 独自の中間フィールド
    thema = models.CharField(
        "テーマ", max_length=16,
    )
~~~
articleモデルの ManyToManyField のthrough引数と、ソース後半の中間テーブル定義の部分を追加しています。

## 操作してみる
リレーションの作成例

~~~python
p1 = Publication.objects.get(id = 1)
a1 = article.objects.get(id = 1)

PublicationArticleRelation.objects.create(
    publication = p1,
    articke = a1,
    thema = "音楽"
)
~~~
このように、基本的には普通のモデルと同じように扱うことができる。

上記で作成したリレーションを呼び出すときは以下のようにすれば良い。
~~~python
# 出版物がp1のリレーション
PublicationArticleRelation.objects.get(publication = p1)

# 記事がa1のリレーション
PublicationArticleRelation.objects.get(article = a1)

# テーマが音楽のリレーション
PublicationArticleRelation.objects.get(thema = "音楽")

# 出版物がp1かつ記事がa1かつテーマが音楽のリレーション
PublicationArticleRelation.objects.get(publication = p1, article = a1, thema = "音楽")
~~~
c++ create folder if not exist

```cpp
#define _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING 1;
#include <experimental/filesystem> // or #include <filesystem> for C++17 and up
    
namespace fs = std::experimental::filesystem;

int CreateFolderIfNotExist(std::string folder) {
    if (!fs::is_directory(folder) || !fs::exists(folder)) { // Check if src folder exists
        std::ostringstream oss;
        oss << "mkdir " + folder; // prevent space in source path
        std::string command = oss.str();
        std::cout << command << std::endl;
        system(command.c_str());
    }
    return 0;
}
```
TypeScript  string | null →string の型変換　型アセーションを使う

# 概要
typescript Nextjsの実装中 型合わせるのに苦労したので記事にします。

# エラー箇所

requestBodyに入れるCustomer型の変数emailの型は　string | null でありStateのemailと型と一致しており　エラーになりません。
<Input value の型はstringなのでStateのemailと型が合わなく　コンパイルエラーになります。
以下具体的なソース
``` sample.tsx
import {useState } from "react";
import { Customer } from "../../types/customer";
import { Button, CarouselItem, Form } from 'react-bootstrap';
export default function Sample(){
	const [email,setEmail] = useState<string | null>(null);
    const requestBody : Customer  ={
        email,		// Customer emailの型は string | nullなので コンパイルエラーにならない
    } ;
	return(
		<>
			<Form.Group className="mb-3" controlId="formBasicEmail">
				<Form.Label>Email address</Form.Label><br />
				<Form.Control 
					type="email" 
					placeholder="Enter email"  
					value  = {email} /><br />　//  valueの型は stringなのでコンパイルエラーになる
	</>
	)
}

```


``` customer.d.ts
export  type Customer = {
	email:string | null
	password:string | null
}

```


# 対処法
型アセーションを使う

```diff_typescript
import {useState } from "react";
import { Customer } from "../../types/customer";
import { Button, CarouselItem, Form } from 'react-bootstrap';
export default function Sample(){
	const [email,setEmail] = useState<string | null>(null);
    const requestBody : Customer  ={
        email,		// Customer emailの型は string | nullなので コンパイルエラーにならない
    } ;
	return(
		<>
			<Form.Group className="mb-3" controlId="formBasicEmail">
				<Form.Label>Email address</Form.Label><br />
				<Form.Control 
					type="email" 
					placeholder="Enter email"  
-					value  = {email} /><br />
+					value  = {email as string} /><br />　
	</>
	)
}

```


# 参考
オライリー　「プログラミングTypeScript」 6.6.1 pp153
esp32cam sdカードから画像を出す

# はじめに
esp32camは簡単にファイルをftpserverに上げることができる。（以下のライブラリー）しかし、sdカードから画像を読み出して送る方法がわからなかったので紹介する。

https://github.com/ldab/ESP32_FTPClient

# コード

```readImage.c
void sendImageFromSD(char* filename) {
  File file = SD_MMC.open(filename);
  if (!file) {
    Serial.println("Failed to open file");
    return;
  }
  Serial.print("  FILE: ");
  Serial.println(file.name());

  ftp.OpenConnection();
  ftp.InitFile("Type I");
  ftp.NewFile(file.name());
  uint8_t* fileinput;
  fileinput = (uint8_t*)malloc(file.size() + 1);
  file.read(fileinput, file.size());
  fileinput[file.size()] = '\0';
  ftp.WriteData(fileinput, file.size());
  free(fileinput);
  ftp.CloseFile();
}
```
SDカードやFTPClientなどの初期化は必ずやってください。SDカードのファイル数が一定以上になったらftpに送るなどできると思います。ftpClientにはMakeDir関数もありますので体裁を整えて送ることもできそうです。

# 最後に
バッテリーで動かす防犯カメラとして動かす場合電力の消費を抑えれそうです。ちなみにftpServerはservaというものを使ってます。

https://www.vercot.com/~serva/download.html
【 Django 】Userモデルのインスタンスにパスワードを１行で設定する

## パスワード以外の保存
フォームなどを使ってユーザー情報を登録したり編集したりするとき、名前やメールアドレスに関しては

~~~python
if (request.method == 'POST'):
    name = request.POST['name']
    email = request.POST['email']
user = User(name=name, email=email)
user.save()
~~~

のようにまとめて一気に save() 、みたいな感じでできます。

## パスワードの保存
しかしパスワードはセキュリティの観点から、ハッシュ化して保存する必要があります。（平文でも保存できるけどPOST通信内容見られたらパスワードがバレる）

そのため、以下のようにして保存します。めっちゃ簡単。

~~~python
if (request.method == 'POST'):
    name = request.POST['name']
    email = request.POST['email']
user = User(name=name, email=email)
# ↓この行を追加
user.set_password(request.POST['password']) # この行で保存
user.save()
~~~
Rustのwasm-bindgenで浮動小数点のCast評価

[wasm-bindgen](https://rustwasm.github.io/docs/wasm-bindgen/reference/types/number-slices.html)を見ながらCastの練習

JS <-> Rust間の値の受け渡しを評価。
Float64ArrayでRustに渡してJSにFloat64Arrayで返す。

途中Rust内で、f64 -> vec -> ndarray matrix -> vec -> Box<[f64]> に変換


```:Curgo.toml
[dependencies]
wasm-bindgen = "0.2.63"
ndarray = "0.15.6"
```

```js:index.js
let f64 = new Float64Array([1, 2, 3, 4, 5, 6, 7, 8, 9]);
let resf64 = wasm.take_boxed_number_slice_by_value(f64);
console.log(resf64);
console.log(resf64 instanceof Float64Array); // true
```


```rust:lib.rs

use wasm_bindgen::prelude::*;
use ndarray::prelude::*;

...


fn type_of<T>(_: T) -> String{
    let a = std::any::type_name::<T>();
    return a.to_string();
}

#[wasm_bindgen]
pub fn take_boxed_number_slice_by_value(x: &[f64]) -> Box<[f64]> {
   
    let mut vec = Vec::new();
    vec.extend(x.iter().copied());

    let matrix = Array::from_vec(vec).into_shape((3,3)).unwrap();
    alert(&format!("{:?}", matrix));

    let (w, h) = {
        let shape = matrix.shape();
        (shape[1], shape[0])
    };
    let v1 = matrix.into_shape(h*w).unwrap().to_vec().into_boxed_slice();
    v1
}
```

## メモ

- [公式のHello World](https://rustwasm.github.io/docs/book/game-of-life/hello-world.html)を見ながらwasm-bindgen環境をセットアップすると、`npm link`の記述がない為、変更の反映ができなくてハマるので注意する。`npm link`は[こちらを参考](https://qiita.com/kamykn/items/371cba5487d3c7cea8aa)にした。
- extern createはいらない。Cargo.tomlに書いたら呼び出せる
- JS<->Rustで Float64Arrayは古い記事ではpointerで渡しているが、書き方が改善している。
- ローカルのPlayground環境はJupyterが使える
```bash
$ brew install jupyter
$ cargo install evcxr_jupyter
$ evcxr_jupyter --install
```
- Jupyter上のRustでndarrayを使う場合はおまじないが必要
```rust
:dep showata = { version = "0.3", features=["show_ndarray"]}
:dep ndarray = "0.14"

use showata::Showable;
use ndarray::prelude::*;
```

## 参考

- [rustでndarrayを利用して行列を扱う方法
](https://asukiaaa.blogspot.com/2018/01/rustndarray.html)
- wasm-bindgen
    - [Number Slices](https://rustwasm.github.io/docs/wasm-bindgen/reference/types/number-slices.html)
    - [Boxed Number Slices](https://rustwasm.github.io/docs/wasm-bindgen/reference/types/boxed-number-slices.html)

- [Rust 🦀and WebAssembly](https://rustwasm.github.io/docs/book/introduction.html)
- [初めてRustでWebAssemblyするときに紹介したいチュートリアル (入門)](https://qiita.com/kamykn/items/371cba5487d3c7cea8aa)
- [evcxr_jupyter](https://crates.io/crates/evcxr_jupyter)
- [showata](https://crates.io/crates/showata)
【 Django 】 ManyToManyField を使ったモデルに対する投稿フォームを実装

django が便利な理由の一つとして、多対多のモデルのテーブルを ManyToManyField で簡単に作れるというものがあります。厳密にいうと中間テーブルを自動で生成してくれます。今回はこの ManyToManyField を使った場合、登録フォームはどのように実装すればいいかを備忘録として残します。

## ManyToManyField を使ったモデル
Django 公式のドキュメントを引用する（一部変更）。Publication (出版物)と Article (記事)が多対多の関係になっている。

この時、 ManyToManyField はどちらか片方にだけ設定されている。

~~~python:モデルファイル
from django.db import models

class Publication(models.Model):
    title = models.CharField(max_length=30)

    class Meta:
        ordering = ['title']

    def __str__(self):
        return self.title

class Article(models.Model):
    headline = models.CharField(max_length=100)
                   # ↓ここで多対多の設定
    publications = models.ManyToManyField(Publication, related_name="articles")

    class Meta:
        ordering = ['headline']

    def __str__(self):
        return self.headline
~~~

さらに加えて注目したいのが、 ManyToManyField に related_name を与えていることである。ここが引用元の公式ドキュメントには無かった部分。

これは「もう一方のモデルから見たとき、このモデルを何と呼べばいいか」を設定するものであり、後に使えるので分かりやすい名前を付けておく。

## Article 登録時に Publication を付与
ManyToManyField を持つ方のモデルのレコードを新たに登録する時に、もう一方のモデルのオブジェクトを付与するときはビューファイルのフォーム処理をしているクラスの、 save() の直後に以下のように書く。

~~~python:ビューファイルなど
（省略）
article_obj.save()

# ここから
publication_obj = Publication(id=2) # 付与したいオブジェクトを取得
article_obj.publications.add(publication_obj)

（省略）
~~~

## Publication 登録時に Article を付与
~~~python:ビューファイルなど
（省略）
publication_obj.save()

# ここから
article_obj =Article(id=1) # 付与したいオブジェクトを取得
publication_obj.articles.add(article_obj)

（省略）
~~~
基本的にはさっきと同じだが、ここで先ほどモデルで設定した related_name が役に立つ。 publication から見た article は「 articles 」という名前で呼べることになっているのでそれをそのまま使う。
このチャレンジはよくわかりませんね Add Next Best Action to Your Contacts

https://trailhead.salesforce.com/ja/content/learn/projects/put-predictions-into-action-with-next-best-action/add-a-next-best-action-to-your-contacts

# We can't find a new Contact Record Page or you have multiple Contact Record Pages. Make sure that you have a single new Contact Record Page on the Contact object. 

+ [Problem with Add Next Best Action to Your Contacts](https://trailhead.salesforce.com/ja/trailblazer-community/feed/0D54S00000JfjCi)

Check the contact object in Object Manager.
If you have multiple page layouts, you'll probably want to run them in a new playground.
オブジェクト マネージャで連絡先オブジェクトを確認します。
複数のページ レイアウトがある場合は、それらを新しいプレイグラウンドで実行することをお勧めします。
 

Please check the lightening record page on contact object
Go to contact object and check lightening record page. If this is multiple then delete wrong one. 
Also record page API should be same as challenge.
Nothing will work then try new playground.
コンタクトオブジェクトのlightening record ページをご確認ください
連絡先オブジェクトに移動し、lightening record ページを確認します。これが複数ある場合は、間違ったものを削除してください。
また、記録ページ API はチャレンジと同じである必要があります。
何も機能しない場合は、新しい遊び場を試してください


https://trailhead.salesforce.com/trailblazer-community/feed/0D54S00000G3H4pSAF
ホットペッパーのAPIを使ってランダムでご飯屋さんを提案してくれるプログラム

# きっかけ
コロナも落ち着いてきて家族や友人とご飯に行く機会も増えてきましたが、毎回違うお店に行きたいと思っても飲食店なんていくつもあるので、どれがいいか迷ってしまっていました。特に私は優柔不断なので、探して選ぶだけで何時間もかけてしまうこともありました。そこで、APIを使ってお店を勝手にいくつか選んでくれるプログラムがあったら、お店選びの指標の一つとして便利かなと思って作ってみました。

# ホットペッパーAPI
無料で使えるホットペッパーAPIを使います。利用するには登録が必要です。
ホットペッパーAPIリファレンス → [ホットペッパー | APIリファレンス - リクルートWEBサービス](https://webservice.recruit.co.jp/doc/hotpepper/reference.html)
ホットペッパーAPIはここから登録 → [APIキー発行登録 - リクルートWEBサービス](https://webservice.recruit.co.jp/register/)
(登録は数分程度で完了できます)

# プログラムの流れ
1. キーワードを入力
2. キーワードをもとに飲食店を検索
3. 検索結果を取得し、そこからランダムで飲食店を選択する

# 作ってみる
```python
import requests
import random

URL = 'http://webservice.recruit.co.jp/hotpepper/gourmet/v1/'

API_KEY = '取得したAPIキー'
KEYWORD = input('キーワードを入力：')
```
最初にPythonAPIを叩くのに必要なrequestsとリストの要素をランダムに抽出するためにrandomを呼び出します。次にリクエストURLを指定しています。リクエストURLは変わっている可能性があるので、APIリファレンスで確認してください。そして、登録申請で取得したAPIキーを設定していきます。
<br>
```python
body = {
    'key':API_KEY,
    'keyword':KEYWORD,
    'format':'json',
    'count':100
}
```
bodyにAPIキーと各パラメータを指定していきます。今回はjson形式で、検索結果の最大出力データ数を100で設定しています。
<br>
```python
response = requests.get(URL,body)
```
getメソッドでリクエストを送ります。
<br>
```python
datum = response.json()
stores = datum['results']['shop']
select_shop = random.sample(stores, 3)
for store_info in select_shop:
    genre = store_info['genre']['name']
    name = store_info['name']
    hp = store_info['urls']['pc']
    print(genre, name, hp)
```
jsonデータを取得し、さらにそこからお店のデータを取得しています。random.sampleによって、ランダムに飲食店情報を3つ選ぶようにしていますが、ここは自由に1個や10個にカスタマイズ可能です。ループ処理の中では、お店のジャンルと店名、URLを表示するようにしています。

### コード全体
```python
import requests
import random

URL = 'http://webservice.recruit.co.jp/hotpepper/gourmet/v1/'

API_KEY = '取得したAPIキー'
KEYWORD = input('キーワードを入力：')

body = {
    'key':API_KEY,
    'keyword':KEYWORD,
    'format':'json',
    'count':100
}

response = requests.get(URL,body)

datum = response.json()
stores = datum['results']['shop']
select_shop = random.sample(stores, 3)
for store_info in select_shop:
    genre = store_info['genre']['name']
    name = store_info['name']
    hp = store_info['urls']['pc']
    print(genre, name, hp)
```

# 実行例
```terminal
$ python hotpepper_api.py
キーワードを入力：札幌駅
居酒屋 炭火居酒屋 炎 北2条店 https://www.hotpepper.jp/strJ001040307/?vos=nhppalsa000016
居酒屋 ミートボーイニューヨーク MEAT BOY N.Y 札幌駅前店 https://www.hotpepper.jp/strJ001009952/?vos=nhppalsa000016
居酒屋 楓 Kaede 札幌駅前店 https://www.hotpepper.jp/strJ001028475/?vos=nhppalsa000016
```


# 参考サイト
[PythonでAPIを呼び出すサンプルプログラムをご紹介](https://rainbow-engine.com/python-api-call-sample/)
[Python基礎：APIをたたく](https://happy-analysis.com/python/python-topic-api.html)
[pythonでホットペッパーのAPIを叩いてみた](https://web-tweets.com/python/hotpepper-api/)
おまえもしかしてまだ自分がモダンC++使わなくてもいいとでも思ってるんじゃないかね？

# はじめに
現在、モダンなC++(C++14以降、ギリC++11も含んでいいと思ってる)の開発では、様々な機能の追加により過去のC++では考えられないような、セキュアかつ高効率な開発が行えるようになっている。
組み込みのデバイスでは重すぎる等といった話も、何が問題になるのかをきちんと把握すれば怖い話ではない。
そういった環境の変化を受け入れずに、過去のC++にしがみついたり、勉強をしないのは最早怠慢である。
そういった人に送る言葉は一つである。
「おまえもしかしてまだ自分が(仕事があって)死なないとでも思ってるんじゃないかね」
ということで、モダンC++を使いましょう。

# モダンC++とは
そもそもモダンC++とはなんなのか。
C++14以降、ギリC++11を含むと言いはしたが、モダンC++とは単にバージョンを指せばいいということではないと思っている。
というのも、機能が劇的に変化、追加されたものを使わないのであれば、それは最早モダンC++ではない。  
いくらでも過去の危険で低効率な開発をすることになる。

## 何がモダンC++をモダンC++たらしめるのか
先に述べた過去の危険で低効率な開発の対極に位置する、させる機能が必須になると思う。
それはC++の大きな変更に起因するので、その中から特に重要になるものを挙げていく。
- スマートポインタ
- ラムダ
- 範囲for
- auto変数
- constexpr
- enum class
- 追加された各種標準ライブラリ

# モダンC++な機能
## スマートポインタ
モダンC++に限らず、モダンな開発には必須と言ってもいい。
これも標準ライブラリのうちだが、別に書くだけの意味がある程重要なライブラリ。
ただ、ここでいうスマートポインタとはauto_ptrではない。
unique_ptr、shared_ptr、week_ptrである。
何故auto_ptrでは駄目なのかというと、まず真っ先に出てくるのがそもそも既に非推奨の機能だからである。というか、これが全てと言ってもいいかもしれない。
非推奨となる理由は色々とあるだろうが、一番の問題は権限の管理の甘さにあると思っている。
基本的にはunique_ptrのようにし、最終的に誰が管理するものなのかをハッキリするべきだが、そういった管理になっていない。
これに付随して右辺値参照やムーブセマンティクスもあるが、そこまで書くとそれだけで記事を書き起こすレベルなので割愛。

## ラムダ
可読性が上がる。便利。以上。
というのは些か投げやりすぎるのでちょっと…
最も多く使われるであろう用途は、スレッド内の処理だと思う。
これを、メソッドを作ってスレッドに渡すのでは、ちょっとした処理なんかは可読性が落ちるし、実装の手間もかかる。
そこでしか使わない処理を作る必要があるのであれば、ラムダを使って可読性を上げて手間も減らすことができる。
あと、メソッド名を考えなくて済む。

## 範囲for
コーディングの効率が上がる。  
これは間違いない。  
使えば分かるが、一度使うと元には戻れなくなる。  
イテレーターへの理解も強制的に深まる。  
更に言えば、カウンターの操作を誤ったり、配列へのアクセスを誤ったりすることがなくなる。  
vector等の配列内全てに対して処理を行う場合には、範囲forを使うようにしてしまった方がいい。

## auto変数
戻り値の型を意識せずにコーディングできるのは楽でいいし、コードを書く量が減る。
マジで減る。
テンプレート型の指定がなくなるので、書く量が減るし、あれもこれも直さないといけないというのも減る。
特にスマートポインタは記述量が増えがちなので、積極的に使っていきたい。  

## constexpr
constexprは定数を定義することができる。
マクロとは異なり副作用がなく、リテラルであれば関数だけでなくクラスも作ることができる。
C++11ではほぼマクロの置き換えのようなことしかできないが、C++14からは関数を書くように処理ができるようになり、かなり色々なことができるようになった。
ただ、当然マクロでしかできないこともあるが、純粋な文字列の置き換えになるマクロは定数としてではなく、まさしくマクロとしてだけ使えば良くなる。

## enum class
enumだが、かなり型拘束の強いenum。
C言語のintで何となく数値を入れるというような、かなり適当な扱いができたenumに比べて、厳密な型指定とenum自体の型を指定できることで、厳密な方チェックが行えるようになった。
つまり、enum型を無視した値の設定のような問題がなくなる。

## 追加された各種標準ライブラリ
ライブラリなので、モダンとは？という感じだが、ゴリゴリ書かずにライブラリに頼るのはモダンだと思うので。

### filesystem
環境依存のファイル操作のAPIが消える！神！
こいつの親戚(？)にstd::pathというのがあって、ファイルパスを+とか+/とか/とかでいい感じに整形してくれたりするのもいる。
もうファイルパスを整形するコードを書かなくていいんだ。
こんなに嬉しいことはない。

### tuple
モダンな言語ではよくあるタプル。
構造体の作らなくても、複数の型をまとめたデータ型を作ることができる。
タプルってモダンだよねってだけで入れました。

### chrono
時間の取得はほとんどシステムに依存していたが、C++標準でサポートされた。
C++11で追加された時点では、単純な時間を扱う程度でしか使えないので、結局システムのライブラリに頼らなければならない事があったが、C++20で一気に強化され、ほぼこれだけで済むようになった。

# 最後に
なんやかんやほとんどがC++11で追加された機能だが、ここで紹介したもの以外にも、if文での初期化や、各種ライブラリ・STLの追加により、どんどん便利かつセキュアなコーディングが可能になっていっている。
パフォーマンスが上がる改善が行われている場合もある。
是非、古いC++から脱却し、モダンC++で開発を進めていってほしい。
特に組み込み。
マイコンでモダンC++使わせてくれ。
木と線形系における機械学習の可視化

## 使用するアルゴリズム

- リッジ回帰
- ラッソ回帰
- 重回帰
- 決定木
- ランダムフォレスト
- エキストラツリー

以上を対象に，重要度評価をして円グラフに可視化する．

大まかなプログラムの流れ

1. 各アルゴリズムを`for`で回しながらインスタンス化
1. 重要度を算出
1. `plt.add_subplot`でそれぞれを円グラフに

## ソースコード
`X`と`y`を定義してるのを前提としています．

```python:main
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)


models = (Ridge(alpha=0.01),
         Lasso(alpha=0.01), 
         LinearRegression(),
         DecisionTreeRegressor(random_state=0,max_depth=4),
         RandomForestRegressor(max_features=3,random_state=0,max_depth=14, n_estimators=100),
         ExtraTreesRegressor(random_state=0,n_estimators=100,max_depth=5)
        )

fig = plt.figure(figsize = (24,12))

for num,model in enumerate(models):
    feature = Regression(model)
    plot_model(feature,X.collumns)
fig.tight_layout()
plt.savefig('figure.png',bbox_inches='tight',pad_inches=0.05)
```
ここに，`Regression()`はモデルを引き渡し，重要度評価をしてくれる関数として定義しています．
`try`文で木かそれ以外かを場合分けしてるけど，これが最適とは思いにくい．

```python:Regression(model)
def Regression(model):
    model.fit(X_train, y_train)
    try:
        x = model.feature_importances_ #木ならこっち
    except:
        x = model.coef_  # リッジ，ラッソ，重回帰ならこっち
    
    y_pred = model.predict(X_test)
    
    kf = KFold(n_splits=5, shuffle=True, random_state=1)
    result = cross_val_score(model,X,y,scoring='neg_root_mean_squared_error',cv=kf)
    
    print(str(model))
    print("RMSE:", -1*result.mean())
    print("-"*40)
    return abs(x)
```

続いて，`plot_model()`は重要度とラベルを引数にとって可視化する関数．

```python:plot_model(x,labels)
def plot_model(x,labels):
    fig.patch.set_facecolor('white')
    idx = np.argsort(-x)
    ax = fig.add_subplot(2, 3, num+1)
    
    ax.pie(x[idx], autopct=lambda p:'{:.1f}%'.format(p) if p>=4.0 else '',
           pctdistance=0.8, startangle=90, counterclock=False,
           # labeldistance=1.2,
           # radius=0.8,
           textprops={'color': "black",'fontweight': 'bold'} # 'size': 'small'
          )
    ax.set_title(str(model))
    ax.legend(labels[idx],fancybox=True,loc='center left',bbox_to_anchor=(0.9,0.5))
```

そんでできるのがコレ．凡例は一部を隠しています（テキトーに用意するのが面倒くさかった）．
![図1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2530290/abfa341c-8025-a1cd-fc47-74b03de65755.png)


## 参考
https://bakuage.com/ai-blog/entry/2022/05/31/090000

https://qiita.com/wakame1367/items/3e3b91c749fd943b9cb3

https://qiita.com/safari029/items/e8d737137491fd502429
【備忘録】 Git コマンド

## git rm

    git rm ファイル名
ファイルをステージングエリアから削除する。

作業ディレクトリからも削除される。

普通に作業ディレクトリでファイルを削除して git add するのと同じ結果が得られる。

## git mv

    git mv ファイル名１ ファイル名２
ステージングエリアと作業ディレクトリにおいて、ファイル１の名前をファイル２に変更する。

こちらも、作業ディレクトリで名前変更して git add するのと同じ結果が得られる。

## git add

    git add -p
ステージングエリアに追加（add）すべき変更部分を手作業で選択する。

## git reset

    git reset ファイル名
ステージングエリアをリセットしてファイルをコミット予定からはずす。

##### 強制的にoriginの最新のマスターに更新

    git reset --hard origin/master

## git checkout

    git chaeckout — ファイル名
checkout はブランチを切り替えるというコマンドでもあるが、 — ファイル名 を引数に指定すると、そのファイルの「最後にコミットしたバージョン」を作業ディレクトリに入れることができる。

rm などでファイルを削除してしまったらこれを使おう。
【備忘録】VSCodeで突然「フォン」と音が鳴るようになった

# 概要
Visual Studio Codeでのコーディング中、突然イヤホンから「フォン」という音が鳴ってびっくり。
暫く原因がわからず定期的に謎の音が鳴るので困っていたのですが、解決したので記録を残します。

# 原因
結論から言うと、コードにエラーが発生すると音が鳴るようです。
例）
音が鳴った時
```
def __init__(self)
    self.. <- 2個目のピリオドを入力時に鳴った
```
selfの後にピリオドが複数続いていますが、ピリオドは１つでよいため、２個目のピリオドで「フォン」音が発生しました。

# 対策
1. VSCodeの設定ファイルを開き（Ctrl+,）、「**Audio Cues**」を検索
1. Audio Cuesの項目を全部offにするか、「Volume」を0にする

# まとめ
いつからこの設定がONになっていたのかわからないけど、パソコンが壊れたかと思ってかなりビビったので本当にやめてほしい涙

# 参考サイト
https://zenn.dev/syoppy/scraps/15929394c12832

https://scrapbox.io/RutileProgramming/%E3%80%90VS_Code%E3%80%91VS_Code%E3%81%8B%E3%82%89%E8%AC%8E%E3%81%AE%E9%9F%B3%E3%81%8C%E9%B3%B4%E3%82%8B%E3%81%AE%E3%81%A7%E6%B6%88%E3%81%97%E3%81%9F
【手引書】 Django でWebアプリを開発する１歩目（Docker環境の構築から）

## Django を使えばPythonでSNSなどのWebアプリが作れる

SNSなどのようなWebアプリを作る場合、その方法はいくつかありますがPythonを使って開発したい場合は Django というフレームワークを使えば簡単に開発できます。本記事では0から新規アプリを立ち上げるまでの手順の備忘録を記します。

## Dockerを使うためにRancher Desktopを入れる

### ダウンロード

Rancher Desktopでは、Docker Desktopと同じようにDockerコマンドが使えます。今回はRancherDesktopを使用していきます。

公式サイトからダウンロードします。

https://rancherdesktop.io/

ダウンロードしたらアプリケーションを起動して、設定を確認します。

私が変更した点はこの辺りくらいかと思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2388259/734f2b12-90cb-197e-3e4c-bb01c0821ddc.png)

### 動作確認

ターミナルを開いて

    docker compose ps

と打ち込んで、コマンドが認識されません的なエラーが起きなければ成功です。

## Dockerの環境をプロジェクト用に整える

ここでは以下のサイトを参照していきます。

https://qiita.com/reflet/items/4b3f91661a54ec70a7dc

### ファイル作成

Django のプロジェクト用のディレクトリを作成し、その中に次の３つのファイルとディレクトリを作成します。
~~~
├ Dockerfile
├ docker-compose.yml
├ requirements.txt
└ opt （←ディレクトリ）
~~~
~~~:Dockerfile
FROM python:3.9
ENV PYTHONUNBUFFERED 1
USER root

RUN mkdir /code
WORKDIR /code
ADD requirements.txt /code/
ADD . /code/

RUN apt-get update
RUN apt-get -y install locales && \
    localedef -f UTF-8 -i ja_JP ja_JP.UTF-8
ENV LANG ja_JP.UTF-8
ENV LANGUAGE ja_JP:ja
ENV LC_ALL ja_JP.UTF-8
ENV TZ JST-9
ENV TERM xterm

RUN pip install -r requirements.txt
RUN apt-get install -y vim less
RUN pip install --upgrade pip
RUN pip install --upgrade setuptools
~~~
~~~:requirements.txt
Django==3.2.13 (←任意のバージョン)
psycopg2
~~~
~~~yml:docker-compose.yml
version: '3'
services:
  python3:
    restart: always
    build: .
    container_name: 'python3' # ←環境を２個以上作る場合はここが被らないようにする
    working_dir: '/root/'
    tty: true
    volumes:
      - ./opt:/root/opt
~~~

### コンテナを起動
プロジェクトのディレクトリで次のコマンドを打ちます。

    docker compose up -d --build

これでコンテナ起動までできました。

次回から同じコンテナに入るときは

    docker compose up -d

をプロジェクトのディレクトリでコマンドします。

ここから先はこのコマンドでコンテナに入った状態で進めていきます。

## Django アプリケーションを立ち上げる

本題の Django はここからなのに最後投げやりになってしまうのですが、あとは Django の公式サイトのとおりにやればアプリケーションの立ち上げと、さらにその先では簡単な投票サイトの作成までできます。

https://docs.djangoproject.com/ja/4.0/intro/tutorial01/

### 上記URLサイトのコマンド手順まとめ
Django確認

    python -m django --version

プロジェクトを作成する

    django-admin startproject mysite

サーバー立ち上げ

    python manage.py runserver 0:8000

コントロール + C でサーバー停止

アプリケーション作成

    python manage.py startapp アプリケーション名

これより先は上記URLサイトを参照（投票Webサイトの開発）
WITH句（SQL）

## WITH句

- 副問い合わせ（サブクエリ―）を書くことができる
- 複数書けるし、使いまわすことができる
- SQL99の規格に載っている
- Oracleでは12cから対応

書き方
```sql
WITH subqueryname1 as (
  select a,
  count(*) as kensu
  from X
  group by 1
),
subqueryname2 as (
select Y.b,subqueryname1.kensu,
from Y
    join 
    subqueryname1
    on subqueryname1.id=Y.id
)
select subqueryname2.b,Z.c,subqueryname2.kensu
from subqueryname2
join
Z
on Z.id=subqueryname2.id


```
Reactの環境構築

## はじめに
プロジェクトでReactを触るため環境構築自分で行い、理解を深める目的で書きます。

今回の構築方法は以下の流れです。
1. nvmをインストール
1. nodeをインストール
1. yarnをインストール

nvmを使ってnodeのバージョンを管理。
nodeに付帯するnpmを使ってyarnを導入。
yarnでパッケージの管理をする。
さらに、yarnでReactのプロジェクトひな形を作成。

## 環境構築
### 前提条件
- WSL2でUbuntuを使用
WSL2にUbuntuを使用する方法はご自身で調べてください。
(メモ：WSLを使う理由→　一般的に開発端末はWindows＆実際にデプロイする環境はLinux)

以下の作業はすべてWSL2のUbuntu下で行います。
```Bash
$ cd ~ //念のため
$ sudo apt update
$ sudo apt upgrade
$ sudo apt-get install curl

// 以下のサイトに接続し、nvmの最新バージョンを確認する(次のコマンドで使用する)
//https://github.com/nvm-sh/nvm
// 0.39.2の箇所を確認した最新バージョンに置き換えてください
$ curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.39.4/install.sh | bash
//Ubuntuを再起動
$ nvm -v 
$ nvm install v14.20.1 
$ node -v 
$ npm -v 
$ npm install -g yarn
$ npx create-react-app first-project
$ cd first-project
$ yarn start
```

Nodeのバージョンはページの右側に以下のように記載されています
（2023年7月現在だと v0.39.4）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2779836/1ff8d703-b9b8-f847-a857-43208609c245.png)


ブラウザで　http://localhost:3000
に接続し、画面が表示されたら成功
「Ctrl」+「c」 で終了する



## nvmのTIPS
```Bash
$ nvm ls-remote          // インストールできるnodeの一覧を表示
$ nvm install v14.20.1   //  指定したnodeのバージョンをインストールする
$ nvm install stable     //
$ nvm ls                 //インストールされているnodeのバージョン一覧を表示
$ nvm use 14            //使いたいnodeのバージョンに変更する
$ nvm alias default v18  // nodeのデフォルトバージョンを指定
```
機械学習エンジニアの悩み毎：pythonのバージョン問題

皆さん、機械学習、ディープラーニングやる時どの環境使ってますか？
僕は、JupyterNotebookを使う事が多かったですが、いつも困ってました。

# バージョン問題
これ本当に困ります。サンプル調べるとpython2.7系でしか動かない奴、python3系でしか動かない奴。
これちゃんと調べて、違いを変更すればよいのですが、結構面倒でいつも困ってました。

せっかくバージョンをあわせて作業してても、ReactとかNodeの関係で、バージョン違うのをいれる必要が出てきたり、ライブラリもグチャグチャになる事がほとんどです。

# Anacondaが解決してくれた
Anaconda！　これ使うとバージョン違いの環境から動かせたり、登録済みライブラリとかも管理できます。![スクリーンショット 2022-12-29 15.03.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/23130/3d0f037a-c0b1-8665-ea7c-034f8d8e28d2.png)

environmentでpythonのバージョンを指定できて、それ毎にパッケージの管理が出来ます。
![スクリーンショット 2022-12-29 15.04.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/23130/9f787e29-f78b-bac3-8188-c467adb3f228.png)
超便利です。

# 最近は、GoogleColaboratory
Anacondaを使ったのですが、色んなパッケージを入れたりしてると、それでもローカルが汚れてきます。
そんな時に使い始めたのが、GoogleColaboratoryです。
これは、環境利用時にパッケージを入れる感じになるので汚れません。
ただし、起動時に必要パッケージを入れないと駄目なので時間が掛かります。

googleドライブをマウントも出来るのでファイルからのデータ読み込み簡単です。
![スクリーンショット 2022-12-29 15.09.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/23130/ab887bea-2bff-9ea9-e816-e963c5341c73.png)

ファイル読み込みは、
```
fpath = "/content/drive/MyDrive/Colab Notebooks/csv/"
dt = pd.read_csv(fpath+"hoge.csv")
```
と言った感じです。

ただ、DBからデータを取得しながらは、色々と成約が出て来くるので、そういう場合は、Anaconda利用もしてます。

# 最後に
同じ悩みをお持ちの機械学習エンジニアの皆さん、良い方法教えて下さい
これからも記事を書いていきますので、モチベーションアップのためフォロー、イイねお願いします。
cpp regex filename version number and get latest version file only

Ex
- J:\prj\work\prod\lig\s069\220\scenes\vd2_s069_220_lig_v000.ma

```cpp
#include <regex>

#define _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING 1;
#include <experimental/filesystem> // or #include <filesystem> for C++17 and up
namespace fs = std::experimental::filesystem;

int getFileVersion(std::string fPath) {
    int version = 0;
    std::vector<std::string> result = {};
    std::stringstream  data(fPath);
    std::string line;
    while(std::getline(data,line,'_'))
    {
        result.push_back(line);
    }
    std::string res = result[result.size()-1].c_str();
    std::regex str_expr ("v([0-9])([0-9])([0-9])");
    if (regex_match (res,str_expr)) {
        res = res.erase(0,1);
        int ver = atoi(res.c_str());
        return ver;
    } else return -1;
}

for (const auto& seq : fs::directory_iterator(rootPath)) {
        if (fs::is_directory(seq)) {
            const auto seqStr = seq.path().filename().string();
            if (seqStr == "CharacterTest") continue;
            for (const auto& shot : fs::directory_iterator(seq)) {
                if (fs::is_directory(shot)) {
                    const auto shotStr = shot.path().filename().string();
                    
                    int latestVersion = 0;
                    std::string latestFilePath = "";
                    for (const auto& scene : fs::directory_iterator(shot.path().string()+"\\scenes")) {
                        if (fs::is_regular_file(scene)) {
                            if (scene.path().extension().string() != ".ma") continue;
                            const auto sceneStr = scene.path().filename().string();
                            // std::cout << "seq:  " << seqStr << '\n';
                            // std::cout << "shot:  " << shotStr << '\n';
                            // std::cout << "scene:  " << sceneStr << '\n';
                            std::string fPath = scene.path().filename().string();
                            size_t lastindex = fPath.find_last_of("."); 
                            std::string rawname = fPath.substr(0, lastindex); 

                            int version = getFileVersion(rawname);
                            if (version >= latestVersion) {
                                latestVersion = version;
                                latestFilePath = scene.path().string();
                            }
                        }
                    }
                    
                    if (latestFilePath != "") {
                        copyFile(latestFilePath, bkpFolder);
                    }
                }
            } 
        }
    }
```
vim ~/.zshrcで環境変数を入力する際の注意点

初めて投稿します。

ターミナルでエラーが止まらなくなり、かなり焦ったので、備忘録として投稿。

## やった事
ターミナルでvim ~/.zshrcに環境変数を入力

その後、source ~/.zshrcを入力して反映させる。

通常だとそのまま反映されて終わりだが、下記のようなエラーが出てしまい、ターミナルが止まらなくなってしまった。

```
/Users/(ユーザ名)/.zshrc:source:4: too many open files: /Users/(ユーザ名)/.zshrc

/usr/local/Cellar/rbenv/1.2.0/libexec/rbenv-init: line 94: echo: write error: Bad file descriptor
/usr/local/Cellar/rbenv/1.2.0/libexec/rbenv-init: line 94: echo: write error: Bad file descriptor
```

## 解決法
自分ではエラーを発見できず、スクールのメンターさんに確認してもらったところ、vim ~/.zshrcの中にsource ~/.zshrcを誤って記述していた事が判明。
つまりsource ~/.zshrcでsource ~/.zshrcを無限に読み込み続けた為、止まらなかった。



メンターさんにも言われたが、vim ~/.zshrcの入力はコンピューター全体に関わってくる為、次からは記述には注意するようにしたい。
Python で csv (または tsv )ファイルを読み込んで出力するプログラム

今回は Python のpandasライブラリで csv (または tsv )ファイルを読み込んで出力するプログラムを作成します。機械学習でcsv形式やtsv形式のデータセットを利用する機会が多いと思うので、備忘録的にまとめてみました。初めてPythonでデータセットを扱う方の参考になれれば幸いです。

## csv と tsv の違い

私が今回扱うデータセットのファイルはtsv形式だったのですが、最初「Python　tsv　データセット　読み込み」で検索しても、tsvではなくcsvのファイルを扱う記事ばかりがヒットしました。

しかしこのtsvはcsvと大きな違いはなく、データの内容をなにで区切っているかだけの違いです。

csvはカンマ（ , ）で区切り、tsvはタブ空白で区切ります。pandasでは読み込む際に区別して読み込む必要があります。

## プログラム
今回もGoogleColaboratoryの環境を使ってプログラムを作成します。環境開発やライブラリのインストールが必要ないので便利です。

まず結論、プログラムは以下の通りです。

~~~python
# Googleドライブに接続
from google.colab import drive
drive.mount('/content/drive')

import pandas as pd
# カラム名を指定しないとエラーが起きた
col_names = ['c{0:02d}'.format(i) for i in range(17)]
# データセットファイルを読み込む。「delimiter='\t'」入れることでtsvファイルを読み込める
df = pd.read_csv('/content/drive/MyDrive/dataset/ichiba04_review201002_20140221.tsv', delimiter='\t', names = col_names)

print(df.sort_values(by='c00'))
~~~

## プログラム解説

### GoogleDrive接続

まず最初のコードブロックの「Googleドライブに接続」では、GoogleColaboratoryをGoogleDriveに接続するためのものなのでPCのローカル環境で開発している人には関係ありません。ちなみにこの場合、csvやtsvのデータセットファイルはこのGoogleDriveに入れておきます。

実行すると以下のような画面が出てきますので、そのまま接続します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2388259/3488d0ab-065a-6b17-7600-d9def04fe8e5.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2388259/1f43df6d-4065-6a9a-d361-d4dcb338f3fc.png)

### tsv 読み込み（ csv も可）

二つ目のコードブロックでpandasライブラリを読み込み、データセットファイルを読み込みます。用意する scv や tsv ファイルは何でもいいので自分で作った適当なやつでいいのですが、私は大学の教授から研究用に頂いた楽天市場のレビューデータを使いたいと思います。

データセットファイルを読み込む前にデータセットのカラム名をc00~c16で指定するようにします。カラム名は何でも大丈夫です。これがないと読み込みでエラーが起きてしまいました。

データセットの読み込みにはread_csvを使います。csvファイルの読み込みであれば引数はファイルパスとカラム名だけでいいのですが、tsvファイルを読み込む場合は引数にdelimiter='\t'を追加する必要があります。

### 出力

そして最後に、print(df)で読み込んだ内容を出力します。

ここでdf.sort_values(by='c00')としているのは、出力するデータを任意にソートする（順番を変える）ためです。この場合、カラムc00（一つ目のカラム、今回の場合ユーザー名）を基準にしてソートするようになっています。

ソートには他にも、ascending=Falseをつけると降順になったり、df.sort_index(axis=1, ascending=False)でカラム名に基づいてソートしたりするなど、様々な手法があります。

## 結果例

参考までに、結果は以下のようになりました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2388259/ca7f8730-c155-33ba-0ef6-0a9b2036f502.png)

## おまけ：任意の列を抽出して出力

### やりたいこと
c00列とc05列だけを抽出して表示させたい。

### コード

3つ目のコードブロックを以下のように変更します。

~~~python
df2 = df.sort_values(by='c00')
print(df2.loc[:,['c00','c05']])
~~~

１行目でまず前と同じようにc00（ユーザー名）順にソートして、それを新たに作成した変数df2に格納します。

２行目でlocを使って出力するカラムを指定してprint出力します。

## 参考記事
↓read_csvの使い方

https://qiita.com/piruty/items/4981a5e88c75f58a58cb

↓ソートの手法

https://pythondatascience.plavox.info/pandas/%E3%83%87%E3%83%BC%E3%82%BF%E3%83%95%E3%83%AC%E3%83%BC%E3%83%A0%E3%82%92%E3%82%BD%E3%83%BC%E3%83%88%E3%81%99%E3%82%8B

↓列・行の抽出

https://pythondatascience.plavox.info/pandas/%E8%A1%8C%E3%83%BB%E5%88%97%E3%81%AE%E6%8A%BD%E5%87%BA
Log AnalyticsにApacheのアクセスログを保管する

# 試したこと
Apacheのアクセスログを長期保管や解析のためにLogAnalyticsに保管してみた。

# 手順

## アクセスログをJSON化する
Log Analyticsに取り込む際にデフォルトのフォーマットでは扱いづらいので、JSON化する。
またローテーションにおいて循環ログ(access.logを使い回すなど)は推奨されないので、日付を付与したログファイル名でローテーションをかける。
```/etc/httpd/conf/httpd.conf
LogFormat "{\"Timestamp\":\"%{%Y-%m-%dT%H:%M:%S}t.%{msec_frac}t\", \"RemoteIP\":\"%a\", \"Host\":\"%V\", \"Port\":\"%p\", \"Request\":\"%U\", \"Query\":\"%q\", \"Size\":\"%B\", \"Method\":\"%m\", \"Status\":\"%s\", \"UserAgent\":\"%{User-agent}i\", \"Referer\":\"%{Referer}i\"}" json
CustomLog "|/usr/sbin/rotatelogs /var/log/httpd/access_%Y%m%d.json 86400" json
```
ログ出力のサンプルはこんな感じとなる。
```access_20221229.json
{"Timestamp":"2022-12-29T14:28:59.769", "RemoteIP":"172.31.254.9", "Host":"10.162.2.102", "Port":"80", "Request":"/index.php", "Query":"", "Size":"25", "Method":"GET", "Status":"200", "UserAgent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36 Edg/108.0.1462.54", "Referer":"-"}
{"Timestamp":"2022-12-29T14:29:48.686", "RemoteIP":"172.31.254.9", "Host":"centos.localdomain", "Port":"80", "Request":"-", "Query":"", "Size":"0", "Method":"-", "Status":"408", "UserAgent":"-", "Referer":"-"}
```

## Log Analyticsにカスタムログ設定をする
あとで書く

## Log Analyticsのクエリを作成する
標準では"RawData"にまるっと一行が入る。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609092/8a41fd2c-5e7a-d0c5-6960-a97114b2f4da.png)

このままでは検索や解析がしづらいので、パースするクエリを書く
※ローカル時間をUTC時間に変換するために -9h するのがミソ
```KQL
httpd_accesslog_json_CL
| extend d = parse_json(RawData)
| extend Timestamp = todatetime(d.Timestamp) - 9h
| extend RemoteIP = d.RemoteIP
| extend Host = d.Host
| extend Port = d.Port
| extend Request = d.Request
| extend Query = d.Query
| extend Size = tolong(d.Size)
| extend Method = d.Method
| extend Status = d.Status
| extend UserAgent = d.UserAgent
| extend Referer = d.Referer
| project Timestamp, RemoteIP, Host, Port, Request, Query, Size, Method, Status, UserAgent, Referer
```
以下のように整形されて出力できる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609092/bdfb46d1-46fa-7a3b-ba63-11a0aa7c320c.png)
自作Go言語WebAPIをEC2上で デーモン化する。簡単だけどつまづきやすいポイントもあった。

今回はGo言語で作成した自作のお問い合わせメールAPIを、EC2からログアウトした後でも デーモン によってmain.goが実行され続けるようにします。つまりパソコンを閉じてもgo run main.goの処理が止まらないようにします。

前回、EC2環境で実行させることでAPIがインターネットにつながり、ようやくWebAPIが実用的なものになったと思われました。

しかし実際やってみると、EC2でコマンド「go run main.go」によってAPIは動作したのですが、EC2をログアウトすると実行も途切れてしまいました。これでは実用的とは言えません。

## デーモン とは

そこで必要になるのがデーモンです。

デーモンという名前ですが、怖いものではありません。

デーモンとは、バックグラウンドで実行処理をしてくれるおかげで、パソコンを閉じてもEC2のインスタンスが実行状態である限り実行ファイルの実行が途切れなくなるプログラムです。

そしてデーモン状態に切り替えることをデーモン化と言います。

EC2環境でAPIが動作することが前提です。EC2での動作方法はこちらの記事をご覧ください。

https://qiita.com/haruki-lo-shelon/items/2693a06a68f0a8fc81a6

## 手順

さて手順ですが、あまり難しくありません。

まずコマンドプロンプトからEC2環境に入ります。

    ssh -i .ssh/tokumemo-key.pem ec2-user@xxx.xxx.xxx.xxx

.ssh/tokumemo-key.pemには作成したSSHキーのファイルパス、xxx.xxx.xxx.xxxにはインスタンスのパブリックIPアドレスを入れます。詳しくは[前回の記事](https://qiita.com/haruki-lo-shelon/items/2693a06a68f0a8fc81a6)を参照してください。

次に実行ファイルを生成します。

    go build main.go

見た目では分かりませんが、これでmain.goからmainという実行ファイルを生成しました。

そしてデーモン化の設定ファイルを作成して編集します。

    sudo vi /etc/systemd/system/適当な名前.service

デーモン化するプログラムに適当な名前をつけて新規ファイルを作成します。

一応ですが、viコマンドによるファイル編集では「i」を押すと入力モードになり、「Esc」を押すとコマンドモードに戻ります。

コマンドモードにおいて「:wq」からのEnterで保存してファイルを閉じます。

以下のようにファイルを編集します。

~~~
[Unit]
Description=適当な説明
After=network.target

[Service]
Type=simple
WorkingDirectory=/home/ec2-user/go_sendEmail
ExecStart=/home/ec2-user/go_sendEmail/main
Restart = always

[Install]
WantedBy=multi-user.target
~~~

上記のWorkingDirectoryは、実行したいmain.goが入っているフォルダパスを指定します。（筆者はgo_sendEmail）

ExecStartはそのフォルダの実行ファイルとなる「main」を指定します。（main.goではありません。）

そして以下のコマンドを順に打っていきます。

まず設定ファイルの読み込み

    sudo systemctl daemon-reload

次にサービスの自動起動を有効化

    sudo systemctl enable 適当な名前.service

してサービスを起動

    sudo systemctl start 適当な名前.service

サービスの状態を確認します。

    sudo systemctl status 適当な名前.service

これでエラーが起きていなければ成功です。

### デーモン プログラム再起動の注意点

デーモンでプログラムを起動させた後に、もしファイルの変更をした場合にしないといけないことがあります。

それは、サービスの再起動です。

ここで注意なのが、サービスの再起動は

    sudo systemctl restart 適当な名前.service

で行います。restartになっていることに注意です。（私はここで、startでずっと再起動しようとしていたため手こずりました。）

デーモン化の設定ファイル（適当な名前.service）を変更した場合は、再起動の前に

    sudo systemctl daemon-reload

も実行する必要があります。

## デーモン 確認

これでexitコマンドなりコマンドプロンプトを閉じるなり、EC2からログアウトしてからAPIを実行させてみて、ちゃんと動作すればデーモン化成功です。
効果的なプレスリリースを配信するポイント！


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1180499/db481253-87ee-952f-8656-115da3b074e0.png)


# はじめに
プレスリリースを打ってはいるけれど、問い合わせが少ない、メディアに取り上げられる数が少ないと悩んている方も多いのではないでしょうか。

本記事では、メディアに取り上げられる、継続的に引用されるプレスリリースの配信方法について解説します。

# タイトルは短く不足情報はサブタイトル
プレスリリースのタイトルは、少なくとも1行に収まる程度にしましょう。なぜかというと、記者は朝、たくさんのプレスリリースをチェックしますが、タイトルがわかりにくく、内容が一度で把握できないものは、記事として取り上げにくいからです。

ヤフーニュースのタイトルは13文字と決まっています。なぜなら13文字が、読者にとって短時間に情報を取得しやすい文字数だからです。

どうしも13文字に収まらないという場合は、タイトルはなるべく短めに、その下のサブタイトルに補足的な内容を含めるようにしましょう。

また、タイトルは「会社（または人）が、何をして（ユーザーに）どういうメリットがある」という内容になるようにしましょう。

## アイキャッチ画像を入れる
![切り抜き (1200 × 800 px).jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1180499/770e07ef-1bf2-fb2e-3f6c-12229fa11ad3.jpeg)


せっかく素晴らしいプレスリリースを書いても、アイキャッチ画像がないと、記者の注目も、読者のエンゲージメントも下がってしまいます。プレスリリースにはインパクトのあるアイキャッチを入れるようにしましょう。

アイキャッチ画像は、製品やサービスを連想できるものが良く、可能であれば写真の上にプレスリリースのタイトルか製品やサービスの名称の文字を入れるようにしましょう。

### 内容は項目別に分けてわかりやすく

文章が、改行もなく長文がダラダラと書かれているだけでは、多くの読者が途中で離脱してしまいます。内容を項目別に分けて、文章を書くようにしましょう。

また、結論を先に書くようにすると、忙しい記者や読者に、内容を理解してもらいやすくなります。

### 文章は短く、3行程度で1行の改行を入れる

プレスリリース全体の文章が長すぎても、読まれない傾向があります。全体の文字数は、600文字～1,000文字以内にするとよいでしょう。また、プレスリリースの一文もなるべく短く、約3行ごとに、1行空けるようにすると読みやすくなります。

## 製品やサービスの内容を図解する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1180499/9e4d80f5-f973-7a77-74e8-db84da0bcffb.png)


### 製品やサービスの内容が、一目でわかるような図を入れましょう。

忙しい記者や読者はプレスリリースの全文を読んでいる時間がありません。一目でわかる図を入れることで、内容が伝わりやすく、多くの人に取り上げてもらいやすくなります。

## 効果的な時間に配信する
プレスリリースを配信するのは、朝9時前が良いでしょう。なぜなら、毎朝、記者が出社して、プレスリリースを確認するのが朝9時だからです。忙しい記者は、朝から何百本、何千本とプレスリリースをチェックします。なるべく記者の目に留まりやすい時間帯に配信するようにしましょう。

# まとめ
プレスリリースをただダラダラと書いていただけでは、メディアの注目度も上がりませんし、一般読者のエンゲージメントも上がりません。タイトルとアイキャッチ画像を工夫するだけでも、かなりの効果がありますので、ぜひとも参考にしてみてください。
ZodでGenericsを使った型定義をする

# 概要

下のようにGenericsを使った型定義を使うことがありますね。
```ts: Genericsを使った型定義
type AnyRecord = { [key: string]: any };
type WithProps<P extends AnyRecord> = {
  props: { name: string } | P;
};
```
```ts: Genericsを使った型定義を利用する
type WithId = AddProps<{ id: number }>;

// type WithId = {
//   props:
//     | {
//         id: number;
//       }
//     | {
//         name: string;
//       };
// };
```
同様のSchemaをZodで定義することができます。

```ts: ZodでGenericsを使った型定義
const anyRecord = z.record(z.any());
type AnyRecord = z.infer<typeof anyRecord>;

const withProps = <T extends z.ZodType<AnyRecord>>(schema: T) =>
  z.object({
    props: z.union([z.object({ name: z.string() }), schema])
  });
type WithProps = z.infer<typeof withProps>;
```
```ts: ZodでGenericsを使った型定義を利用する
const withId = withProps(z.object({ id: z.number() }));
type WithId = z.infer<typeof withId>;

// type WithId = {
//   props:
//     | {
//         id: number;
//       }
//     | {
//         name: string;
//       };
// };
```

# 参考
https://zod.dev/?id=writing-generic-functions
サーチ＆ディスカッションって良いじゃんって話

## ことの発端
　私のチームでは、スクラムイベントとして毎週ふりかえりを実施しているのですが、その中で「このテストコードの可読性が低いのでチームで改善する取り組みがしたい」って話になりました。しかし、そのテストコードを改善しようにも、可読性の高いテストコードのノウハウを持つ人物がチームにおらず、勉強会をするにも参考書籍などにも当てのない状況でした。
　そんな会話をしていると、ふりかえりに参加して頂いていたスクラムコーチから「みんなでググってそれを持ち寄って読み合わせしてみるのはどうですか」と提案を頂きました。
　これまでそんな形式の勉強会（？）をやってみたことのない私たちチームは「まずは経験！」と試しにやってみることにしました。

## やってみたこと

所要時間：全体として1時間くらい

- 各自、調べて、良さげなリンク、スライドをMiroに貼って共有する（10分くらい） ※1
- お互いが貼って共有したリンク・スライドを読む時間をとる（10分くらい）
- 読んでみてどれが一番良かったかなどを話してみる（10分くらい）
- 実際に今学んだことを最近のコードに当てはめて、議論してみる（20分くらい）
- 当てはめてみて学んだことをまとめる（10分くらい）

※1　キーワードを予め共有しておくと調べやすい、今回は「テストコード リーダブル」
※2　読んで良いと思ったポイントや、要点をまとめたものをMiroのふせん機能で貼っておく　　

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/815054/5b020757-3029-5b04-ba7e-bb7307151bee.png)

## やってみて

### 良かった点

- Miroを活用することで共有しやすく視覚的に理解しやかったので議論がスムーズに進んだ
- 学んで読み合わせるだけでなく実際にコードに反映させてみた
- 最終的に共通認識をまとめることでチームの意識が揃った

### 改善できそうな点

- 調べる時間、読み合わせる時間はミーティング以外でも良かったかもしれない

## 今回の感想

　今回の取り組みをチームでは「サーチ＆ディスカッション」と呼んでいます。初めて行った形式でしたが、進行も滞ることなくチームとして納得する結果を得ることができました。またチームでなぜ良かったのかを振り返った際に、「インプット」「アウトプット」「学びの抽象化」をセットで行えたことが良かったという話になりました。他のテーマについてもやってみようと思います。