OmniAICreator/Qiita-1.07M huggingface dataset:
【SwiftUI】MutationObserverを使用してWKWebViewの要素をブロックする

# はじめに
[Chrome拡張を作成した](https://qiita.com/SNQ-2001/items/94b86df3aea7e9d854c7)際にJavaScriptのMutationObserverというものを知りました。

WKWebViewでも動いたので方法を記録しておきます。

「HTMLを監視して変化があったら処理を実行できる」という機能だと認識しています。
普段、ほとんどiOSしかやってないので全く知りませんでした。

WebViewは以下のコードを使用します。

https://qiita.com/SNQ-2001/items/d86685481b4697e81e38

# サンプルアプリ
「Googleでログイン」と「Appleのアカウントでログイン」を非表示にします。
|||
|-|-|
|![simulator_screenshot_B379A600-6655-4A73-827F-8F108E7C6E5B.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1745371/6a9883b1-99d7-7090-fe10-d67eb2dcbf68.png)|![simulator_screenshot_68AD93AF-6671-477D-98E0-0CC6955E7E05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1745371/1a6b371b-8285-449c-c54a-61c283c2b206.png)|

# 実装
```swift
import SwiftUI
import WebKit

struct ContentView: View {
    var body: some View {
        WebView(url: url, configuration: configuration) { webView in
            webView.customUserAgent = customUserAgent
        }
    }

    var url: URL? {
        URL(string: "https://mobile.twitter.com/i/flow/login")
    }

    var customUserAgent: String {
        "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36"
    }

    var configuration: WKWebViewConfiguration {
        let mutationObserverScriptString = """
        const targetNode = document.body;
        const config = { attributes: true, childList: true, subtree: true };

        const callback = function(mutationsList, observer) {
            for (let mutation of mutationsList) {
                if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
                    const addedNodes = Array.from(mutation.addedNodes);
                    addedNodes.forEach(function(node) {
                        if (node.matches && node.matches('[data-testid=google_sign_in_container]')) {
                            node.style.display = 'none';
                        }
                        if (node.querySelector('[data-testid="apple_sign_in_button"]')) {
                            node.querySelector('[data-testid="apple_sign_in_button"]').style.display = 'none';
                        }
                        if (node.querySelector('.css-1dbjc4n.r-18u37iz.r-lz04qo.r-13qz1uu')) {
                            node.querySelector('.css-1dbjc4n.r-18u37iz.r-lz04qo.r-13qz1uu').style.display = 'none';
                        }
                    });
                }
            }
        };

        const observer = new MutationObserver(callback);
        observer.observe(targetNode, config);
        """
        let mutationObserverScript = WKUserScript(source: mutationObserverScriptString, injectionTime: .atDocumentEnd, forMainFrameOnly: true)

        let controller = WKUserContentController()
        controller.addUserScript(mutationObserverScript)

        let configuration = WKWebViewConfiguration()
        configuration.userContentController = controller
        configuration.ignoresViewportScaleLimits = false

        return configuration
    }
}
```

# おわり
MutationObserverを使用すれば動的サイトでも要素の非表示が簡単になりました。
【Python】大量データの相関と有意差の可視化~pairplotとboxplot~


## 相関の可視化

Seabornのpairplotを使う。
デフォルトでは、Axesのグリッドを作成し、入力データの数値をx軸・y軸それぞれに配置し散布図を作成する。対角グリッドは各列のデータの周辺分布を示す。

https://seaborn.pydata.org/generated/seaborn.pairplot.html

下記コードの入力csvファイルを変更するだけで、そのcsvファイルに含まれるすべての数値データの相関グラフを作成してくれる。

```python
import matplotlib as mpl
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sns

df = pd.read_csv('./WHR_2019.csv', sep=',')

pg = sns.pairplot(df, height=2)
pg.fig.suptitle("Happiness score for each country")
plt.tight_layout()
plt.show()
pg.savefig('pairplot_default.png')

pg = sns.pairplot(df, height=2, hue='Country or region')
pg.fig.suptitle("Happiness score for each country")
plt.show()
pg.savefig('pairplot_separate.png')
```

![pairplot_default_2017.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483556/72d53edd-0807-e5a9-cc5a-63f2c8e51b7e.png)
![pairplot_separate_2017.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483556/d35b927d-16b3-2169-3bbc-a751e7167dfc.png)

## 有意差の可視化
Matplotlibのboxplotを使う。

https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.boxplot.html

アヤメのデータを使用して、各数値データの箱ひげ図を描いた。入力ファイルを変更して、箱ひげ図を描くカラム名を指定すれば、そのデータの箱ひげ図を作成してくれる

```Python
#ステップ1. Matplotlib(可視化ライブラリ) とNumpy(数値計算ライブラリ)を読み込む
import matplotlib as mpl
import matplotlib.pyplot as plt
from matplotlib.ticker import (MultipleLocator, AutoMinorLocator)
import numpy as np
import pandas as pd
import datetime
import seaborn as sns

df = pd.read_csv('student-mat.csv', sep=',')
df = sns.load_dataset("iris")

print(df)
print(df.shape, '\n') #行、列の数
print(df.index, '\n') #行数
print(df.columns, '\n') #列の名前
print(df.dtypes, '\n') #データタイプ

y_para = 'sepal_length'

for x_para in df.columns.values:
    cols_to_plot = list(set(df[x_para].values))
    data, labels = [], []
    for column_name in cols_to_plot:
        data.append(df[df[x_para] == column_name][y_para].values)
        labels.append(column_name)

    fig, ax = plt.subplots(figsize=(1.6*len(cols_to_plot), 6))
    ax.boxplot(data, labels=labels, widths=0.5, patch_artist=True, showmeans=True, zorder=0, \
                boxprops=dict(facecolor='#1E90FF80', color='black', linewidth=1),\
                medianprops=dict(color='black', linewidth=1))

    ax.set_xticklabels(cols_to_plot)
    ax.set_title(x_para)
    ax.set_xlabel('Columns')
    ax.set_ylabel('Values')
    plt.show()
    fig.savefig("iris_boxplot_%s.png"%x_para)
```
![iris_boxplot_species.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483556/1a059580-fe5f-d99e-4c14-f4312517d5c4.png)

## まとめ
今回は、Seabornのpairplotを用いた相関グラフの作成とMatplotlibのboxplotを用いた箱ひげ図の作成を紹介した。
Python でコードを書き始めて最初にできるようになるべきこと

**Python でコードを書き始めて最初にできるようになるべきこと**は
（paizaのスキルチェックだとDランク問題を解くのに必要な前提）
以下の4つだと思います。

1. 標準入力を受け取る
2. データを型変換する
3. 数値計算をする
4. 出力する

　

それぞれについて説明します。

**1.標準入力を受け取る**
標準入力を受け取るには `input()` を使うことで実現できます。
具体的には `input_line = input()` と指定することで、
「input_line」という変数に標準入力を格納します。

```Python
### ここまで説明した内容を踏まえたコード例

# 標準入力を受け取る
input_line = input()
```

　

**2.データを型変換する**
受け取ったデータを型変換するには `【データ型】(【変数名】)` と指定することで実現できます。
具体的には `int_data = int(input_line)` と指定することで、
「input_line」に格納したデータを「int」（整数）型に変換して、「int_data」という変数に格納します。

:::note info
Pythonの型は「int」以外も多くの型が存在します。
詳細はマニュアルをご確認ください。
https://docs.python.org/ja/3/reference/datamodel.html#objects-values-and-types
:::

```Python
### ここまで説明した内容を踏まえたコード例

# 標準入力を受け取る
input_line = input()

# 受け取ったデータを型変換する
int_data = int(input_line)
```

　

**3.数値計算をする**
数値計算をするにはPythonに用意された演算子を使うことで実現できます。
加算なら「+」、減算なら「-」、積算なら「*」、除算なら「/」といった具合です。
具体的には `result_data = int_data * 10` と指定することで、
「int_data」に格納したデータを「* 10(10をかける)」して、「result_data」という変数に格納します。

:::note info
Pythonの演算子は他にも多く存在します。
詳細はマニュアルをご確認ください。
https://docs.python.org/ja/3/reference/lexical_analysis.html#operators
:::

```Python
### ここまで説明した内容を踏まえたコード例

# 標準入力を受け取る
input_line = input()

# 受け取ったデータを型変換する
int_data = int(input_line)

# 数値計算をする
result_data = int_data * 10
```

　

**4.出力する**
出力は「print関数」を使うことで実現できます。
具体的には `print(result_data)` と指定することで、
「result_data」に格納したデータを出力します。

```Python
### ここまで説明した内容を踏まえたコード例

# 標準入力を受け取る
input_line = input()

# 受け取ったデータを型変換する
int_data = int(input_line)

# 数値計算をする
result_data = int_data * 10

# 出力する
print(result_data)
```

　

ここまで簡単にですが、
「Python で最初にできるようになるべきこと」を記載しました。

ここには記載していませんが、以下についてもぜひ試してみてください。
+ 複数の標準入力を受け取る
+ 別のデータ型に変換してみる
+ 別の演算子を使ってみる
+ 文字列型と数値型を一緒に出力してみる
NetBSD/armでirssi

自家製のComcerto1000のNetBSD/evbarm 10.99.2で[irssi](https://irssi.org/)をセルフでビルドしてみました。

このターゲットはUSB接続のHDDをrootfsにしているので、容量は十分にあります。

irssiはいまも開発が続いていて2日前に1.4.4がリリースされていました。

irssiのビルドには次のpkgが必要です。

* ninja-build
* meson
* perl
* pkgconf
* glib2

armv6のnon-hfなpkgはないのでv4のpkg_add -fでインストールします。

https://cdn.netbsd.org/pub/pkgsrc/packages/NetBSD/earmv4/

後はirssiのページにあるようにビルドします。ninja+mesonというビルド環境は初めてです。

irssiのリリースファイルをNetBSDのftpコマンドで拾おうとしたら、SSLのエラーでできませんでした。よく分からなかったのでFreeBSDのマシンでfetchしてtftpしました。

ツールや依存ライブラリのpkgが用意されていたので、irssiだけのビルドですみましたが結構時間がかかりました。

NetBSDには標準でtmuxが入っているようなので、この上で動かすと良さそうです。tmuxは入るのはtmux aで、出るのはclt-b+dです。

しばらく動かして様子を見たいと思います。

BitchXと使い方は同じところも多いですが、/whoなどの出力はchatとは別のwindowに表示されるようです。windowの切り替えはctl-n,pです。

windows内でのスクロールはdage-up,downで自分の使っているPCはfn+上矢印,下矢印です。

たしかちょっと前までearmv4の10のpkgは用意されてなくて、片手落ちやんってMLにポストしたのをきいてくれたのかもしれません。

irssiのpkgはないのですが、ツールや依存ライブラリ一式はあるので、十分実用になるとおもいます。

私は、PCはあまり新し物を持ってないので、Slackは重すぎてircの方がいいです。テキストメッセージをやり取りするだけなのに、なぜあんなに重いのでしょうか。新しいPCを買わせる陰謀だと思います。

端末はWin7のRLoginを使っています。コントロールキーが重複しているところがあるような気がします。ぼちぼち調べてみます。

OS入れなおして再ビルドしようとしたらmesonがpythonエラーが出て、python311を入れたりしてたら、なんとかなった。結構危ういかも。

/connect efnet - efnetへ接続
/nick hoge - ニックネームを設定
/join #zrouter - zrouterのチャンネルに入る
/part #zrouter - zrouterのチャンネルから抜ける
/quit - irssiを終了
今月、提出が必要な「割り戻し金計算確認書」はどれ？LINE Botでデータ抽出。

## LINE Botでデータ抽出
https://twitter.com/MitsuhiroM82423/status/1642131626850422785

みなさん、こんにちは。
紙、印鑑、手作業に埋もれて業務をしているX世代の会社員です。
担当業務は、ある一定期間の商品仕入れ金額に応じて、購入代金の一部を返金していただくという割り戻し契約に基づき、毎月契約先に返金金額を報告していただく「割り戻し金計算確認書」を提出していだいています。この「割り戻し金計算確認書」の報告内容をチェックした上で、金額を集計し、会社の利益として計上する業務をしています。こんな書式のものです。
![割り戻し計算確認書_例.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288463/fe5f4cc1-8ff5-cdbc-8fa4-04dd6f2a6713.png)

契約数は、約1,000契約／月。対象契約先は、約300社／月。
つまり、契約先は、平均3～4枚。多いところでは、10枚以上、この「割り戻し計算確認書」の提出が必要な場合があります。また、契約期間もバラバラで、今月はどの契約が提出必要なのか把握しづらいです。
そのため、契約先には、提出漏れがないように対象の契約データを手作業で抽出し、メールで連絡しています。
![提出依頼メール.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288463/5ee06817-d750-899b-653f-2d041383e38f.png)


## 課題

課題は、契約データ抽出も手作業、メール配信も手作業というところです。これを自動で契約データを抽出し、自動でメール配信していきたいと考えていますが、まずは、LINE Botを使い、データを抽出するところまで作成しました。
今回は、契約番号での抽出で作成しましたが、今後の課題として契約先名や、担当者名などでも抽出できるように作成していきたいと考えています。
また、複数データ抽出もできていませんので、こちらも今後の課題です。

## 使用するもの

・Make
・LINE Developers
・Googleスプレッドシート

## 作り方

Googleスプレッドシートにデータを貼り付けます。
G2のセルに数式を入力しておきます。　＝VLOOKUP(F2,A:B,2,0)

![2023-04-01_宿題提出Qiita用.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288463/99e6ec92-1f95-a871-fcad-56e07e720b0a.png)


Makeで設計した全体図です。
![2023-04-01 (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288463/16517025-bc78-39ee-cd1c-7f3fb6faa102.png)
設定詳細はこちら
![2023-04-01 詳細図_Qiita宿題提出用.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288463/e898927e-286c-4772-c41b-ea77abb462ba.png)

## 今後の取り組み

同僚から複数データ抽出についてご意見をいただきました。
Excelでこんな感じで抽出できます。
![2023-04-06 (4).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288463/ae886814-cff4-6691-daed-a457a0a7640d.png)

スプレッドシートで行い、うまくいくか試してみようと思います。
他にもいろいろと方法があると思いますので、試していきたいと思います。
少しづつできることが増えていき、最終的に自動でデータ抽出から自動でメール配信まで行うことを目指して頑張ります。
gpt4all を試行する

# 目的
gpt4all を m1 mac で実行して試す.




# gpt4all とは
local かつ cpu で実行できる軽量LLM
表面的に使った限りでは, それほど性能は高くない




# 公式
https://github.com/nomic-ai/gpt4all




# how to
公式の readme 通りで実行可

* clone
    ```
    git clone https://github.com/nomic-ai/gpt4all.git
    cd gpt4all/chat/
    ```

* download binary
    公式の link から確認する事をおすすめします 
    ```
    wget https://the-eye.eu/public/AI/models/nomic-ai/gpt4all/gpt4all-lora-quantized.bin
    ```


* run
    ```
    ./gpt4all-lora-quantized-OSX-m1
    ```



# 実行状態
![スクリーンショット 2023-04-01 20.24.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/118627/a6cd9483-63a8-0e77-6a6d-74cfa1b1f4c1.png)




# 画像生成 prompt を作成してもらう
普通の説明文っぽいが, これを入力するとちゃんとブラックホールっぽい画像が生成された.
![スクリーンショット 2023-04-01 20.37.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/118627/7f8afc3d-ceff-cb03-c558-fa0f38964e30.png)




# 所管
英語ならばある程度動作する.
日本語は無理がある.




# 動作速度
以下で動作させたが, m1 の方が早かった.
- m1 mac. mem 16G
- ryzen 1700. mem 64G

だいぶ古いCPUと環境のためか :thinking:
プロンプトとは? プロンプトエンジニアとは?

# プロンプトって何?
プロンプトエンジニアの意味が理解できなかったので調べる
prompt = console ≒ shell 
というイメージを持っていた.




# コンピュータ業界でのプロンプトとは
console の末尾行の接頭に表示されている
`ここに入力できるよ`
と示している箇所がプロンプトという定義らしい.

![prompt.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/118627/a21dd6e4-5a8a-c0f2-4d68-b7aa81c129e9.png)

https://wa3.i-3-i.info/word11367.html





# 人工知能界隈でのプロンプトとは
しっかりした定義は不明瞭だが
`人工知能に与える指示や質問文のテキスト`
をプロンプトと呼称している様子.

https://www.fiverr.com/resources/guides/business/ai-prompt-writer#:~:text=In%20plain%20English%2C%20an%20AI,model%20to%20generate%20its%20output

https://dataconomy.com/2023/01/what-is-ai-prompt-engineering-examples-how/





# プロンプトエンジニアリングとは
つまり
- 人工知能から効率的に欲しい情報を引き出すための技術.
- 人工知能を操る魔法の言葉を紡ぎ出す技術. 魔法使い.
- SQLで言うところの, database から情報を引き出す query 文を書く技術.

という様なイメージ




# 所管
- prompt という言葉のニュアンスをしっかりと把握していなので, 違和感が残る.
- 人工知能が進化した場合, 無価値になる手法も出てきそうだが, ある程度癖を掴めるものだろうか



# 英語大事
英語のテキストデータがはるかに多いらしいので, prompt を操る際は英語の方が良い様な気がする.
でも, 日本文化のニュアンスなどは, やはり日本語なのだろうか
OneDrive の「共有ライブラリの作成」を CLI for Microsoft 365 でやってみた

OneDrive の「共有ライブラリの作成」を大量に作成する場合、手動でやるのは辛いですよね。そこで CLI for Microsoft 365 を使ってラクをしようと思います。

![spo-share-library.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/675885/241c1a2b-9fee-54d5-5e8d-517b769ed68f.png)

https://pnp.github.io/cli-microsoft365/

こちらのドキュメントを参考に、下記の CLI を実行します。

```bash:bash
# CLI for Microsoft 365 をインストール
$ npm i -g @pnp/cli-microsoft365

# バージョンを確認
$ m365 version
"v6.4.0"

# ログイン
$ m365 login

# SharePoint　の一覧を表示
$ m365 spo site list \
  --output text

# 共有ライブラリ名をセット
$ sharename=infosys-share

# 共有ライブラリを作成
$ m365 spo site add \
  --alias $sharename \
  --title $sharename

# 共有ライブラリのグループ ID を取得
$ gid=$(m365 aad o365group list \
  --mailNickname $sharename \
  --query "[].id" \
  --output text)

# 共有ライブラリのグループメンバー一覧を表示
$ m365 aad o365group user list \
  --groupId $gid \
  --output text

# 共有ライブラリのグループメンバーにユーザーを追加
$ m365 aad o365group user add \
  --groupId $gid \
  --userName test001@example.com

# 共有ライブラリのグループメンバーからユーザーを削除
$ m365 aad o365group user remove \
  --groupId $gid \
  --userName test001@example.com \
  --confirm
```
推薦入門を読んだ

https://www.oreilly.co.jp/books/9784873119663/

『推薦システム実践入門』を読んだ

## 動機
検索と推薦はかなり近い領域の話だと思ったので、この機会にまとめて概要をおさえておきたかった。
協調フィルタリング、コンテンツベースフィルタリングなどの概念自体は知っていて、とても素朴なアルゴリズム(K近傍)を使った機能などは実装していたが、
より精度を高めるためには何をすればよいのかを知りたかった
PythonでMovieLensのデータを実際に試す、みたいなのはそもそも行列とかが分かってないので一旦飛ばした。
肌感みたいなのは全然得られてないので、機械学習に必要な数学を勉強してから再チャレンジしたい

## メモ

### p.9 閲覧履歴をベースにした推薦
閲覧履歴にはユーザーの嗜好がリアルタイムに反映されるので、関連アイテムを取得するアルゴリズムを実装しておくとパーソナライズも簡単でコスパがよい

### p.20 ROIを考える
推薦機能の実装はコストが高いので検索を含む各施策を出して、ROIの高いものから取り組んだほうがいい

課題 | 対応策 | 重要度 | コスト | 優先度 
--- | --- | --- | --- | ---

### p.21 概要推薦
人気ランキング、とか編集部おすすめ、みたいなものを概要推薦と呼ぶ。
特に人気ランキングのRecall、Precisionを指標として見るのが良さそう

### p.40 パーソナライズされた推薦
〇〇さんへのおすすめ、などパーソナライズされていることを強調する
情報が集まりきっていないうちに推薦すると、ユーザーががっかりして信頼感を損ねることにつながるので十分にデータが集まってからにする
気に入らないアイテムを出現させないようにするなどのネガティブなフィードバックを返す手段を用意する

### p.55 推薦理由を提示する
〇〇を見てるあなたへのおすすめ、などの推薦理由を提示することによって、運営側の利益誘導と疑われることを回避できる

### p.60 メモリベース法とモデルベース法
予測のタイミングで全データを使って計算するものをメモリベース法、事前に作成されたモデルとユーザーのデータのみを使うのをモデルベース法という

### p.70 明示的獲得と暗黙的獲得
レーティングなどが明示的獲得、クリックや滞在時間が暗黙的獲得にあたる

### p.82 未評価と不支持
暗黙的獲得では未評価と不支持を判別しにくい
3回推薦したのにクリックがなかった、など条件を設定することで区別していく

### p.83 データのスパース性
ほとんどのユーザーとアイテム間の評価値は埋まっていないことが多い。
これをデータがスパースであるという
購入データのみを評価対象にするのではなく、暗黙的に獲得された情報(クリックなど)を使って補完することが必要

### p.88 各アルゴリズムの一覧
アソシエーションルールとFactorizationMachines, item2vecが良さそうかなと思った

### p.97 アルゴリズムの評価方法
元になるデータを学習用とテスト用に分けておく。確かにと思った…
予測値と実際の評価誤差(RMSE), 何個当てられたか(Recall@K), どれぐらいの割合で当てはまっていたか(Precision@K)を使う

### p.110 アソシエーションルール
あるアイテムAとアイテムBが一緒に購入されることが多い、といった法則を見つける。
あとで実際に動かしてみたい

### p.118 ピアソンの相関係数
ユーザーやコンテンツの類似度を計算するときに使う。
これも後で動かす

### p.130 特異値分解
評価値行列を分解するときに欠損値をどうするかが大事らしい
これも後で

### p.136 Matrix Factorization
欠損値を埋めず、観測された評価値のみを使って行列分解する

### p.145 Factorization Machines
評価値だけでなく、特徴量も使える

### p.161 word2vec
ユーザーの行動履歴を単語の集合と見なしてword2vecを適用する
アイテムのベクトルさえ保持していればユーザーのベクトルを瞬時に計算できるのでオンライン推薦に使いやすい

### p.165 深層学習
コンテンツベース推薦の性能を高めることができる

### p.180 多段階推薦
候補選択 -> スコアリング -> リランキングの3段階に分けることでシステムの負荷を下げることができる

### p.182 近似最近傍探索
各ベクトルを領域ごとに分割し、インデックスしておくことで類似するアイテムを絞込める

### p.186 近似最近傍アルゴリズムの選択フローチャート
アイテム数、GPU、メモリの速度によって選択すべきアルゴリズムの図が載ってた
N < 10^6 CPUなしなのでfaiss-cpu 線形探索(IndexFlatL2)が良さそう

### p.190 ECサービスでの行動ログ例
`user_id` `item_id` `position` `location` `logic` `action` `date`
推薦ランキングの位置(position),アルゴリズムの名前(logic)などが特徴的

### p216 評価指標の実装にはバグが入りやすい
OSSなどの検証済みの実装を使ったほうがよいとのこと

### p.218 A/Bテスト実施時の仮説テンプレート
コンテキスト、変更点、対象、指標目標、ビジネス目標を明文化する

### p.221 指標の設計方針
[A/Bテストのベストプラクティスと落とし穴 ~KDD2019 レポート~ \- Gunosyデータ分析ブログ](https://data.gunosy.io/entry/kdd2019-online-experiment#%E8%89%AF%E3%81%84%E8%A9%95%E4%BE%A1%E6%8C%87%E6%A8%99%E3%81%A8%E3%81%AF%E3%81%AA%E3%81%AB%E3%81%8BSTEDI)

### p.233 バイアス
ユーザーは自分の好みの作品にしか評価をつけない、他のユーザの評価に影響される、人気の作品がより推薦されやすくなる
などのバイアスを前提にした実装が必要
【Vue】コンポーネントの動的な切り替え

# 実現すること
表示するコンポーネントをセレクトボックスで切り替える．
![コンポーネントの動的切り替え.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3301338/1ed21a2a-06ae-37e0-fc71-0c8a2ff180da.gif)
データの入力方法をユーザに自由に選ばせたり，
一つのデータを色々な方法で見せるときに使えそう．


# コンポーネントの用意
<details><summary>テキストボックス</summary>

```vue:BaseInput.vue
<template>
    <div class="dynamic-component">
        <input type="text" v-model="inputValue">
        <p>{{ inputValue }}</p>
    </div>
</template>

<script setup lang="ts">
import { ref } from "vue";
const inputValue = ref("Hello Vue!");
</script>
```
</details>

<details><summary>ラジオボタン</summary>

```vue:BaseRadio.vue
<template>
    <div class="dynamic-component">
        <label>
            <input type="radio" name="memberType" value="1" v-model="memberType" />
            通常会員
        </label>
        <label>
            <input type="radio" name="memberType" value="2" v-model="memberType" />
            有料会員
        </label>
        <label>
            <input type="radio" name="memberType" value="3" v-model="memberType" />
            非会員
        </label>
        <br>
        <p>選択されたラジオボタン: {{ memberType }}</p>
    </div>
</template>


<script setup lang="ts">
import { ref } from "vue";

const memberType = ref(1);
</script>
```
</details>

<details><summary>セレクトボックス</summary>

```vue:BaseSelect.vue
<template>
    <div class="dynamic-component">
        <select v-model="memberType">
            <option value="1">通常会員</option>
            <option value="2">有料会員</option>
            <option value="3">非会員</option>
        </select>
        <br>
        <p>選択されたリスト: {{ memberType }}</p>
    </div>
</template>


<script setup lang="ts">
import { ref } from "vue";

const memberType = ref(1);
</script>
```
</details>

# アプリケーション

- &lt;component&nbsp;:is="currentComp" /&gt;
    - 動的コンポーネントを読み込むためのタグ
    - v-bind:isで表示するコンポーネントを指定する
- &lt;keep-alive&gt;
    - コンポーネントを動的に切り替えた時に状態を保持する
    - 切り替えるたびに初期状態にしたい場合は不要

```vue:App.vue
<template>
  <h1>動的コンポーネント</h1>
  <select v-on:change="switchComp">コンポーネントを切り替える
    <option value="0">Input</option>
    <option value="1">Radio</option>
    <option value="2">Select</option>
  </select>
  <p>コンポーネント名： {{ currentCompName }}</p>
  
  <keep-alive>
    <component :is="currentComp" />
  </keep-alive>
</template>

<script setup lang="ts">
import { ref } from "vue";
import BaseInput from "./components/BaseInput.vue";
import BaseRadio from "./components/BaseRadio.vue";
import BaseSelect from "./components/BaseSelect.vue";

const currentComp = ref(BaseInput);
const currentCompName = ref("Input");
const compList = [BaseInput, BaseRadio, BaseSelect];
const compNameList: string[] = ["Input", "Radio", "Select"];

let compIdx = 0;
const switchComp = () => {
  compIdx = ++compIdx % compList.length;
  currentComp.value = compList[compIdx];
  currentCompName.value = compNameList[compIdx];

}
</script>

<style scoped>
.dynamic-component {
  border: green 1px solid;
}
</style>
```
要件定義とはなんぞや？

## はじめに
個人開発しようと考え、いざ着手しようとしたが何から手を付けたらよいか全く見当がつきませんでした。そんな中で下記の記事を読み、このロードマップに従って取り組んでいこうと考えました。

[Pythonでゼロからでもサービス開発・公開できる学習ロードマップ](https://qiita.com/Saku731/items/52a3bbacd002f26f408e)

本記事は@Saku731さんのUdemy講座の50%受講完了時点での執筆です。（随時更新する）

■入門編：

https://www.udemy.com/course/digisaku_requirements_definition_and_system_design/?couponCode=6749911953C418843C45

上記講座は実際に要件定義書を作成しながら学んでいけるので、実務を想定しながら学んでいけます。私は自己のWebサービスを作ることを想定しながら取り組みました。


## 要件定義
**目的**
- ビジネスビジネス側とエンジニア側のゴールを明確にする
- 現状からゴールまでの道筋を明確にする

**注意点**
- ビジネスサイドとエンジニアサイドで目線が違うことを意識する。

エンジニア => 日々のメンテナンス性重視
ビジネス => 使いやすさなどユーザー目線重視

**成果物**
- 要件定義書 => Word
- 機能一覧 => Excel

**進め方**
①要望（ビジネスサイド）

②要求（ビジネスサイド） 

③検討（エンジニアサイド）

④提案（エンジニアサイド）

```要件（最終着地点）```
「こんな機能を作ろう！」



## 【実践】①要件定義を作成　～要望フェーズ～
①現状の洗い出し
 - 業務プロセス
 - 現状の問題点

②ゴール設定
 - 目指す状態
 
③ギャップ分析
 - 解決すべき課題の洗い出し
 
④ヒアリング（現場・ユーザー）
 - システムに何を求めるか？

## 【実践】②機能一覧を作成　～要求フェーズ～
**項目**
 - 大項目
 - 中項目
 - 重要度
 - 要求内容

以上を踏まえ、**ゴールの達成に必要な要求**を記述。
```要件定義の④ヒアリング```内容を参考にすればよい。

## 【実践】②機能一覧を作成　～検討フェーズ～
**検討項目**
 - 課題解決できる
 - 技術
 - 予算
 - 納期

以上を踏まえ、**要求の実現性**をIT目線で検討する。

実現性が低い機能の場合、**代替案**を提供

**他の事例やトレンドをサーチ**して、企画の実現性を見極められるようにする。

## 【実践】②機能一覧を作成　～提案フェーズ～
**項目**
 - ビジネス要求へのフィードバック
 - 実装する・しない機能
 - 予算

エンジニアからの提案フェーズ。

## 【実践】②機能一覧を作成　～要件フェーズ～
**項目**
 - 開発する機能
 - やらない事
 - 最新の業務フロー図
 - 簡単な画面イメージ

最終決定のフェーズ。
**ユーザーのニーズ**を忘れないようにする。
必ず**新しい業務フロー図**に落とし込んで確認する。


## おわりに
かつては「要件定義」はPMの仕事だから関係ないや、と思っていました。
でも自分でWebアプリを開発したいと思うようになって、ちゃんとしたシステムが開発できるよう、要件定義を学びました。
今回紹介させていただいた下記の講座を通して、改めて私は「システム」をよく知らないことを痛感しました。今回の要件定義書に沿って、最初のアプリケーション開発に着手します。（それもQiitaに投稿できるようにします）

https://www.udemy.com/course/digisaku_requirements_definition_and_system_design/?couponCode=6749911953C418843C45


もっと多くのアプリケーションの事例をインプットして、目を養いたいと思います。

個人開発でも、チーム開発でも、大事なことは**ゴールを達成できること**。
**よいシステムは、よい要件定義**から。

閲覧ありがとうございました。
借りたサーバーにGOをinstallした

# 概要
https://lolipop.jp/

にGOを使えるようにインストールしました。

## 追記

https://lolipop.jp/terms/prohibition/

ここに
- C言語などによるバイナリ実行ファイルのCGIの設置
がある事を完全に見落としていました

お試し期間中なのでphpを使っていきます。goを使いたいと思ったらサーバーに乗り換えようと思います


## 動機
サーバーエンジニアになろうとしているのに一回も自分のサーバーを使ったことがないのはどうかとお思い、無料期間で一旦試したかったからです。

## 流れ
登録
↓
ssh接続
↓
goをinstall
↓
pathをつなげる

### 登録
自分がとりあえず借りたのはここです。

https://lolipop.jp/

### ssh接続

https://qiita.com/chihiro/items/c24fcbd82d1d8833e497

ssh接続をユーザーページで有効化してからじゃないとログインできないです。

### goを入れる

基本はここを参考にしました。

https://go.dev/doc/install

ただinstallするversionが下のようにいっぱいあったので

https://golang.org/dl/

~~~
$ uname -m
~~~
によって確認しました

### pathをつなげる

ダウンロードしただけだとgoは使えないので

~~~
$ touch .bash_profile
$ echo 'export GOROOT="$HOME/go"' >> ~/.bash_profile
$ echo 'export PATH="$GOROOT/bin:$PATH"' >> ~/.bash_profile
$ source .bash_profile
~~~

して実行できるようにしました。

## 感想
ささっと入れれることができたのでよかったです。

たぶん無料期間が終わったら別に映る可能性もあるので備忘録としておいときます。
【ReactHooks】useRefについて

## useRefとは
**再レンダリングを発生させずに値を保持する**方法です。コンポーネント内で可変な値を管理するために使用します。`useRef`を使用した場合、再レンダリングしても`currenct`プロパティに値を保持したままにすることが可能です。 また、`useRef`を変数に格納すると、`ref`オブジェクトを返します。

通常、DOMへの参照やタイマーIDの保存など、**再レンダリングに依存しないデータを保持するために使用されます**。代表的なのが「フォームにフォーカスする」ことや「ある要素までスクロールする」のようです。

#### .currentプロパティとは
`useRef()`に渡された初期値を含むオブジェクトのラッパーであり、オブジェクトのプロパティを持つことができます。コンポーネント内で状態を保持し、DOMへの参照などの情報を保持することができます。

#### refオブジェクトとは
Reactコンポーネントで、DOMまたはコンポーネントインスタンスへの参照を保持するオブジェクトです。React では、コンポーネントがマウントされ、アンマウントされるときに、`ref`オブジェクトを使用して DOMにアクセスすることができます。また、`ref`を使用して、子コンポーネントにプロパティーを渡すこともできます。


#### 基本形
```javascript
// 初期値はinitialValue
const refContainer = useRef(initialValue);
```

## フォームにフォーカス
`useRef`を使用して、`input`要素にフォーカスを当てる例です。
```javascript
import React, { useRef } from 'react';

function TextInput() {
  const inputRef = useRef(null);

  const handleClick = () => {
    inputRef.current.focus();
  };

  return (
    <div>
      <input type="text" ref={inputRef} />
      <button onClick={handleClick}>Focus Input</button>
    </div>
  );
}
```
`handleClick`関数内で、`inputRef.current.focus()`を呼び出すことで、`input`要素にフォーカスを当てることができます。ここでは、`input`要素がレンダリングされた後に、`useRef`が使用されています。`useRef`を使用することで、コンポーネント内で可変な値を管理して、再レンダリングに依存しないデータを保持することができます。


## 特定の要素までスクロースする
```javascript
import React, { useRef } from 'react';

function ScrollToElement() {
  const elementRef = useRef(null);

  const handleClick = () => {
    elementRef.current.scrollIntoView({
      behavior: 'smooth',
      block: 'start',
    });
  };

  return (
    <div>
      <button onClick={handleClick}>Scroll to Element</button>
      <div style={{ height: '1000px' }}></div>
      <div ref={elementRef}>Element to scroll to</div>
    </div>
  );
}
```
`useRef`フックを使用し、`elementRef`という`ref`オブジェクトを作成しています。divに`ref={elementRef}`属性を追加して、要素への参照を取得しています。そして、`scrollIntoView`メソッドを使用して、指定した要素までスクロールします。

※`scrollIntoView`メソッドは、要素の上部、中央、下部をスクロール位置として指定できます。ここでは、 `block: 'start'`を指定して、要素の上部をスクロール位置として指定。また、`behavior: 'smooth'` を指定し、アニメーションをスムーズにしています。
お母さんでもわかるReact「State」「useEffect」

## State
状態が変更されたときに、Reactは自動的にコンポーネントを再レンダリングするため、状態に応じてイベントを付与することができます。その状態とは下記のようなものです。

・フォームの入力値
・ボタンのクリック回数
・チェックボックスやラジオボタンの選択状態
・ドロップダウンリストの選択状態
・モーダルウィンドウの開閉状態
・タブの選択状態
・APIから取得したデータ
・ユーザーの認証状態
・ページネーションの現在のページ数

難しくなってしまいましたが、「素のVanillaJSのイベント系」と同じようなものと考えてもらって大丈夫かなと思っています。
```jsx
import { useState } from "react";

export const App = () => {
  const [num, setNum] = useState(0);

  const onClickButton = () => {
    setNum((prev) => prev + 1);
  }
  return (
    <>
      <button onClick={onClickButton}>ボタン</button>
      <p>{num}</p>
    </>
  );
}
```
上記コードのStateは、「ボタンがクリックされたときに何かを行う」です。

`import { useState } from "react"`
useStateはReactのフックの一つで、useState関数を用いてStateを扱っていきます。

`const [num, setNum] = useState(0)`
配列の一つ目に**State変数**。二つ目に**そのStateを更新するための関数**を設定します。
つまりnumが状態を持った変数。そして配列の中でsetNumという関数を定義しているというニュアンスになると思います。

`setNum(num + 1)`ではなく、 `setNum((prev) => prev + 1)`と記述する。
numを引数で受け取ることでよきせぬ代入を防ぐことができます。ちなみに略さないと次のようになります。`setNum(function(prev) {
  return prev + 1;
})`

詳しいことは下記の通りです。
引数は「前回の状態」を利用するので、しっかりと値が変わる
↓
```jsx
//初期値が０の場合

//numに直接代入しているとき
{
setNum(num + 1);//1
setNum(num + 1);//1
}

//引数を利用しているとき
{
setNum((prev) => prev + 1);//1
setNum((prev) => prev + 1);//2
}
```

※numの中身は`undefined`のため、`useState(0)`で０を初期値としている。
※`num,setNum`、`count,setCount`という組み合わせは暗黙の了解 。

（むずかしー）

## useEffect
```jsx
import { useState } from "react";

export const App = () => {
  const [num, setNum] = useState(0);

  //この部分を追加
  useEffect(() => {
    alert();
  }, [num]);

  const onClickButton = () => {
    setNum((prev) => prev + 1);
  }
  return (
    <>
      <button onClick={onClickButton}>ボタン</button>
      <p>{num}</p>
    </>
  );
}
```
`useEffect(() => { alert(); }, [num])`
この文で「numが変わった時に、アラートの実行する」という意味になる。
**※コンポーネントを表示した一番最初(ようは、`return`の中がDOMになるとき)にも発動されるので注意！！**
**この機能は「画面を表示して初期データを取得するとき」などに使える！**

ちなみに、
```jsx
useEffect(() => {
　　　　//マウント時の実行
  document.body.style.backgroundColor = 'lightblue';
　　　　//アンマウント時の実行
  return () => {
    document.body.style.backgroundColor = '';
  }
}, []);
```
マウント　　　　 　：DOMになるとき（コンポーネントが表示される時）
アンマウント　：コンポーネントが何らかの処理で消える時

※基本的に直接DOMをいじるような操作はNG!!
rex0220 アプリ集計プラグイン テーブルから対象区分の数値を抽出

[rex0220 アプリ集計プラグイン](https://qiita.com/rex0220/items/9a6996d46e199c83289d)で、集計元アプリのテーブルから対象区分の値を抽出して集計を行ってみます。

# 概要

集計元アプリのテーブルから、集計区分が同じものを集計します。
※アプリ集計プラグイン Ver.44 以上が必要です。

- 追加・編集画面でレコード集計
![2023-04-01_18h06_55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100572/a8e987c3-46ba-43fd-f0ac-06f5018b073a.png)

- 一覧画面で、一括集計
![2023-04-01_18h53_27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100572/d5a336cd-7bba-af0c-eb11-4af8ef32b9d4.png)


# 操作例

年月・集計区分を指定して「アプリ集計」ボタンをクリックすると、集計元アプリのテーブルから対象区分の値を抽出して集計します。

![2023-04-01_18h23_38.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100572/0591e94b-6dda-3bba-557c-0ae8960e6470.gif)

# アプリ集計プラグイン設定

![2023-04-01_18h39_12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100572/d201e0ce-3f66-df82-c1b3-f565d2ed6b69.png)

## 対象アプリ条件： 年月と集計区分を指定
![2023-04-01_18h28_10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100572/d119a01c-61b3-767e-b1ff-23fadc5fd567.png)

## 集計順：　日付順
![2023-04-01_18h29_24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100572/99bfd523-1537-bf22-3522-0c45281aecb5.png)

## 集計項目：　集計区分が同じものを取得
![2023-04-01_18h40_06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100572/2fd1326b-6def-c18b-ca1f-20280b986033.png)

計算式
```
// 数量
SUMIF(集計区分=#集計区分,数量)
```
- SUMIF ：テーブル行の値を取得するため、集計関数の SUMIF を指定
    - 集計区分=#集計区分 ：　集計元と集計先の集計区分が同じもの
        - 「#フィールドコード」は、集計先のフィールドコードの値

## テーブル項目：　集計区分が同じものを取得
![2023-04-01_18h31_18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100572/aeebc671-e51a-1568-4a00-7da8f7d78c19.png)

計算式
```
// 出力条件
集計区分=#集計区分
```
- 集計区分=#集計区分 ：　集計元と集計先の集計区分が同じもの
try/catch/finally文 例外処理メモ

javaScriptではtry/catch/finally文を使って例外を処理する。tryには例外処理の対象となるコードを記述する。
tryの後には、catchを記述する。catchにはtryブロックで例外が発生した時に呼び出される文を記述する。最後にfinallyを記述する。finallyに記述したコードはtryブロックの処理が終了した後に必ず実行される。
catchとfinallyはそれぞれ省略可能だが、必ずどちらかは記述する必要がある。

```js
try {
//ここのコードはブロックの先頭から順に実行される。
//ただし、throw文を使ったり、例外をスローする
//メソッドを呼び出して、例外が発生する場合もある
}
catch (e) {
//もしtryで例外は発生した場合に実行される
//その時に、ローカル変数eで参照できる。
}
finaly {
//ここは以下の条件のいずれかが満たされると実行される
//tryが最後まで実行された後。
//catchで例外が処理された後。
//キャッチされなかった例外が伝播すす前
}
```
```js
try {
  let n = Number(prompt('正の整数を入れてね', ''));
  let f = factorial(n);
  alert(n + '! = ' + f);
} catch (e) {
  alert(e);
}
```
上のコードはユーザーに数値を入れてもらいます。数値が正しい値ならば、その数値の階上を計算する。
正しくない場合はcatchに処理が移動し、alertでエラーをユーザーに伝える。

#### 勉強した本
https://www.amazon.co.jp/JavaScript-%E7%AC%AC7%E7%89%88-David-Flanagan/dp/4873119707/ref=pd_lpo_1?pd_rd_w=oDw4z&content-id=amzn1.sym.d769922e-188a-40cc-a180-3315f856e8d6&pf_rd_p=d769922e-188a-40cc-a180-3315f856e8d6&pf_rd_r=ZJWJEX7MXJ83Q5TEFK49&pd_rd_wg=YvEUj&pd_rd_r=53659345-9f54-40e8-b61c-e171cc478887&pd_rd_i=4873119707&psc=1
Slackbot x ChatGPT x Cloud functions

GASでやっていたが、好きなチャンネルにボットを読んでやり取りしたかったのと、権限周りの関係で、Cloud functionsでやってみた。

```package.json
{
    "name": "slack-chatgpt-gcloud",
    "version": "1.0.0",
    "description": "Slack bot with GPT-4 integration on Google Cloud Functions",
    "main": "index.js",
    "dependencies": {
      "@slack/web-api": "^6.6.0",
      "axios": "^0.26.1",
      "express": "^4.17.1",
      "body-parser": "^1.19.1"
    },
    "engines": {
      "node": "14"
    }
  }
```

API_keyやbotのkeyは、ランタイム環境変数に入れておく。
ログは、console.logで出しておく。
あと、chatgptが返答遅い時に、再度リクエストしないようにケアしておく。

```index.js
const { WebClient } = require('@slack/web-api');
const axios = require('axios');
const express = require('express');
const bodyParser = require('body-parser');

// Slack APIとOpenAI APIのキー
const SLACK_BOT_TOKEN = process.env.SLACK_BOT_TOKEN;
const OPENAI_API_KEY = process.env.OPENAI_API_KEY;

// Slack WebClientのインスタンス作成
const slackClient = new WebClient(SLACK_BOT_TOKEN);

const app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));

app.post('/', async (req, res) => {
  // タイムアウトしないためにとにかく先に返す
  res.status(200).send()
  // Slackのチャレンジリクエストに対応
  if (req.body.type === 'url_verification') {
    res.status(200).send(req.body.challenge);
    return;
  }

  const { event } = req.body;

  if (event && event.type === 'app_mention') {
    const inputText = event.text;
    const channelId = event.channel;

    try {
      const gptResponse = await axios.post(
        'https://api.openai.com/v1/chat/completions',
        {
          messages: [{ "role": "user", "content": inputText }],
          model: "gpt-3.5-turbo"
        },
        {
          headers: {
            'Authorization': `Bearer ${OPENAI_API_KEY}`,
            'Content-Type': 'application/json',
          },
        }
      );
      
      const gptReply = gptResponse.data.choices[0].message.content;

      const userInfoResponse = await slackClient.users.info({
        user: event.user,
      });
      const userName = userInfoResponse.user.profile.display_name;
      // ユーザー名とメッセージを記録
      console.log(`${userName}: ${inputText}`);
      console.log(`GPT-3.5: ${gptReply}`);

      await slackClient.chat.postMessage({
        channel: channelId,
        text: gptReply,
        thread_ts: event.ts
      });

      res.status(200).send();
    } catch (error) {
      console.error('Error:', error);
      res.status(500).send();
    }
  } else {
    res.status(200).send();
  }
  res.end()
});

const PORT = process.env.PORT || 8080;

exports.slackBot = app;
```

slackbotの設定はいつもの通り。

デプロイ
```
gcloud functions deploy slackBot --runtime nodejs14 --trigger-http --allow-unauthenticated --project your-project-id

```
【Flutter】table_calendarを使ってみた。

# はじめに
カレンダー機能を実装するための便利なパッケージが用意されていたので、　使ってみました。
こんな感じのものが作れます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1850625/57e8d67c-a150-8d1b-a732-e056a55743b6.png" width=60%>


# table_calendar
公式ページはこちらです。
https://pub.dev/packages/table_calendar

# 実装
以下、コードになります。参考にして頂ければ嬉しいです。

```dart:main.dart
import 'package:flutter/material.dart';
import 'package:table_calendar/table_calendar.dart';

void main() {
  runApp(MaterialApp(
    debugShowCheckedModeBanner: false,
    home: MyApp(),
  ));
}

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  // 日付をキャプチャする変数の作成
  DateTime today = DateTime.now();
  void _onDaySelected(DateTime day, DateTime foucusDay) {
    setState(() {
      today = day;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // スマホ画面上部の文字
      appBar: AppBar(title: Text("table_calendar")),
      body: content(),
    );
  }

  Widget content() {
    return Padding(
      padding: const EdgeInsets.all(15.0),
      child: Column(
        children: [
          // 選択した日付を表示させる。
          Text(
            "Selected Day = " + today.toString().split(" ")[0],
          ),
          Container(
            // このように追加するだけでカレンダーが表示される。
            // （表示自体はfocusDay、firstDay、lastDayの３つでOK！）
            child: TableCalendar(
              focusedDay: today,
              firstDay: DateTime.utc(2020, 4, 1),
              lastDay: DateTime.utc(2030, 3, 31),
              rowHeight: 60,
              // デフォルトで表示されている２weeksを消し、年月表示を中央揃え
              headerStyle:
                  HeaderStyle(formatButtonVisible: false, titleCentered: true),
              // availableGestures: AvailableGestures.all,
              // 選択した日付にもマークを出す
              onDaySelected: _onDaySelected,
              selectedDayPredicate: (day) => isSameDay(day, today),
            ),
          ),
        ],
      ),
    );
  }
}

```
タワーデフェンスゲーム　Unity　敵の機能　4/10 敵が終点に到着した時　残機が減るよう実装

# 概要
前回の続きです。

https://qiita.com/RYA234/items/fa9f1e49b7b97233c4d1

今回は残機機能を実装します。
以下gifアニメは　敵オブジェクトが終点に到着し残機(赤枠部分)が10→9に変化しているときの様子です。

![Enemy1222222333.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710623/68d3dbc0-7258-4e03-2c56-0d3dd352b84f.gif)


次へ

https://qiita.com/RYA234/items/9ba3908eec0a15709e91

# 開発環境

IDE:Rider
Unity:2020.3.42(LTS)
OS:Windows10

# 実装のポイント
オブサーバーパターンを作成します。

EnemyクラスでReturnENemyToPoolメソッドが実行されたら、LevelマネージャクラスのReduceLivesメソッドが実行されます。
Observale = Enemyクラス
Observer = LevelManagerクラス OnEnable or OnDisableでイベントを登録しています。


# コード部分
```diff_c_sharp:LevelManager.cs
using System;
using UnityEngine;


public class LevelManager : MonoBehaviour
{
    [SerializeField] private int lives = 10;

    public int TotalLives { get; set; }
    private void Start()
    {
        TotalLives = lives;
    }

    private void ReduceLives()
    {
        TotalLives--;
        if (TotalLives <= 0)
        {
            TotalLives = 0;
        }
    }
    private void OnEnable()
    {
        Enemy.OnEndReached += ReduceLives;  
    }

    private void OnDisable()
    {
        Enemy.OnEndReached -= ReduceLives;  
    }
}



```


```diff_c_sharp:Enemy.cs
using System;
using UnityEngine;

public class Enemy : MonoBehaviour
{
+    public static Action OnEndReached;
    
    [SerializeField] private float moveSpeed = 3f;
    [SerializeField] private Waypoint waypoint;
    public Vector3 CurrentPointPosition => waypoint.GetWaypointPosition(_currentWaypointIndex);
    private int _currentWaypointIndex;

    private void Start()
    {
        _currentWaypointIndex = 0;
    }

    private void Update()
    {
        Move();
        if (CurrentPointPositionReached())
        {
            UpdateCurrentPointIndex();
        }
    }

    private void Move()
    {
        Vector3 currentPosition = waypoint.GetWaypointPosition(_currentWaypointIndex);
        transform.position = Vector3.MoveTowards(transform.position, currentPosition, moveSpeed * Time.deltaTime);
    }
    
    private bool CurrentPointPositionReached()
    {
        return Vector3.Distance(transform.position, CurrentPointPosition) < 0.1f;
    }

    private void UpdateCurrentPointIndex()
    {
        int lastWaypointIndex = waypoint.Points.Length - 1;
        if (_currentWaypointIndex < lastWaypointIndex)
        {
            _currentWaypointIndex++;
        }
        else
        {
            ReturnEnemyToPool();
            
        }
    }

    
    private void ReturnEnemyToPool()
    { 
+        OnEndReached?.Invoke();
        ObjectPooler.ReturnToPool(gameObject);
    }
}


```

# 参考

https://qiita.com/Yamara/items/334c3f4d2cb97f91bd4f

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710623/965f9ddb-6761-f59f-c1df-bce190332c88.png)


公式ドキュメントは抽象的過ぎて難しいですね

https://learn.microsoft.com/ja-jp/dotnet/api/system.action?view=net-7.0




github　コミット分

https://github.com/RYA234/2DTowerDeffenceMyLearning/commit/03bf84888e6f3f23c60437693bebb957ae7b2087

章まとめ

https://qiita.com/RYA234/private/6d878a7958a3c760c8af
【DB】インデックスとは？

## はじめに
インデックスについて理解があいまいだったので本で読んだ内容をまとめました。

## インデックスとは
キー値とそれに結びつく情報（実データ/ポインタ）をもつデータの配列。
インデックスを使用しない場合、DBMSはテーブルのデータを順に辿ってデータを探す（＝フルテーブルスキャン）必要があるが、インデックスを使用することで特定のデータに狙い撃ちでアクセスできる。

## B-treeインデックスとは
最もポピュラーなインデックスの形式。
ツリー（木）構造になっており、ツリーを上から順に辿って目的のデータを検索する。→１段辿るごとにデータが絞られていく。
**ツリー内のデータはソートされた状態。**

## B-treeインデックスの特徴
**1. キー値（＝絞り込む項目）による検索速度のばらつきが小さい**
- B-treeは最上位から最下層のノードの距離（高さ）が一定であるため（＝平衡木）、
キー値に何を指定しても一定の速さで検索できる。

**2. テーブルのデータ量が増えてもインデックスの検索/更新にかかる時間はほぼ増えない**
- B-treeの高さは平均的に3~4程度であり、データ量が増えた際には高さではなく幅が大きくなる
    → データ量が増えても最上位から最下層までの到達時間（＝検索時間）は変わらない

**3. 検索/挿入/更新/削除の処理時間が同程度**
- インデックスの種類によっては検索が高速だが更新に時間がかかるものもあるが、B-treeインデックスは検索/挿入/更新/削除の処理時間が同程度。
- 上記の通り、データ量が増えた場合も処理時間はほぼ変化しない。

**4. 等号（=）以外の検索が可能**
- B-treeは構築時にキー値をソートする。
- 不等号を使用した検索や範囲検索の場合に、検索値をインデックスで一つまで絞り込むことができなくても、"ある特定のデータよりも右/左" のようにデータを絞りこむことは可能なので検索の高速化が可能になる。
- 否定条件（!=, <>）の場合は、インデックスで絞り込んだ結果が"特定のデータ以外の全てのデータ"となるため絞り込みが効かない。

**5. ソート処理を省略することができる**
- B-treeは構築時にキー値をソートする。B-treeインデックスが存在する列をORDER BY句のキーとして指定した場合、ソート処理をスキップすることができ、パフォーマンス向上に繋がる。
- SQLで明示的にORDER BY句以外にも、下記の処理をした場合は暗黙にDBMS内でソートが行われるが、これらも同様にB-treeインデックスが存在する列をソート対象とする場合はソート処理をスキップできる。
    - 集約関数（COUNT, SUM, COUNT, SUM, AVG, MIN, COUNT, SUM, AVG, MIN, MAX）
    - 集合演算（UNION, INTERSECT, INTERSECT, EXCEPT）


## 参考書籍
「達人に学ぶDB設計 徹底指南書 初級者で終わりたくないあなたへ」 ミック 著
slack bot x chatgpt x GAS　ver2

下記をちょとアプデ。

https://qiita.com/mazeltov7/items/d09152bdaa44b6417971

・質問によっては、chatgptのレスポンスが遅くて、slackが再度リクエストする、みたいなのがあるので、それを回避
・やり取りをスプシに入れて保存


```
function doPost(e) {
  //受信データをパース
  const json = JSON.parse(e.postData.getDataAsString());
  
  //Challenge認証用
  if (json.type === 'url_verification') {
    return ContentService.createTextOutput(json.challenge);
  }

  //イベント再送回避
  //キャッシュに積まれていれば処理済
  //未処理の場合はキャッシュに積む（有効期間5m）
  const event_id = json.event_id;
  const cache = CacheService.getScriptCache();
  const isProcessed = cache.get(event_id);
  if (isProcessed)　return;
  cache.put(event_id, true, 601);

  //サブタイプが設定されたイベント
  if('subtype' in json.event) return;

  //ChatGPTBotが送信した場合
  //ChatGPTで応答メッセージを作成し、Slackに送信する
  const botId = PropertiesService.getScriptProperties().getProperty('Slack_Bot_Id');
  if (json.event && json.event.user !== botId) {
    const channel = json.event.channel;
    const text = json.event.text;
    const message = requestChatGPT(text);
    sendSlack(channel, message);
    writeToSpreadsheet(json.event.user, text, botId, message); // ここでスプレッドシートに書き込みます
  }

  return;
}

//SlackBotsを通してメッセージを送信する
function sendSlack(channel, message) {
  const slackToken = PropertiesService.getScriptProperties().getProperty('Bot_User_OAuth_Token');
  const slackApp = SlackApp.create(slackToken);
  slackApp.postMessage(channel, message);
}

//ChatGPTにテキストを送信し、応答メッセージを取得する
function requestChatGPT(message) {
  const apiKey = PropertiesService.getScriptProperties().getProperty('OPENAI_API_KEY');
  const apiUrl = 'https://api.openai.com/v1/chat/completions';
  
  //リクエストデータの設定
  const headers = {
    'Authorization':'Bearer '+ apiKey,
    'Content-type': 'application/json'
  };
  const options = {
    'headers': headers, 
    'method': 'POST',
    'payload': JSON.stringify({
      "model": "gpt-3.5-turbo",
      "messages": [{ "role": "user", "content": message }]
    })
  };

  //リクエストを送信し、結果取得
  const response = JSON.parse(UrlFetchApp.fetch(apiUrl, options).getContentText());
  const resMessage = response.choices[0].message.content;
  return resMessage;
}

// SlackのユーザーIDからユーザー名を取得する
function getUserName(userId) {
  const slackToken = PropertiesService.getScriptProperties().getProperty('Bot_User_OAuth_Token');
  const slackApp = SlackApp.create(slackToken);

  try {
    // Call the users.info method using the WebClient
    const response = slackApp.usersInfo(userId);

    const userName = response.user.profile.display_name;
    return userName;
  }
  catch (error) {
    console.error(error);
  }
}

// スプレッドシートに書き込む
function writeToSpreadsheet(user, userMessage, botId, botMessage) {
  // スプレッドシートIDを指定
  const spreadsheetId = 'XXXXXXXXXXXXXXX';
  const spreadsheet = SpreadsheetApp.openById(spreadsheetId);

  // シートを選択（例えば、名前が "Sheet1" のシートを使用する場合）
  const sheetName = 'Sheet1';
  const sheet = spreadsheet.getSheetByName(sheetName);

  // ユーザー名とボット名を取得
  const userName = getUserName(user);
  const botName = getUserName(botId);

  // ユーザーのメッセージを書き込む
  sheet.appendRow([userName, userMessage, new Date()]);

  // ChatGPT Botのメッセージを書き込む
  sheet.appendRow([botName, botMessage, new Date()]);
}

```
ワイ、ボタンの概念を考え直す




# しっかりしたパート

### 結論
以下の2点が言いたいことです。
- **ボタンとして認識してもらう** のと **他の必要な情報を目立たせる** のトレードオフ
- 認知負荷を軽減するため**表示する数をなるべく少なくすべき**




# （駄作）物語パート
上記結論に気づいた背景を物語風にまとめました。（クソ文章注意）

### 主要登場キャラ
- **ワイ**
    - `variant=contained` 至上主義。大バカ者
- [MUI の Buttonコンポーネント](https://mui.com/material-ui/react-button/) 
    - **contained**
        - ワイが尊敬する王道ボタン
    - **text**
        - ワイに軽視される不遇な存在
    - **outlined**
        - 今回はモブA


-- --

### contained至上主義な日常

　「よし！完成や」

MUIを使って雑誌データの個人記録用サイトを作っていたワイ。完成したサイトのデザインを概観する。



![スクリーンショット 2023-04-01 15.45.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2847454/ae2b0fe6-6e8a-63ec-3db1-f32542c6f78b.png)


　「それにしてもやっぱり、`variant=contained`は押せる場所......ってのがわかりやすくて最高だな〜」

MUIの素朴ながらに洗練されたUI。中でも澄んだ水の色が放つ輝き。これでこそ "王道のボタン" ってものである。

　「それにしても、`variant=text`っていつ使うんや？ ボタンとして目立たないし、Linkと間違えるだろうし絶対使わんやろ」

.......そう、contained至上主義にとって他の人権、ならぬボタン権は存在しない。......まぁ、`outlined`はたまに使うけど。


### 瓦解する信頼感
実際にデータを記入するうち、使い勝手に違和感を覚えるワイ。

　『押せる場所が分かりやすくて使いやすいと思うんだが...』

操作はしやすいはず。......なのに、生じるフラストレーションにモヤモヤが積もる。

そんな日々が2週間続き、毎週末の『雑誌記載順』を総括して記録する日。......この日、やりどころのない不満の原因に気づいてしまった。

　「...りずらい。......わかりずらいんじゃ〜。
雑誌記載順が記録されているか分かりずらいにもほどがある！」

　上記写真の『雑誌記載順』の横に、**順番を表す数字** が表示されている（未記入は非表示）。
これが、**ボタン** と **ラベル文字** に挟まれて目立たない。......あまりにも目立たないのである。


### さらばcontained至上主義、ようこそ....

　”ボタンが目立つ”


この問題を解決しなければ使い勝手がどうしても悪い。...

 『...なさい。......私を使いなさい...』

　「...？ .........誰の声なんだ？」

 『目立たないボタンである`text`......この私を使ってごらんなさい』


　ワイの頭の中に、響く声。......という荒唐無稽な流れは残念ながら訪れない。
単純に目立たないボタンの`variant=text`の姿を想起したのだった。

　『......むっ、`text`か。 ボタンっぽさが無いからな...。だが、モノは試しや！』

早速、`variant=contained`のボタンを全て`variant=text`に変更した。

![スクリーンショット 2023-04-01 16.38.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2847454/1c40709f-2d54-832e-9c95-15f1a6e063b9.png)

　「......これは！」

　`variant=text`の落ち着きのある様。
彼・彼女はボタンであることを語りつつ、他の要素（番号）を引き立ててくれる器量。......最高か。


　『さらばcontained至上主義。ようこそ、text』

こうして、ワイの中のボタン界に革命が起きたのだった。


### 『テメェも用無しよ......』（ポイッ）

　`text`にしてから、番号が分かりやすくなった。
これは上々。だが、使い勝手が気になる日々は続く。

　『邪魔をする輩が他にも潜んでいるのだろうか？』

ごちゃついていた時代の表示を回顧し、邪魔をする要素を思案する......。

>上記写真の『~順』の横に、**順番を表す数字** が表示されている。これが、**ボタン** と **ラベル文字** に挟まれて目立たない。......あまりにも目立たないのである。

"順番を表す数字" に意味を与える存在、ラベル。**ラベルとボタンがサンドされているから分かりにくい**。

　『...ボタンと。......ラベルのサンド？』

つまり、"順番を表す数字" が他の要素に挟まらなければいいのでは？ つまり.........

　『テメェも用無しよ......』（ポイッ）

![スクリーンショット 2023-04-01 17.04.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2847454/ecf15cf5-0863-fc54-4c16-0fe070eb5bd3.png)


`variant=text`のボタンがラベル表示を担う形式に変更した。

　**ボタンであることを強調** する色合いで **ラベルの意味を強調する**。
　ラベルが強調されるからこそ、**隣り合う 順番を表す数字** も自然と目に留まる。...... まじ最高。


こうして、ただの文章だった"ラベル文字" は（勇者パーティから）解雇されたのだった......。


-- --

※ 上記の文章はUX・UI知識が浅い個人の見解です。
※また、レビューデータに深い意味はありません、悪しからず。
resampleの引数のconvention="end"はどこを指しているのか

## 今回の疑問
期間でインデックス付けされたデータをアップサンプリングする際、```resample(...convention="end")```の"end"が何を対象にしているかわからないので調べてみました。
```python
import pandas as pd
frame = pd.Series([1,2],index=pd.period_range("2022","2023",freq="A"))

#2022	1
#2023	2

frame.resample("Q",convention="start").asfreq()

#2022Q1	1.0
#2022Q2	NaN
#2022Q3	NaN
#2022Q4	NaN
#2023Q1	2.0
#2023Q2	NaN
#2023Q3	NaN
#2023Q4	NaN
#ここまでは理解可能。

frame.resample("Q",convention="end").asfreq()

#2022Q4	1.0
#2023Q1	NaN
#2023Q2	NaN
#2023Q3	NaN
#2023Q4	2.0
#???
#2022Q1~2022Q3まで消えた。
```
## 調べたこと
まず初めに公式を見なさいと偉い人が言ってました。
https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.resample.html
>convention{‘start’, ‘end’, ‘s’, ‘e’}, default ‘start’
For PeriodIndex only, controls whether to use the start or end of rule.

こちらによるとデフォルトは"start"で、PeriodIndexにしか使えず、ルール(第一引数のデータセット)の始まりか終わりを選べるそうです。

. . .？

これで理解ができれば苦労していないです。

手を動かして理解していきましょう。
別のデータを用意して傾向をつかんでいきます。
```python
qframe = pd.Series([1,2,3,4],index=pd.period_range("2023",periods=4,freq="Q",))

#2023Q1    1
#2023Q2    2
#2023Q3    3
#2023Q4    4

qframe.resample("M",convention="start").asfreq()

#2023-01    1.0
#2023-02    NaN
#2023-03    NaN
#2023-04    2.0
#2023-05    NaN
#2023-06    NaN
#2023-07    3.0
#2023-08    NaN
#2023-09    NaN
#2023-10    4.0
#2023-11    NaN
#2023-12    NaN

qframe.resample("M",convention="end").asfreq()

#2023-03    1.0
#2023-04    NaN
#2023-05    NaN
#2023-06    2.0
#2023-07    NaN
#2023-08    NaN
#2023-09    3.0
#2023-10    NaN
#2023-11    NaN
#2023-12    4.0
#2023-01と2023-02が消えた。
```

変化をまとめてみます。
```python

#1つめ
#2022 1 
convention="start"
2022Q1    1.0
2022Q2    NaN
2022Q3    NaN
2022Q4    NaN
convention="end"
2022Q4    1.0

#2つめ
#2023Q1    1
convention="start"
2023-01    1.0
2023-02    NaN
2023-03    NaN
convention="end"
2023-03    1.0
```
## まとめ

2つのシリーズの再サンプリングの共通点は1つめの期間のインデックスに対して作用が働いているようです。

・```convention="start"```の場合は1つ目の期間の最初。
・```convention="end"```の場合は1つ目の期間の最後。

を対象にしていることがわかりました。

おわり。

参考にさせていただいたもの
https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.resample.html
https://runebook.dev/ja/docs/pandas/reference/api/pandas.dataframe.resample
Autoware v1.14.0でのYOLOv3の有効化



## 下記ディレクトリに学習ファイルを置くだけ。

weights:
```
/home/autoware/Autoware/install/vision_darknet_detect/share/vision_darknet_detect/darknet/data/yolov3.weights
```
このフォルダ内に学習ファイルをDLする
```
wget https://pjreddie.com/media/files/yolov3.weights
```
【16億連の】Ethereumで秘密鍵ガチャ【自己満足】

## はじめに
「[EthereScan](https://etherscan.io)」とかで不思議なアドレスを見たことはありませんか？

たとえば「[OpenSea](https://opensea.io/ja)」の販売スマコンのアドレスは「[0x00000000000001ad428e4906ae43d8f9852d0dd6](https://etherscan.io/address/0x00000000000001ad428e4906ae43d8f9852d0dd6)」です。
頭から「0」の文字がずらりと13文字並んでいて、中の人たちの無駄なこだわりを感じることができます。

「物好きな開発者もいるもんだなぁ」

などと呆れつつ、こういった遊び心をちょっと羨ましくも思いました。
なので、Ethereumの秘密鍵をガチャガチャするプログラムを作って、かっこいいアドレスが出るまでひたすら回してみました。


## プログラム
下記のリポジトリへあげてあります。
[https://github.com/hakumai-iida/PrivateKeyGacha](https://github.com/hakumai-iida/PrivateKeyGacha)

Ethereumアドレスは秘密鍵から一意に決定されます。
秘密鍵はランダムで生成されますが「**いくらでも好きなだけ**」作成することができます。

このプログラムは「**指定された回数**」「**秘密鍵を生成**」して「**アドレスに指定の文字列があるか判定**」するとてもシンプルなものです。
環境は「[Visual Studio 2022](https://visualstudio.microsoft.com/ja/vs/)」、ライブラリとしては「[Nethereum](https://nethereum.com)」を使っています。

## 回してみた
動作確認として、先頭が「**BEEF**」、末尾が「**FACE**」となるアドレスを探すことにしました。
Ethereumのアドレスは40桁の16進数なので、１文字がマッチする確率は「**16分の1**」となります。

今回テストで探す文字数は８文字なので「**16の8乗分の1**」＝「**約42億分の1**」の確率でお目当てのアドレスを引ける計算になります。

また、プログラムの指定では「**大文字小文字の区別**」が可能です。
が、８文字の英数字の場合、42億分の1の確率に「**2の8乗＝256分の1**」が上乗せされて「**1兆分の1**」を下回る狭き門となるので、今回は大文字小文字を「**区別せず**」としました。

で、プログラムを走らせて放置すること１０日以上。
![ss5](https://user-images.githubusercontent.com/13220051/229270695-4b41e373-97e5-4ace-800f-4a0ce2988173.PNG)
**1,620,276,399連目**で当たりました！

１％のガチャに換算すると38連目あたりで引いたことになります。
これは大勝利の部類では！？

## 自己満足
早速、OpenSeaのプロフィールに設定してみました。
[https://opensea.io/ja/BEEF-FaCE](https://opensea.io/ja/BEEF-FaCE)

![スクリーンショット 2023-04-01 16.41.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/596438/f06387cc-293c-5358-fcbb-fdb0f871e97a.png)
OpenSeaは Ethereumアドレスの先頭４文字＋末尾４文字で短縮するのでなかなかイカした感じになりました！


## おわりに
通常は味気のない Ethereumアドレスですが、ちょっとした工夫で大きな自己満足を得ることができました。
マシンパワーと忍耐力に自信がある人は 10桁以上の文字数に挑戦してみるのはいかがでしょうか？