OmniAICreator/Qiita-1.07M huggingface dataset:
Rating（レビュー、評価） Udemyの場合


# はじめに
　[Udemyの各コンテンツの評価](https://support.udemy.com/hc/ja/articles/229234267-%E3%82%B3%E3%83%BC%E3%82%B9%E3%81%AE%E3%83%AC%E3%83%93%E3%83%A5%E3%83%BC%E6%8A%95%E7%A8%BF%E3%81%A8%E7%B7%A8%E9%9B%86)は5段階評価の★ですが、1.5、2.5というよりに間の値も含められる9段階評価になっています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/419691/08aa3c39-9ffc-fa03-bf48-bbe752bdbd29.png)

こちらの評価システムのいいのは、それぞれの★の上に「こんな感じの時だよ」大変良い、不満など言語化してくれていることです。

このひとことを、日本語、英語で抜き出してみました。

| 星 | 日本語 | 英語 |
|:-----------:|:----------:|:------------:|
| 5       | 大変良い、期待を上回る    | Amazing, above expectations!       |
| 4.5     | とても良い     | Good / Amazing       |
| 4       | 良い        | Good, what I expected       |
| 3.5         | どちらでもない         | Average / Good        |
| 3       | やや不満      | Average, could be better(あんまり良くない)     |
| 2.5    | 不満    | Poor / Average  |
| 2    | かなり不満    | Poor, pretty dissapointed     |
| 1.5    | 酷い、かなり不満   | Awful / Poor    |
| 1    | 酷い、著しく不満    | Awful, not woat I expected at all   |

## 迷ったら中央値を付けやすい?日本人

　一般論かつ私見であり、私自身もこの手の評価をあまり考えずにいた頃は、

「**迷ったら3だな**」

ぐらいに雑に付けていることがありました。でも **3=やや不満、could be better(あんまり良くない)** なんですよね。

なので、雑に3を付けちゃうのは良くないかなと思います。（本当に良くないことがあるならありですが）

## Aarageは2.5～3.5の3つの段階に含まれる

　Averageは3つの段階に含まれています（2.5、3、3.5）。

「3.5 どちらでもない、Average / Good」が、まぁまぁいいんじゃない的で、前項のざっくりうと真ん中です、という日本人の発想でしょうか。

 これらのいずれもAverageなので、2.5あれば及第点!?、いや平均点は持っているイメージともいえるでしょうか。

## 3以下には「不満」が含まれる

　不満の程度があるといはいえ、3以下には、日本語だと不満、英語だとcould be better(あんまり良くない)、Poor等があります。

明確に不満な要素があるときはこちらですね。でも不満ということがなければ3以下を付けるのは避けたいものです。

## 3.5という中間値がない場合は3 or 4のどちらを?

 あまり深く考えずに3を付けていたあなた、コンテンツを参照することでマイナスになっていないのであれば

**4がベターでは?**

ないでしょうか。3以下は不満な要素があり、マイナスになっているという意味合いがあると思います。

もちろんマイナス要素があるときはそれに従って付けるのもありですが。

# おわりに
　2021年10月に[Udemyにコンテンツをひとつ出して](http://ishiayaya.net/Udemy)、日々確認と[Power BIレポートにして視覚化](https://ishiayaya.net/UdemyBI)し、フィードバックとしてコメントをいただいたときには、オープンな場でも個別メッセージでもお返ししています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/419691/261c3b67-2bfb-aef7-0908-9eaaa4bbbd6d.png)


あるケースでは、酷評の評価があり、調べてもらって違反行為だと認定されました。また、違反行為とならなくても、これはなんらかの妨害行為ですか?といったこともあります。
あまりよくない評価をいただいていも、フィードバックをいただければ次回以降は改善しようと励みになりますね。
不満な評価があるものの、学習進度は進んでいない、どこがどう不満なのかわからない、そもそも雑に付けました？とつい言いたくなるw こともあります。このような背景から、自分の整理にも、日英並べてみて、本メモを作成しました。

　日本も含むグローバルイベントだと「日本勢、辛い評価だな」となっている、というような話も聴きます。あまり意識していなかったなという方は、少し考慮いただけますとありがたいです。
Canvasを画像化する

# はじめに
Canvasで作成したイメージは、そのままだとスマホで画像保存できません。
したがって、一度画像に変換して上げる必要があります。

以前記事で紹介したWebサービスをアップデートする際に実装しました。

https://qiita.com/soh_mitian/items/86c8eccf060a72607c29
# コード
基本的な流れはimgタグに対して、src属性としてBase64に変換したCanvasを食わせる形です。

## HTML

```html
<img id="canvas">
```

## Javascript
```javascript
document.querySelector('#selectPeopleImg').addEventListener('change', (event) => {
  const file = event.target.files[0]
  if (!file) return
  
  const reader = new FileReader()
  reader.onload = (event) => {
    imgData = event.target.result

    const img = new Image();
    img.onload = () => {
      const orgWidth  = img.width;  // 元の横幅を保存
      const orgHeight = img.height; // 元の高さを保存
      const newWidth = 300;  // 横幅を300pxにリサイズ
      const newHeight = orgHeight * (newWidth / orgWidth); // 高さを横幅の変化割合に合わせる
      //画像オブジェクトを生成
      const canvas = document.createElement('canvas');
      canvas.width = newWidth;
      canvas.height = newHeight;
      const ctx = canvas.getContext('2d');
      ctx.drawImage(img, 0, 0, newWidth, newHeight);
      document.querySelector('#canvas').src = canvas.toDataURL()
    }
    img.src = imgData;
  }
  reader.readAsDataURL(file)
})
```
ITエンジニアの肩書と価値提供の軸

| 達成すること| 肩書| 例えば|対象|
|:-----------|-------------|-------------|:------------:|
| 自分のスキルアップ   | XXエンジニア   | Rails使えます| 己  |
| 技術的ブレークスルー | XXエンジニア テックリード | Railsを作りました| モノ |
| チームのパフォーマンスアップ |  アーキテクト SE | Rails研修を社内で開きました  | 人 | 
| お客様の事業の成功 | PM SE | ○円儲けさせました | 顧客 | 
| 自社事業の成功 | CTO |  ○円稼ぎました| 市場 |
TikTokで連続再生するためのGreasemonkeyスクリプト

```tiktok.js
// ==UserScript==
// @name         tiktok
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  move to next video on videoend
// @author       Yugo Yamamoto
// @match        https://www.tiktok.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=symmetric.co.jp
// @grant        none
// ==/UserScript==

function movenext(){
    console.log("ended");
    let event = new Event("click",{bubbles: true});
    let button = document.querySelector("button[data-e2e=arrow-right]");
    if ( !button ) {return; }
    button.dispatchEvent(event);
}

(function() {
    'use strict';
    setInterval(function(){
        let videos = document.getElementsByTagName('video');
        if (videos.length == 0){
            return;
        }
        videos[0].addEventListener('ended',movenext);
    },1000);
})();
```

これで連続再生ができる。

videoタグでvideoを再生しているので、videoが終わったときのendedのイベントが取得できる。

実際に動かしてるのは、PC上のChrome + Tampermonkey。

本当はドキュメントがロードされた後にvideoタグにaddEventListenerをつければよかったんだけど、タイミングによってはvideoタグが存在しない時があったので、定期的にvideoタグを探しに行くスクリプトにした。
ubuntsでTFTPサーバをたてる

# 概要
仕事でTFTPサーバを使うことになったので調べてみた。
TFTPサーバとはUDPを使ったFTPのようなものらしい。（概要を斜め読みしただけ）
環境はUbuntu 18.04.6 LTS
TFTPサーバをたててputとgetをしてみる。

# TFTPサーバのたてかた
まずはインストール
```shell:
$ sudo apt-get install tftpd-hpa
```

環境ファイルの編集
tftpd-hpaはsudoしないと編集できない
TFTP_OPTIONSを編集する。
```shell:
$ sudo vi /etc/default/tftpd-hpa
TFTP_USERNAME="tftp"
TFTP_DIRECTORY="/var/lib/tftpboot"
TFTP_ADDRESS=":69"
#TFTP_OPTIONS="--secure"
TFTP_OPTIONS="-c -v -s"
```

ディレクトリの権限の変更
/var/lib/tftpbootの所有者をtftpに変更しないと、この後のputがうまく動かなかった
tftpユーザとは・・・？私にはわかりませんでした
```shell:
$ sudo chown tftp /var/lib/tftpboot
$ ls -l
drwxr-xr-x  2 tftp      nogroup   4096 Apr  3 00:19 tftpboot
```

サーバ再起動
```shell:
$ sudo service tftpd-hpa restart
```

これで準備完了

# TFTPクライアントからget/putしてみる
まずはクライアントをインストールする
```shell:
$ sudo apt install tftp
```

次にput/getしてみる
```shell:
$ tftp
tftp> connect localhost
tftp> put text.txt
Sent 116 bytes in 0.0 seconds
tftp> get text.txt
Received 116 bytes in 0.0 seconds
```

「Error code 0: Permission denied」がでる場合は「ディレクトリの権限の変更」を参照

以上
Android Studio Bumblebeeインストールできない?

## SDK なんとか?がないと表示される
インストールできない?

古いバージョンをインストールして上書きすれば解決すると調べたら出てきた!
私のMacはM1じゃないけど...

Android Studio 3.6.3をインストールする

https://blog.ch3cooh.jp/entry/2021/11/09/110215

BumblebeeをインストールしてMacのバージョンによっては置き換えますかと表示されるので、選択する。

後は、flutterで使うときと同じ環境構築をしていく

flutter-doctorするとエラーが出てくるので、一つづつ解決していく。
ライセンスのコマンドとかツールのインストールが必要。

![スクリーンショット 2022-04-09 7.49.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2303722/774881ae-f29f-2b86-adfa-2acaf4908c4a.png)

![スクリーンショット 2022-04-09 7.49.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2303722/2137c407-25c2-6639-9bcb-284b9d3a8eec.png)

![スクリーンショット 2022-04-09 7.49.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2303722/22c8696e-65d3-ab50-22a3-4bfb70023d69.png)

ApplyしてOKする。

こちらの記事も参考までに
https://qiita.com/y-okudera/items/d5b79be030c8a526ad80

flutter doctor --android-licenses

環境構築終わってサンプルアプリをflutterで使おうとするとbuildができない?
こちらのサイトを参考にした
https://errorsfixing.com/how-to-fix-error-processdebugmainmanifest-debug-in-flutter-while-run-from-android-studio/

ホームディレクトリーからcdコマンドを実行していく。
```
cd /Applications/Android\ Studio.app/Contents/jre

ln -s ../jre jdk

ln -s "/Library/Internet Plug-Ins/JavaAppletPlugin.plugin" jdk
```

エミレーターを使えるようにする。
https://flutternyumon.com/how-to-setup-android-emulator/
x86_64の方がいいらしい

buildできた!

![スクリーンショット 2022-04-09 7.54.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2303722/abc3aaa5-b6a2-dd37-f578-3166c7065151.png)
ユークリッドノルムで遊んだ

# ユークリッド距離の定義
ユークリッド空間上に定義された２つの座標$A,B$を以下のように定義します。
```math
\begin{align}
A&=
\begin{pmatrix}
a_1 & a_2 &\cdots &a_{n-1} & a_n
\end{pmatrix}\\
B&=
\begin{pmatrix}
b_1 & b_2 & \cdots &b_{n-1} & b_n
\end{pmatrix}
\end{align}
```

このように定義すると２つの座標間のユークリッド距離は次のように与えられます。

```math
\begin{align}
d=\sqrt{(a_1 - b_1)^2+(a_2 - b_2)^2 + \cdots +(a_{n-1} - b_{n-1})^2+(a_n - b_n)^2}= \sqrt{\sum_{i=1}^n (a_i - b_i)^2}
\end{align}
```

# C言語での実装
## 素直に作る
まず素直にC言語での実装すると次のようになります。

```c
#include<stdio.h>
#include<math.h>

double norm(double*, double*, int, int);

int main(int argc, char* argv[]) {


    double p1[] = { 0,0};
    double p2[] = { 0,10};

    double d;

    int arrayNumber1 = sizeof p1 / sizeof p1[0];
    int arrayNumber2 = sizeof p2 / sizeof p2[0];

    d = norm(p1, p2, arrayNumber1, arrayNumber2);

    printf("norm -> %f", d);
    return 0;
}

double norm(double* p1, double* p2, int arrayNumber1, int arrayNumber2) {
    if (arrayNumber1 == arrayNumber2) {
        int i;
        double num = 0;
        for (i = 0; i < arrayNumber1; i++) {
            num = num + pow((p1[i] - p2[i]), 2.0);
        }

        printf("%f", num);
        return sqrt(num);
    }
    else {
        return -1;
    }
}
```

norm関数内でfor文をくるくる回して計算しています。norm関数内部のif文はエラー処理で、$A,B$の配列の長さが異なる場合エラーを返します。

```c
if (arrayNumber1 == arrayNumber2)
```

## 再帰呼び出しで作る

次にこれを再帰呼び出しで作るとこのようになります。multiplication関数が何度も呼び出され結果がsumに加算されていきます。

```c
#include<stdio.h>
#include<math.h>

double norm(double*, double*, int, int);
void multiplication(double*, double*, int, double*);

int main(int argc, char* argv[]) {


    double p1[] = { 0,0 };
    double p2[] = { 1,1 };

    double d;

    int arrayNumber1 = sizeof p1 / sizeof p1[0];
    int arrayNumber2 = sizeof p2 / sizeof p2[0];

    d = norm(p1, p2, arrayNumber1, arrayNumber2);

    printf("norm -> %f", d);
    return 0;
}

double norm(double* p1, double* p2, int arrayNumber1, int arrayNumber2) {
    if (arrayNumber1 == arrayNumber2) {
        double num = 0;
        int n = arrayNumber1 - 1;
        multiplication(p1, p2, n, &num);
        printf("%f", num);
        return sqrt(num);
    }
    else {
        return -1;
    }
}
void multiplication(double* p1, double* p2, int n, double* num) {
    if (n == 0) {
        *num = *num + pow(p1[0] - p2[0], 2.0);
    }
    else {
        multiplication(p1, p2, n - 1, num);
        *num = *num + pow(p1[n] - p2[n], 2.0);
    }
}
```

# 参考
・[Wikipedia ユークリッド距離](https://ja.wikipedia.org/wiki/%E3%83%A6%E3%83%BC%E3%82%AF%E3%83%AA%E3%83%83%E3%83%89%E8%B7%9D%E9%9B%A2)

・[しろねこらぼ（ブログ）](https://sironekolab.com/?s=%E3%83%A6%E3%83%BC%E3%82%AF%E3%83%AA%E3%83%83%E3%83%89%E8%B7%9D%E9%9B%A2)
Paraya Server と IntelliJ IDEAで Webアプリケーションの開発環境を構築する

# Payara Serverをインストールする
Payara Serverを[ダウンロード](https://www.payara.fish/downloads/payara-platform-community-edition/, "ダウンロード")します。

ダウンロードしたファイルを解凍します。
```
unzip payara-5.2022.1.zip
```

解凍したフォルダを任意のフォルダに移動します。
```
mv ~/Downloads/payara5 ~/tools/
```

# Payara Platform Toolsをインストールする
IntelliJ IDEAを起動します。

プラグインをクリックします。
![スクリーンショット 2022-04-09 5.56.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14202/8948d714-dff6-485d-eec7-bfaad1f9247c.png)

検索バーに、「Payara」と入力します。
「Payara Platform Tools」が表示されます。
[インストール] をクリックします。
![スクリーンショット 2022-04-09 5.58.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14202/54a2a2a0-cd8c-adc5-cff9-d2f034af1a10.png)

# IntelliJ IDEAにPayara Serverを設定する
[IntelliJ IDEA] - [Preferences..]をクリックします。
![スクリーンショット 2022-04-09 6.03.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14202/4f154eae-ff37-0531-1e2f-87320c783775.png)

[ビルド、実行、デプロイ] - [アプリケーションサーバー]をクリックします。
![スクリーンショット 2022-04-09 6.04.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14202/8072a713-ed7b-2981-7b92-2e3783964e8d.png)

[+]をクリックし、[Payaraサーバー]を選択します。
![スクリーンショット 2022-04-09 6.05.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14202/6a6dd3ef-90ae-e69f-9e4d-fe1f53dc2edf.png)

Payara Serverを解凍したフォルダを指定し、[OK]をクリックします。
![スクリーンショット 2022-04-09 6.06.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14202/93b91e7b-3a77-3f64-f65d-1677a4944deb.png)

# Webアプリケーションを作る
[新規プロジェクト]をクリックします。

[Java Enterprise]を選択します。
プロジェクトテンプレートで、[Webアプリケーション]を選択します。
アプリケーションサーバーで、[Parayaサーバー]を選択します。
[次へ]をクリックします。
![スクリーンショット 2022-04-09 6.09.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14202/55f25731-a0f9-ef20-0f19-38e5aad0dcc8.png)

[Full Platform]をチェックし、[完了]をクリックします。
![スクリーンショット 2022-04-09 6.10.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14202/19e59acf-53ec-4f03-4168-5bbfb37b33a3.png)

# Webアプリケーションを実行する
^R(コマンドキー + Rキー)を押し、Webアプリケーションを実行します。
![スクリーンショット 2022-04-09 6.15.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14202/8edb6ce7-5fa2-d601-eadd-fd89cb4f3fc5.png)
Sales Cloud Superbadge



# Step 8

**We couldn't find the correct configuration for the 12 month Pipeline report. Make sure that you created the report according to the requirements, including the correct date filter.  How do I get the correct date filter.**

['Sales Cloud Superbadge Section 8 covering Reports - For the 12 month pipeline report, I am getting this error message: We couldn't find the correct configuration for the 12 month Pipeline report. Make sure that you created the report according to the req](https://trailhead.salesforce.com/ja/trailblazer-community/feed/0D54S00000G5JTj)
Android === Ubuntu w/ SSH [ Termux @ Github (@ F-droid ) ]

#### 概要
Androidにターミナル環境を実装できるアプリ[Termux](https://termux.com/)を使い、OpenSSHで接続
月額もかからずポータブルなサーバーとして使えます。

#### 手順

::: note warn
Andoid === Local
:::

TermuxのAPKをGithubからダウンロード　もしくは　F-Droid*からダウンロード
　　* オープンソースのAndroid用レポジトリ
ターミナル用にキーボードアプリダウンロード*　もしくは　Samsungならキーボードのショートカットを表示するとビルトイン使用可
　　* Hacker’s Keyboardなどがよく使われる
フォルダ設定、パッケージのアップデート
````
$ termux-setup-storage
$ pkg update
$ pkg upgrade
````
ユーザー設定します
````
& pkg install openssh
$ sshd
$ ip -4 a
$ id
$ passwd
````
 

::: note warn
Ubuntu === Local
:::
````
# 上記のコマンドの結果からuid=xxxxx(u0_axxx) と inet 192.168.1.x/24を確認
$ ssh -p 8022 u0_axxx@192.168.1.1 
$ cd ~/.ssh
$ ssh-keygen -t rsa -b 2048 -f FILE_NAME
````
秘密鍵と公開鍵ができたら以下で鍵をスマホにシェア
``scp ~/.ssh/名前.pub u0_axxx@192.168.1.1:~/``

````
# .ssh/configに下記を記述
Host ssh2android
    HostName 192.168.1.1
    User u0_axxx
    Port 8022
    IdentityFile ~/.ssh/名前
    ServerAliveInterval 60


# @PC PCからTermuxにSSHでログインしてから下記を叩く
$ cat ~/名前.pub >> ~/.ssh/authorized_keys
$ chmod 700 ~/.ssh
$ chmod 600 ~/.ssh/authorized_keys
````

スマホで設定したパスワードを変更せず毎回使っても問題無い場合は、``ssh -p 8022 u0_axxx@192.168.1.1``でも接続できます。
　
　
無事接続完了したら、HomeでUbuntuをインストールしてみましょう。

::: note warn
Android === Ubuntu
:::
````
pkg install wget openssl-tool proot -y
$ hash -r
$ wget https://raw.githubusercontent.com/EXALAB/AnLinux-Resources/master/Scripts/Installer/Ubuntu/ubuntu.sh
$ bash ubuntu.sh
$ ./start-ubuntu.sh
````


Termux起動時にLinuxを起動させるには、~/.bashrcに下記を記入
````
sshd
./start-ubuntu.sh
````

以上。
新人3か月くらいでやらかしたこと


# はじめに

自分が会社に入ったばかりの3か月くらい、
思い出せる範囲で、失敗したところ、分からなかったところをまとめました。
（今もできてない気が…( ◠‿◠ )）（ん？( ◠‿◠ )）


# 参考サイトを違うものを見ていた。

・公式を読まなかった。
・Qiitaばかり見た。
・OSが違うのを参考にした。
・バージョンが違うのを参考にした。

→基本公式を見る。
Qiitaは個人的なものなので、違うこともある。
バージョンで変わるので対応したものを見る。
既に行った先人がおられる場合は、聞いてみるのも良い。

# エラーの意味を読まなかった。

・読めば分かる英語を読まなかった。
・パニックになった。

→英語から逃げない。
エラーはヒントなので恐れ過ぎない。


# ログを読まなかった。

・ログの場所が分からなかった。
・ログの調べ方が分からなかった。
・ログの開き方が分からなかった。

→ログはヒントなので読む。
諸々必要な階層や、コマンドを覚える。


# 作業する前に作業の宣言をしなかった。

・「〇〇のドキュメント参考にして、今から〇〇します」、を言わなかった。

→参考サイトなどとも、繋がりますが、全然よくわからないけど、失敗しました！と、かなり先を行ってから、報告して周りが混乱&助けられない状態になった。
まるで森に入ったよう。
報告はマメにすると、状況がわかりやすい。（ようだ）


# ドキュメントの読み方が違った。

・ドキュメントを色々読み替えなかった。
・ドキュメントに書かれている作業は、全部いる作業だと思った。

→ ドキュメントのコピーが出来る状態とは限らない。
そのままとは限らない。
OSごとなど、それぞれコマンドが違う。
これは、知識なのでだんだん理解する。


# やり方が色々あると、どれが正しいか、困惑した。

・例えば、どれがaptで、どれがwgetをするのか迷い続けた。

→出来れば何でも。


# 権限周りにビビる。

→下記を理解する。
・何であるのか
・自分は誰なのか
・パーミッションとは
・ユーザーとは誰がいるのか


# エディタにパニックになる。

・viが起動して終われない。（viの、終わるコマンドが不明）
・知らないエディタが起動して終われない。

→慣れる。
必要なら、お好みを入れる。







# VMが何が何だか分からない……

・ホストとゲストが脳内で混在。
どこに何してんのか分からなくなる。
Apacheを、ホストとゲストで2つ作る。
・vagrantファイルが何故あるか分からない。

→目的場合によりけりで異なる。
落ち着いて、
何を何のためにするか理解しながら行う。
vagrantファイルは、何をしてるのか、理解していく。


# OSにより異なることを理解してなかった。

・Ubuntuなのに、yumを打つ。
・OSで階層やフォルダ名などが違うことを知らなかった。

→LinuxのOSは、色々ある。
コマンドが異なる。


# パスで詰まる

・パスが何か知らなかった。
・パスの通し方が分からなかった。

→パスとは何かを理解する。
インストールが上手くいってるにも関わらず、コマンドが、動かないなどがあったら疑う。

# 終わりに
今でも出来てる気はしませんが書きました👼

何か思い出したら追記しておきます。
argparseのノート

# narg

`nargs=*`で引数が無い時の挙動が位置引数とオプション引数で違う．

``` python test.py
parser = argparse.ArgumentParser()
parser.add_argument('infile', nargs='*')
parser.add_argument('-f', '--foo', nargs='*')
args = parser.parse_args()

print(args.infile)
print(args.foo)
```

引数なしで実行
``` zsh
python3 test.py

[]
None
```
位置引数は空のリストになるが，オプション引数はNoneになる．

`-f`だけつけて引数なしの場合
``` zsh
$ python3 test.py

[]
[]
```
この場合は空のリストになる．


# helpをプリントする方法
``` python test.py
parser = argparse.ArgumentParser()
parser.add_argument('infile', nargs='*')
parser.add_argument('-f', '--foo', nargs='*')
args = parser.parse_args()

parser.print_help()
```

``` zsh
$ python3 test.py
usage: test.py [-h] [-f [FOO ...]] [infile ...]

positional arguments:
  infile

optional arguments:
  -h, --help            show this help message and exit
  -f [FOO ...], --foo [FOO ...]
【Recoil入門】Selectorの使い方

今回はRecoilのselectorについて説明します。
Recoilの環境構築、atom、useRecoilStateについての説明はこちら↓。

https://qiita.com/itachi/items/69784d66dbe624ebaceb

## selector

`selector`は`atom`や他の`selector`からの派生stateです。
`atom`や他の`selector`の値を何らかの計算や加工をして返します。
また、`atom`や他の`selector`に何らかの計算や加工をした値を設定することもできます。

## selectorの作成
`selector`はアプリケーション全体で一意なキー、getプロパティ、setプロパティを設定します。

- `key`: アプリケーション全体で一意なキーを設定。他のatomやselectorと重複不可。

- `get`: atomや他のselectorの値を何らかの計算や加工して取得することができます。
    - get(): atomや他のselectorの値を返します。引数にatomや他のselectorを取ります。この関数に渡された**atom/selectorの値が更新された場合、コンポーネントは再レンダリングされます。**
    
- `set`: setプロパティはオプションです。このプロパティがある場合、selectorはatomや他のselectorを何らかの計算や加工をした値で更新することができます。第１引数にgetやsetなどの関数をとり、第2引数に新しい値を取ります。
    - get(): atomや他のselectorの値を返します。引数にatomや他のselectorを取ります。この関数は渡されたatom/selectorをsubscribeしないため、**atom/selectorの値が更新されても再レンダリングされません。**
    - set(): atomや他のselectorを何らかの計算や加工をした値で更新します。第1引数にatomや他のselector、第2引数に新しい値を取ります。
    - reset(): atomの値をdefaultにリセットします。引数にatomを取ります。

```jsx:src/selectors/mySelector
const mySelector = selector({
  // 一意のキー
  key: 'mySelector',
  get: ({get}) => get(myAtom)　* 2, //atomの値を取得して２倍して返す
  set: ({set}, newValue) => set(myAtom, newValue) // atomに新しい値を設定する
});
```

## selectorを使用する
以下selectorの使用例です。
```jsx
import React from 'react';
import ReactDOM from 'react-dom';
import {
  RecoilRoot,
  atom,
  selector,
  useRecoilState,
  useRecoilValue,
} from 'recoil';

const counterAtom = atom({
  key: 'counterAtom',
  default: 0,
})

const counterSelector = selector({
  key: 'counterSelector',
  get: ({get}) => get(counterAtom) ** 2,
  set: ({set}, newValue) => set(counterAtom, newValue)
});

const Counter = () => {
  const count = useRecoilValue(counterAtom)
  const [squareCount, setSquareCount] = useRecoilState(counterSelector)
  return(
    <>
      <p>count: {count}</p>
      <p>square: {squareCount}</p>
      <button onClick={() => setSquareCount(count + 1)}>Increment</button>
    </>
  )
}

const App = () => (
  <Counter/>
);

ReactDOM.render(
  <RecoilRoot>
    <App/>
  </RecoilRoot>,
  document.getElementById('root')
);


```
cd -

**忘らるる身をば思はずちかひてし人の命の惜しくもあるかな**


---

Advent Calendar 2022 89日目[^1]の記事です。
I'm looking forward to 12/25,2022 :santa::santa_tone1::santa_tone2::santa_tone3::santa_tone4::santa_tone5:
私の[Advent Calendar 2022 一覧](https://docs.google.com/spreadsheets/d/1HQvFjagQLRPjOYAjDVzWp9S4b8dKixxvvaz_TtbZWto/edit#gid=1723448955)。

[^1]: @kaizen_nagoya さんの「[「@e99h2121 アドベントカレンダーではありますまいか Advent Calendar 2020」の改訂版ではありますまいか Advent Calendar 2022 １日目　Most Breakthrough Generator](https://qiita.com/kaizen_nagoya/items/49ebebee3a0377f3b59b)」から着想を得て、模倣いたしました。 

---



# はじめに

[Elixir](https://elixir-lang.org/)を楽しんでいますか:bangbang::bangbang::bangbang:

今日は、[Elixir](https://elixir-lang.org/)とは関係ありません。

コマンドで、`cd -`とすると移動する前にいたディレクトリに戻れます。
以上です。
お恥ずかしながら、さきほど知りました。

# 知った背景

[Elixir](https://elixir-lang.org/)の記事を書こうと公式ドキュメントを眺めていました。
次の流れです。

[Other tools and community](https://elixir-lang.org/getting-started/debugging.html#other-tools-and-community)

↓↓↓

[Visualixir](https://github.com/koudelka/visualixir)

３年前くらいの少し古いプロジェクトです。
Phonix 1.3とバージョンが古いです。
公式のドキュメントで紹介されているくらいのプロジェクトですからおもしろそうです。
まだ動かしてはいません。
READMEを読んでいると、こんなコマンド例がありました。

```bash
mix deps.get && mix compile && cd assets && npm install && cd -
```

プロジェクトのルートで、`mix deps.get`して、`mix compile`して、`assets`フォルダへ移動してから`npm install`してプロジェクトのルートに戻っています。

ハイフンの意味は、以下のドキュメントから転載します。

https://pubs.opengroup.org/onlinepubs/9699919799/utilities/cd.html

> When a <hyphen-minus> is used as the operand, this shall be equivalent to the command:
> cd "$OLDPWD" && pwd

`"$OLDPWD"` -- この環境変数もはじめて知りました。


# おもったこと

私がはじめて知ったコマンドは、`cd`と`mkdir`です。
だいたい20年くらい前のことです。
アニメのヒーローが必殺技を繰り出すように、当時はよく宣言しながら使っていました。

ホームディレクトリ配下に雑然と課題の`.c`ファイルが並んでいたものを、大学４年生になって研究室の先輩に教えてもらって整理をしたときには感動しました。
ちょっとしたコンピュータの凄腕名人にでもなったかのような気がして、誇らしくおもいました。

いまでもよく`cd`コマンドは使っています。
さすがにいまは必殺技然として使用しなくなりました。

`cd -`を知らなくても元に戻る指定の仕方は他にもあるのでそれで困ることはありません。
知らないなら知らないでもたいして困ることはないとおもいます。
コマンドリファレンスでも引っ張り出してきて読み返す必要はあるかもしれませんし、そこまでする必要はないかもしれません。

この体験からおもったことを開陳します。
わかったつもりになっていることでもまだまだ知らないことがたくさんあるのだろうなあとしみじみおもいました。
**ひとりかも寝む** です。

その感傷は、感傷として感じたことを記しておきました。

こんなところで止まっているわけにはいきません。

**東洋の道徳と西洋の芸術（技術）と、この両方についてあますところなく詳しく研究し、これを民衆の生活に役立て国恩に報じる、これが第五の楽しみである（佐久間象山）**

**堯舜孔子の道を明らかにし、西洋器械の術を尽くさば、（略）大義を四海に布かんのみ（横井小楠）**


---

# Wrapping up :lgtm::lgtm::lgtm::lgtm::lgtm:

Enjoy [Elixir](https://elixir-lang.org/):bangbang::bangbang::bangbang:
<font color="purple">$\huge{Enjoy\ Elixir🚀}$</font>

`cd -`について書きました。



以上です。
web3.jsでコントラクト叩くスクリプト例

web3.jsでMetamask等ブラウザを利用せずにnodejsだけでコントラクトを叩くスクリプト例
ehters.js版も記載したがweb3.jsの方が複雑でわかりにくいイメージ

https://qiita.com/taaaaho/items/c5fa4e379b206fe8457c

```javascript
const Web3 = require('web3')

// ABIの読み込み
const fs = require('fs')
const metadata = JSON.parse(fs.readFileSync('./xxxxx.json', 'utf8'))

const web3 = new Web3('Infura指定')

// アカウント指定
const privateKey = 'xxxxxxxxxx'
const account = web3.eth.accounts.privateKeyToAccount(privateKey)

// コントラクト生成
const contractAddress = "xxxxxxxxxx"
const contract = new web3.eth.Contract(metadata.abi, contractAddress)

// トランザクションのデータ生成
const tokenURI = "xxxxxxxxxx"
const encodeABI = contract.methods.mint(tokenURI).encodeABI()

const func = async () => {
    const tx = { 
        to: contractAddress,
        data: encodeABI,
        gas: 1500000
    }
    const signedTx = await account.signTransaction(tx)
    const res = await web3.eth.sendSignedTransaction(signedTx.rawTransaction)
    console.log(res) 
}  

func()
```
PHPでの文字列追記は結合(.)より結合代入(.=)が圧倒的に早い

あまりにマニアックな情報

2022/04/10 ソース見たので表現修正(内容は同じ)

### まとめ
* 文字列追記は結合演算子(.)より結合代入演算子(.=)の方が圧倒的に早い
  * つまり ```$a = $a . $b``` より ```$a .= $b``` が早い
* 文字列が大きいほど、結合回数が多いほど顕著に差が出る
  * Webアプリならいずれも小さいので、ほとんどのケースでは気にしなくて良い
  * エクスポートなど大量のデータを吐き出す処理では顕著に影響する(簡単に何百倍、分単位で差が出る)
* PHPはちゃんと最適化されてる

### 結合演算子(.)より結合代入演算子(.=)の違い
ほとんどの場合は結合演算子(```$a = $a . $b```)と結合代入演算子(```$a .= $b```)は同じで、単にショートハンド位の認識をされていると思う。[マニュアル](https://www.php.net/manual/ja/language.operators.string.php)でも特に違いには触れていない。
というか自分がそう思っていたが、想定以上に重いコードがあり、その調査で内部的には大きく違うことに気づいた。

例としてメール等のデータエクスポートを想定し、合計10MBの文字列をN回に分けて結合した際の時間を以下に示す(100, 1000, 10000回; foreach自体の実行は控除済み)。余りに差が出るので対数スケールでも示す(右側)。
結合演算子(.)ではたった10MB, 1万ループで40秒もかかるが、結合代入演算子(.=)では30msと高速。また回数の増加による処理時間増加も結合演算子は大きい。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/508490/7fa6c940-6311-5995-e5f9-9ecbf6e52f2c.png)


### 原因

コードを読んだわけではないので以下は推測。
 2022/04/10 ソース見たので表現修正

結合演算子(```$a = $a . $b```)での処理は、書いてあるまま、`$a`と`$b`を結合した別のデータを作り、それを改めて`$a`としている。つまり、メモリの新規確保(拡張ではなく)と全メモリコピーが都度発生している。そのため容量、回数で処理時間も大きく増加する。
また文字列結合が元変数に追記の形か否かは見ておらず、```$a = $a . $b```でも```$a = $b . $c```でも同じ処理。

一方結合代入演算子(```$a .= $b```)では、元変数への追記として最適化されており、元変数(内部のzend_string)のメモリサイズを拡張、追加データだけを末尾にコピーという処理になっている。

考えてみれば当たり前の処理ではあるものの、PHPはちゃんと最適化してるんだなと。

ちなみに文字列結合としてimplode()もあるが、最初に配列をなめて結合後のサイズを確定、一気にメモリ確保・データコピーを行っていた。
配列で扱うオーバーヘッドもあるものの、[ベンチしてくださった結果](https://qiita.com/hoogee/items/2a3b8d49b67c99de7df9#comment-16d0f24a7caef427e95f)からメモリ使用ちょっと増えるのが許せば柔軟性高くなり、これも良さそう。


### 検証コード
以下コードでPHP 7.4で確認。
```
<?php
declare(strict_types=1);

//第一引数に文字列の最終サイズ(バイト)、第二引数に結合回数を指定
$tota_size = (int)$argv[1];
$loop_count = (int)$argv[2];
$chunk_size = (int)($tota_size / $loop_count);

//結合ごとに追記する文字列
$chunk = str_repeat('0', $chunk_size);

//予めループだけで食う時間を計測しておく
//コンパイラ最適化等はないので、空ループでも特に消されることはない
$start = microtime(true);
for ($i = 0; $i < $loop_count; $i++) {
    
}
$tare = (microtime(true) - $start);

//文字列追記の時間計測
$start = microtime(true);
$memory_init = memory_get_usage();
$str = '';
for ($i = 0; $i < $loop_count; $i++) {
    $str .= $chunk; //← ここの処理差し替えで時間比較
    //$str = $str . $chunk;
}
$took = (microtime(true) - $start) - $tare;
$memory_used = memory_get_usage() - $memory_init;

echo $took * 1e3; //ms単位
```
基本情報技術者試験対策　27日目

基本情報技術者試験勉強　27日目
-
この記事の目的
-
今年の5月に基本情報技術者試験を合格するために、過去問で間違えた箇所をただまとめる自分のための記事

プロフィール
-
・文系大学の３年生
・ITパスポートは保有
・ウェブアプリやサイト作成を個人で行ったことがある程度
・猫好き

今日の目標
-
今日も引き続き、午前問題を最低30問は解く。

間違った箇所
-
1、PTS法
観測対象の作業内容を基本動作にまで分割・分析し、作業条件ごとに設定した基本動作の標準時間から作業時間を割り出す。

2、ワークサンプリング法
作業内容のどの状態にあったかという瞬間観測を何回か行い、観測記録の回数の割合から、各作業時間がどのくらいであるかを推定する。

3、XBRL（eXtensible Business Reporting Language）
各種財務報告用の情報を電子化して作成、流通、利用できるように標準化されたXMLベースの言語

4、IDC(Internet Data Center)
インターネットとの接続を前提として設計されているデータセンタ

5、指標アドレス指定
アドレス部の値にインデックスレジスタの値を加えたもの

6、基底アドレス指定
アドレス部の値にベースレジスタの値を加えたもの

7、相対アドレス指定
アドレス部の値にプログラムカウンタの値を加えたもの

8、レイトレーシング
光源からの反射や屈折，透過を計算し描画

9、シェーディング
光源と物体の形状などに基づいて、表示する時に陰影をつける


>引用
基本情報技術者試験ドットコム
https://www.fe-siken.com
rex0220 プロセスカラープラグイン

プロセス管理のアクションボタンの色を指定できるプラグインです。
アクションによって、ボタンの色を変えることにより、操作がわかりやすくなります。

主な機能
・プロセス管理のアクションボタンの色指定
・３ヶ国語対応
・PC・モバイル対応

[rex0220 stores](https://rex0220.stores.jp/)で、販売中です。

Ver.5
・プラグイン設定画面のデザイン変更対応
・プラグイン設定に、「説明」設定機能を追加

# 概要

プロセス管理のアクションボタンの色指定例です。

![2022-04-08_23h07_53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100572/475e2fc0-9730-d29f-d37b-6aa4d31da967.png)

モバイル表示例
![2022-04-08_23h08_37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100572/b54b65c6-2f73-0327-1afe-a36226d03791.png)


# プロセス管理の操作例

![2022-04-08_23h20_48.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100572/055e3475-a6f4-7fb3-3463-ac8c66043ff0.gif)

# プラグインの設定例

- タイプ：　action, reassign
- アクション名
- ステータス(From)
- ステータス(To)
- テーマ：　カラーパレットで選択すると、文字色と背景色がまとめて設定されます。
- 文字色：　カラーパレットで選択すると、文字色が設定されます。
- 背景色：　カラーパレットで選択すると、背景色が設定されます。

![2022-04-08_23h13_23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100572/faa2cc59-5e5b-0210-4c82-84e3ad4491aa.png)

テーマの選択
![2022-04-08_23h16_45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100572/765be827-99ed-91b1-3597-71138bd582c9.png)

文字色、背景色の選択
![2022-04-08_23h18_40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100572/7c47bfd0-afcf-2dc6-62b0-3330bc517f18.png)

# 注意事項

- プロセス管理の設定を変更した場合、プロセスカラープラグインを再設定してください。
- 既に、JavaScript によるカスタマイズを行っているアプリの場合、処理が競合して、エラーになる場合があります。その場合は、該当するアプリでプラグインの使用を中止してください。
9軸IMUモジュールWT901CをROS 2で使用する

こちらの記事をみてWT901Cを購入したのですが、ROS1があまり得意ではないのでROS2に移植しました！

https://qiita.com/porizou1/items/0e9c956c89c584f6c072

## ROS2での動かし方

### パッケージのインストール

今回作成したパッケージはこちら。
メッセージ、ドライバに関する部分はほぼそのまま移植し、ノードをROS2で動作するように書き換えています。

https://github.com/fateshelled/wit_node

ros-foxy-ecl*をインストールします。

```bash
sudo apt install ros-foxy-ecl-devices \
                 ros-foxy-ecl-sigslots \
                 ros-foxy-ecl-time \
                 ros-foxy-ecl-build \
                 ros-foxy-ecl-geometry
```

リポジトリをクローンして、ビルドします。
```bash
source /opt/ros/foxy/setup.bash
mkdir -p ~/ros2_ws/src
cd ~/ros2_ws/src
git clone -b ros2 https://github.com/fateshelled/wit_node
cd ~/ros2_ws
colcon build --symlink-install
```

### WT901Cを接続
PCに接続して実行権限を付与します。
```bash
sudo chmod 666 /dev/ttyUSB0
```

### 起動
公式のROS1パッケージは可視化が面倒くさかったので、PoseStampedをPublishするように機能追加しています。
ROS2ノードのデフォルトボーレートは115200bpsです。WT901C側のボーレートと合致していないとデータが取得できないので気をつけましょう。この設定が一番のハマりどころかもしれません。

[WT901C側のボーレート変更方法](https://qiita.com/porizou1/items/0e9c956c89c584f6c072#uart%E3%81%AE%E9%80%9A%E4%BF%A1%E3%83%9C%E3%83%BC%E3%83%AC%E3%83%BC%E3%83%88%E3%81%AE%E5%A4%89%E6%9B%B4)

```bash
source install/setup.bash
ros2 launch wit_node wit_visualize.launch.py

# ROS2ノードのボーレートを変更する場合
ros2 launch wit_node wit_visualize.launch.py　baud_rate:=9600
```


## おわりに
あくまで個人が作成した非公式なパッケージなのであしからず。
楽しいROS2ライフを！！
【小ネタ】Qiitaに投稿した自分の記事一覧を取得する

# 背景・目的
自分が投稿した記事情報を一覧として取得したい場面が時折あるので、備忘録として残します。

# 実践
## コマンド
```
curl -H GET 'https://qiita.com/api/v2/authenticated_user/items?per_page=100' -H "Authorization: Bearer $TOKEN" | jq  -r '.[]|[.title,.created_at,.url] | @csv'
```
- TOKENには、各自でアクセストークンをセットしてください。
- ここでは、jqでタイトルと作成日、URLのみ取得してます。
- per_pageで100件取得しています。

# 参考
https://qiita.com/api/v2/docs#%E6%8A%95%E7%A8%BF

https://qiita.com/h_tyokinuhata/items/ab8e0337085997be04b1
IISのログのとりまとめ

# はじめに
IISのログってどこを見れば良いの？をメモしておく

## IISのログの種類
調べた限り、IISには「アクセスログ」「エラーログ」がある

### アクセスログ
C:\inetpub\logs\LogFiles\W3SVC1\u

にある


### エラーログ
C:\Windows\System32\LogFiles\HTTPERR

にある

中身を見ても、正直何が書いてあるかわからない。フォーマットを下記を参照
https://docs.microsoft.com/ja-jp/troubleshoot/developer/webapps/aspnet/site-behavior-performance/error-logging-http-apis
【Docker】Next.jsの環境構築

## はじめに
　本記事は、プログラミング初学者が学習を進めていて疑問に思った点について調べた結果を備忘録も兼ねてまとめたものです。
　そのため、記事の内容に誤りが含まれている可能性があります。ご容赦ください。
　間違いを見つけた方は、お手数ですが、ご指摘いただけますと幸いです。

## Next.jsの環境構築
### Dockerfileを作成する

```Dockerfile:Dockerfile
FROM node:16.14.2

ENV APP_NAME=myapp
ENV USER_NAME=myuser
ENV TZ=Asia/Tokyo

WORKDIR /${APP_NAME}

RUN adduser ${USER_NAME} && \
  chown -R ${USER_NAME} /${APP_NAME}
USER ${USER_NAME}

EXPOSE 3000

CMD ["npm", "run", "dev"]
```

### docker-compose.ymlを作成する
```docker-compose.yml
version: '3.9'
services:
  front:
    build: .
    volumes:
      - .:/myapp
    environment:
      NODE_ENV: development
    ports:
      - 3000:3000
```

### `npx create-next-app`を実行する
```:ターミナル
$ docker compose run --rm front npx -y create-next-app tmp --ts && mv tmp/* tmp/.* . && rm -r tmp
``` 
カレントディレクトリに展開するために`mv tmp/* tmp/.* . && rm -r tmp`を追加で実行しています。
また、`-y`オプションを使用すると`create-next-app`のインストール確認メッセージが出力されませんので、「`create-next-app`」の入力ミスにご注意ください。
`-y`オプションについては以下をご参照ください。

https://qiita.com/P-man_Brown/items/6210e7c6a28a36e516d0

### `docker compose up`を実行する
```:ターミナル
docker compose up
```
http://localhost:3000 にアクセスし、以下の画面が表示されることを確認してください。

![スクリーンショット 2022-04-08 23.11.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2342443/1b09dd37-e407-c3ad-e2f7-bfdb619db06a.png)
【LINE Messaging API, GAS】友だち追加時にスプレッドシートにユーザIDを記録

## フォローイベントとは
> LINE公式アカウントが友だち追加またはブロック解除されたことを示すイベントです。フォローイベントには応答できます。（[Messaging APIリファレンス](https://developers.line.biz/ja/reference/messaging-api/#follow-event)より）


## フォローイベントを取得
```js:コード.gs
function doPost(e){
  const events = JSON.parse(e.postData.contents).events;
  for (var i = 0; i < events.length; i++){
    execute(events[i]);
  }
}

function execute(event){
  const eventType = event.type;
  const replyToken = event.replyToken;
  const userId = event.source.userId;

  if(eventType === "follow"){
    // スプレッドシートにユーザIDを書き込む
  }
}
```


<details><summary>【参考】フォローイベントのJSONコード例</summary><div>

[Messaging APIリファレンス #フォローイベント](https://developers.line.biz/ja/reference/messaging-api/#follow-event)より

```json:JSON
{
  "destination": "xxxxxxxxxx",
  "events": [
    {
      "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA",
      "type": "follow",
      "mode": "active",
      "timestamp": 1462629479859,
      "source": {
        "type": "user",
        "userId": "U4af4980629..."
      }
    }
  ]
}
```
</div></details>
<details><summary>【参考】メッセージイベントのJSONコード例</summary><div>

[Messaging APIリファレンス #メッセージイベント](https://developers.line.biz/ja/reference/messaging-api/#message-event)より

```json:JSON
{
  "destination": "xxxxxxxxxx",
  "events": [
    {
      "replyToken": "nHuyWiB7yP5Zw52FIkcQobQuGDXCTA",
      "type": "message",
      "mode": "active",
      "timestamp": 1462629479859,
      "source": {
        "type": "user",
        "userId": "U4af4980629..."
      },
      "message": {
        "id": "325708",
        "type": "text",
        "text": "@example Hello, world! (love)",
        "emojis": [
          {
            "index": 14,
            "length": 6,
            "productId": "5ac1bfd5040ab15980c9b435",
            "emojiId": "001"
          }
        ],
        "mention": {
          "mentionees": [
            {
              "index": 0,
              "length": 8,
              "userId": "U850014438e..."
            }
          ]
        }
      }
    }
  ]
}
```
</div></details>


## スプレッドシートの操作

```javascript:gs
const sheetId = "シートID";

var data = SpreadsheetApp.openById(sheetId).getSheets[0];  // シートを取得
var lastRow = data.getLastRow();  // 最終行を取得
data.getRange(lastRow+1,1).setValue(userId);  // A列目にユーザID記入
data.getDataRange().removeDuplicates([1]);  // ユーザIDの重複を削除
```

+ **シートID**
  スプレッドシートのリンクから取得
`https://docs.google.com/spreadsheets/d/[sheetId]/edit`
+ **getRange(row, column)**
  行と列を指定してセル範囲を取得（[引用元](https://caymezon.com/gas-remove-duplicates/)）
+ **getDataRange()**
  そのシート上の値が存在するセル範囲を取得（[引用元](https://gsuiteguide.jp/sheets/getdatarange/)）
+ **removeDuplicates(columnsToCompare)**
  指定された列の値のうち、前の行と重複する値を含む、この範囲内の行を削除（[引用元](https://caymezon.com/gas-remove-duplicates/)）


:::note warn
`getRange(row, column)` において（1行, A列）の指定は `getRange(1, 1)` とする。
:::

## 参考
https://qiita.com/s_home/items/50bf4834189cf7ad1318
どのディレクトリに居ても正しい gradle wrapper を起動する

めんどくさがって path にある適当に新しい gradle を使うとビルドできないレガシープロジェクトとか生まれがち。

これでいける。

```
gradle () {
	GITDIR=`git rev-parse --show-cdup 2> /dev/null`
	if [ -e "$GITDIR./gradlew" ]
	then
		echo Using $GITDIR./gradlew
		$GITDIR./gradlew "$@"
	else
		echo Using $(/usr/bin/which gradle)
		$(/usr/bin/which gradle) "$@"
	fi
}
```
【トリビアのDelta Lake】#5 Pysparkで破産しないためにGoogle Colabにて無料で使う

# Pysparkで破産？
Databricksでは、Clusterという有償のコンピューティングリソースを用いてデータソースにアクセスしたり、プログラムを動かしたりします。

Clusterは**従量課金制**であり、その単位はDBUというもので表されます。
契約形態やClusterの種類にもよりますが、およそ1DBUは0.15~0.65ドル。ドルです。

Clusterはデータ量の大きさに応じれるようさまざまなマシンのサイズが用意されていますが、一番よく使うサイズであるi3 Xlargeインスタンスの3ノード（90GBくらい）で、だいたい1時間3DBU、つまり2ドルくらいです。

https://databricks.com/jp/product/aws-pricing

そしてClusterの裏側は自前で契約しているクラウドのコンピューティングリソース（AWSならEC2）なので、その料金もかかってきます。
だいたいDatabricks Cluster料金の等倍か、それ以下くらいだそうな。

なのでDatabricksでは、Sparkのメリットを受けられるほど大きくないデータを扱うだとか、慣れてないPysparkのコマンドを壁にぶち当たりつつ検証する、という使い方だと少しもったいないことになるというのは想像に難くないと思います。
**皿洗いで、蛇口を開けたまま、なかなか取れない油汚れに苦戦するのと同じ感じで。**

そこで、特に後者の「慣れてないPysparkのコマンドを壁にぶち当たりつつ検証する」で財布を痛めるのを回避するために、Pysparkを無料で使う方法をご紹介します。

# Google Colabを使う
いろいろ試しましたが、「**とにかく手軽にPysparkを試してみる**」というニーズであれば、無料のPython環境である**Google Colab**を使うのが最も手軽なのではないかと思います。
僕は最初ブラウザでのPython環境でJupyterNoteBookを使いましたが、Google Colabもなかなか使いやすいですよね！

検証にはGMO様のサイトを参考にしました。

https://techblog.gmo-ap.jp/2021/06/07/colab_pyspark/

結論、モジュールを入れるときにも使う!pip をするだけで出来ちゃいます。

![スクリーンショット 2022-04-08 22.31.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1781449/478bc588-6daa-5834-9bb2-68b1306a73b7.png)
```
!pip install pyspark

from pyspark.sql import SparkSession 
spark = SparkSession.builder \
    .master("local[1]") \
    .appName("IAB") \
    .getOrCreate()
```
これでsparkが定義されたので、あとはGoogle ColabにサンプルCSVを取り込むなり、スクリプト上でテストデータを作成して.createDataFrame()するなりでSpark DataFrameを作成、検証したいコードを実行することが出来ます。

今回はこうしたプチ節約術を紹介しましたが、機械学習での大量の教師データをクレンジング・学習するぜ！となったときは、さすがにシングルノードでは厳しいものがあると思いますので、そういうときに有償・高性能なDatabricks Clusterが活きるはず。

適材適所で、いいPysparkライフを送りたいですね！
基本情報技術者平成31年春期　午前問2について

# はじめに
基本情報の勉強をしていて、分からない問題がありましたので、整理しました。


# 公式はこちら

https://www.jitec.ipa.go.jp/1_04hanni_sukiru/_index_mondai.html

https://www.jitec.ipa.go.jp/1_04hanni_sukiru/mondai_kaitou_2019h31.html#01aki


# 参考さま

https://www.fe-siken.com/kakomon/31_haru/q2.html

# 問題について

基本情報技術者平成31年春期　午前問2
最上位をパリティビットとする8ビット符号において、パリティビット以外の下位7ビットを得るためのビット演算はどれか。

16進数0FとのANDをとる。
16進数0FとのORをとる。
16進数7FとのANDをとる。
16進数FFとのXOR(排他的論理和)をとる。




# 問題の意味について

まず、問題の意味から確認していきましょう。


まず問題が何言っているのかさっぱり・・・

![kangaeruhito.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/148394/10e1387c-93f2-e5f5-1f0d-7e0a5487c222.png)

よく分からんかった用語達を調べました。


### パリティビット
・符号のこと。
・ビットの列に含まれます。
・1の数が、偶数か奇数か表します。


### パリティチェック
・パリティビットを使って、
データを送ったり、記録したりする時に起きる間違いを検出する方法のこと。


### 最上位ビット
・MSBともいう。
・ビットの位置の1つです。
・コンピュータで２進数で最も大きな値のこと。
・一番左にあります。


### AND OR XORなど
こちらは、論理演算の話ですね。


### 8ビット
全部で8ビットあるということですね。


### 下位7ビット
最上位、つまり、最初のビットより後ろにある、7つのビットという意味になります。


## つまり
全部で、8ビットあります。
一番左にある最初のビットを、パリティビットというやり方で、チェックをします。
パリティビットしない7つのビットを知りたいです。
そのために、論理演算を使いましょう。


# 続く
ちょっと長くなりましたので、続きます。
CompTIA Security+ 勉強方法記録

CompTIA Security+に合格したので、勉強方法を記録として残します。

■事前に持っていた関連資格・経験
・CCNA
・FWなど実機での設定変更
・セキュリティ機器のログ分析

■行った勉強
・公式テキスト読み込み（2日間）
・Udemyにて問題集を2周（3日間）

公式テキストを見る限り広く浅くセキュリティ知識を身に着ければ受かるかなと思い、
１週間ほど試験勉強に使って受験をしました。

基本的な攻撃手法などは勉強開始する前に理解できていましたが、
無線関連のセキュリティや暗号などは暗記項目が多く苦戦しました。

実際の試験ではUdemyと全く同じ問題が出なく、
問題演習時よりも手が止まることが多かったので、
セキュリティの考え方自体を身に着けるべきだと思いました。
（問題集は９割以上は取れていましたが、実試験ではぎりぎり合格でした。反省ですね。）

次はCEHを受験予定なので頑張ります。
はじめてのOpenAPI

# はじめに
なるべく簡潔にOpenAPIの各所の意味を備忘録として残す。
「Swaggerって何？」とか細かいことはここでは割愛するから許してね。

## 記述例
以下のように書いて[Swagger Editor](https://editor.swagger.io/)で見てみると分かりやすいです。

```yaml
openapi: 3.0.0
info: # APIのメタデータ
  title: Sample API # APIドキュメントのタイトル
  description: 'サンプル' # ドキュメントの説明
  termsOfService: # APIを提供するサーバー
    - url: 'http://localhost:3000'
      description: "ローカル環境"
    - url: 'https://stg-api.sample.com'
      description: "ステージング環境"
  contact: # サポートページのURLやメールアドレス等
    name: API support
    url: http://sample_suppurt.com
    email: support@sample.com
  license: # ライセンス情報
    name: Apache 2.0
    url: http://www.apache.org/licenses/LICENSE-2.0.html
  version: 1.0.0
paths:
  /api/sample: # エンドポイント
    get: # HTTPリクエスト
      summary: 'サンプルサマリー' # サマリ
      description: '説明' # 説明
      tags:
        - Sample # エンドポイントのタグ
      parameters: # リクエストパラメータ
        - name: name # カラム名
          in: path # パラメータをパス内に含める
          description: '名前' # カラムの説明
          required: true # 必須かどうか
          schema:
            type: integer # 型を指定
      responses: # レスポンス定義
        '200': # HTTP status
          description: 'レスポンスの説明'
          content:
            application/json: # レスポンスの形式指定
              schema: 
                required:
                  - name
                  - email
              type: object
              properties:
                name:
                  description: 名前
                  type: string
                  example: 'サンプル太郎'
components:
  schemas: # スキーマオブジェクトの定義
    User: # モデル名
      type: object # 型
      required: # 必須フィールド
        - id
        - name
      properties:
        id: # プロパティ名
          type: integer # 型 
          format: int64 # フォーマット(int32, int64等)
          example: 1
        name:
          type: string
          example: 'サンプル太郎'
```

# 参考
[Swagger Documentation](https://swagger.io/docs/)
[OpenAPI (Swagger) 超入門](https://qiita.com/teinen_qiita/items/e440ca7b1b52ec918f1b#%E5%8F%82%E8%80%83)
認識合わせの難しさについて

##### アジェンダ

- 0.この投稿について
- 1.背景について
- 2.原因
- 3.対策
- 4.意識すること
- 5.ideeeチーム開発への還元
 
---

##### 0.この投稿について

私は本業でSierでSEをやっているものです。
強くWeb系技術に惹かれて「[ideee](https://www.ideee.tech/about?utm_source=qiita&utm_medium=article&utm_id=yamaura)」のチーム開発に2021/9に参加しました。
そのideeeにて毎週行なっているLT（Lightning Talks）で発表した内容を投稿しました。

---

##### 1.背景について

###### 本業のシステム開発にて、関係者間での認識合わせが如何に難しいかを感じている

次ページで挙げる問題が発生するため、認識合わせが如何に難しいかを考える。
どうしたら、認識が大きくズレないかを考える必要がある

---

##### 1.1.背景について(問題)

そもそも、どんな問題があるんだろう？？

**問題**
- お客様との取引において、請負った仕事の内容で明文化されていない作業の取り扱い
- チーム開発において、認識違いによる仕様不備による手戻り
- 言った、言わないのやりとりが出て問題が起きる

---

##### 2.原因

**原因は何にあるのか？**

どんな原因があるかを、３つ考えてみました！

---

##### 2.1.原因1

**会話の「具体的なテーマが明確」になっていない（何を話したいかが明確になっていない）**

   ```:ex)箱について話をする場合
   自分は箱のサイズについて話をしたいが、
   話し相手は箱の柄・デザインを話したいので、
   テーマがズレていて会話が噛み合わない。
   ```

---

##### 2.2.原因2

**話し相手の「基礎知識（前提知識）」がどこまであるかを踏まえられていない**

   ```:ex)箱についてを話をする場合
   自分は「箱の色が白色」の前提で話をしていても、
   話し相手は「箱の色が黄色」の前提と話をしていると会話が噛み合わない。
   ```

---

##### 2.3.原因3

**話の内容が「文章に残っていない」、「曖昧」、「解釈に違いがある」、**

   ```:ex)箱についてを話をする場合
　　　　　　自分：この間、会話した通りのサイズで箱を作ってね。
　　　　　　相手：そんな話ってありましたっけ？（文章に残っていない）
　　　　　　自分：あったよ。
　　　　　　相手：ありましたっけ？。。。じゃあ、どれぐらいの長さでやりますか？
　　　　　　自分：猫が横に伸びたぐらいの長さでお願い。（曖昧）
　　　　　　相手：わかりました。

　　　　　　＜後日＞
　　　　　　相手：箱作りました。
　　　　　　自分：えっ、長くない？
　　　　　　相手：うちの猫が横に伸びたら、これぐらいですよ。（解釈の違い）
   ```

---

##### 3.対策

**対策はどうすれば良いのか？**

対策は３つあります。

---

##### 3.1.対策1

- 会話の冒頭にテーマを話してから、話を進めていく
- 何を話したいかを明確にしてから、話をしていく

---

##### 3.2.対策2

- 基礎知識（前提知識）がどこまであるかを会話の中で確認していく

    - 会話の中で噛み合わない時は、基本知識（前提知識）にズレがあります

    - 前提となる情報が、お互いに同じ認識であるかを確認し、ズレを無くす

---

##### 3.3.対策3

- ビジュアルで認識合わせを行う（イメージ図、議事録などの文字面で認識を合わす）

    - 会話だけだと空中戦となる
    - 空中戦だと、イメージが「曖昧」、「解釈の違い」となる
    - 「曖昧」、「解釈の違い」には、ビジュアルですり合わせる。
    - ビジュアルとして確認したものは、エビデンスが残せる
    - エビデンスがあれば、関係者間で振り返ったときにも認識がズレない

---

##### 4.意識すること

「3.対策」に加えて、以下を意識することで、相手の思いを読み取れ、自分の思いを伝えられると思います。

1. 相手にとって重要なことが何であるかを意識して会話する
2. 自分によって重要なことが何であるかを明確に伝える

   ```
   聞き手は重要なことしか気にしていなくて、その重要なことも最大で３つまでじゃないと伝えたいことがボヤけます。
   全て重要だと思って話をしても、何が言いたいのか、伝えたいのかが聞き手はわからなくなります。
   ```

---

##### 5.ideeeチーム開発への還元

1. GitHub上で、どういった意図でプログラムを修正したのかを伝えられるようにする

   ```
   1commitのコメントで説明できる内容でプログラムを修正する
   GitHubでソースコードにコメントを入れられるので、コメントをして修正の意図を伝える
   ```

2. 「曖昧さ」は具体的、かつ数字を使って文章に残す。（Issue、Pull Request）
3. 「解釈の違い」が発生しそうな場合は、解釈の仕方についても丁寧に文章で記載をすること


---

### ideeeとは

アイデアとエンジニアのマッチングプラットフォーム

https://www.ideee.tech/about?utm_source=qiita&utm_medium=article&utm_id=yamaura
PCとスマホで投稿記事の件数を変える

## やりたかったこと

WordPressの投稿記事を
・PCでは10件表示
・SPでは3件表示

funtions.phpではなく、メインループで。

```php:PHP
<?php
  if( wp_is_mobile() ){
    $num = 3; //SPでの表示件数
  } else {
    $num = 10; //PCでの表示件数
  }
  $args = array(
  'post_type' => 'post',
  'posts_per_page' => $num,
  'order' => 'DESC'
  ); 
  $new_query = new WP_Query($args);
  if ($new_query->have_posts()) :
  while ($new_query->have_posts()) : $new_query->the_post();
?>
 
// ループの中身

<?php
  endwhile;
  wp_reset_query();
  endif;
?>
```
Javascript 入力内容をもとにアクションを変える

## はじめに
javascriptでプロンプトをだして入力した値で分岐する
### 教科書
https://www.sbcr.jp/product/4815601577/
## 実行内容
```js:yesと入力されたら次のメッセージをだす。違う場合は無反応
<script>
    'use strict';

    const answer = window.prompt('ヘルプを見ますか？');
    if (answer === 'yes') {
        window.alert('タップでジャンプ、障害物を避けます');
    }
    console.log(answer);
</script>
```
### メモ
`let answer`で変数のanswerになり後から中身を変更可能
`const answer`で定数のanswerになり後から変更は不可能
laravel、vue.jsで変更が反映されない

新しい現場でvue.jsの機能変更をしたのですが、
変更内容がブラウザに反映されていなくて、
その解決法をまとめました。

vue.jsを読み込んでいるファイルを変更
↓
ブラウザを確認するも変更ないようがブラウザに反映されていない、、、

一度ログアウトしてからもう一度確認するが全く変化なし。

先輩に相談すると、
```
$ npm install && npm run dev
```
コマンドを実行するとブラウザに反映されるらしい。

実行後ブラウザを確認。
見事に反映されていた。

```
$ npm inistall
```
はnpmリポジトリからライブラリをダウンロードしてくるコマンド。

```
$ npm run dev
```
対象のファイルを全てコンパイルするコマンド。

コンパイルとは？と思ったのでググってみると
*コンパイル＝プログラミング言語で書かれたソースコードを解析し、
　　　　　　 コンピュータが直接実行可能な形式のプログラムに変換すること。
           そのためのソフトウェアをコンパイラ（compiler）という。
と記述されている。
わかりやすく言えば、記述したコードをパソコンが理解できるように変換する事。
その変換作業ができるのがコンパイラというソフトウエアという認識でいよう。
40代おっさんpythonでGoogleスプレッドシートを操作してみる

## 本記事ついて

本記事は プログラミング初学者の私が学習していく中でわからない単語や概要をなるべくわかりやすい様にまとめたものです。
もし誤りなどありましたらコメントにてお知らせいただけるとありがたいです。

## GCPに登録してAPIの有効化して使えるようにする

・Googleアカウントが必要になる

Google Cloud Platformを開いて登録を行う

### My First Projectを選んで新しいプロジェクトを選ぶ

プロジェクトを記入
場所は組織なし
＊会社に属している場合は必要かも

右上に通知が出てきます
作成出来たらプロジェクトを選択
プロジェクトに飛ぶと思います。
上のタスクバーが自分のプロジェクト名に変わっていると思います

### APIを有効化

左に「APIとサービス」という欄があると思いますのでそこをクリック
「APIとサービスの有効化」という項目が出てきたと思うのでそれをクリック
＊最初はすべて無効になっていると思います

必要な下2つのAPIを有効化
Google Drive API
Google Sheets API

APIとサービスの画面に戻って
下のほうにあるフィルタを見ると2つの項目が追加されています

### サービスアカウントを作る

APIとサービスに行き
認証情報を選ぶ
下にサービスアカウントとありますが今は何もありません
上にある認証情報を作成をクリック
その中のサービスアカウントをクリック
サービスアカウント名をいれて
作成して続行をクリック
ロールを選択で
基本を選んで編集者を選んでください
続行をクリック
最後に完了をクリック
サービスアカウントに今作ったものが追加されていると思います

### 秘密鍵を作成

先ほど作ったサービスアカウントの所をクリック
色々出てくると思いますが
キーを選んで
その中にある鍵を追加をクリック
新しい鍵を選んで
キータイプは
JSONを選んで
作成
秘密鍵がパソコンに保存されましたと出ていると思います
自分のパソコンにもダウンロードされていると思います

## スプレッドシートで認証情報を共有する

今回、操作したいスプレッドシートを開いてください
そちらに
GCPのサービスアカウントのメールアドレスをコピーしたものを
右上にある共有を押して編集者として追加してください

## スプレッドシートを操作

### API使用に必要な認証

先ほどダウンロードした秘密鍵を作業ディレクトリに移動してください

#### 必要なライブラリをインストール

```python
!pip install gspread
!pip install google-auth
```

#### インポート

```python
import gspread
```

```python
from google.oauth2.service_account import Credentials

scopes = [
    'https://www.googleapis.com/auth/spreadsheets',
    'https://www.googleapis.com/auth/drive'
]

credentials = Credentials.from_service_account_file(
    'path/to/the/downloaded/file.json',
    scopes=scopes
)

gc = gspread.authorize(credentials)
```

from google.oauth2.service_account import Credentials
これで認証系をインポート

scopes
対象範囲
今回は必要になるAPIを指定

Credentials.from_service_account_file
ファイルから秘密鍵の情報をとります
秘密鍵の位置をパスで指定

scopes=scopes
スコープ範囲を引数で指定

これで認証周りはおｋです

### スプレッドシートからデータを取得

#### スプレッドシートキー

スプレッドシートキーを変数に格納
シート名も変数に格納

```python
SP_SHEET_KET = 'スプレッドシートキー'
SP_SHEET = 'シート名'
```

スプレッドシートキーは
スプレッドシートのURL
docs.google.com/spreadsheets/d/{スプレッドシートキー}/edit#gid=0
こちらに書かれている

#### スプレッドシートを読み込む

```python
sh = gc.open_by_key(SP_SHEET_KET)
```

gc.open_by_keyでSP_SHEET_KET引数でしていして読み込む
変数shにはスプレッドシート全体の情報が入ったことになります

#### シート情報を読み込む

```python
worksheet = sh.worksheet(SP_SHEET)
```

worksheetにシート情報を引数にすると読み込めます

#### シートの中身を見る

```python
data = worksheet.get_all_values()
data
```

get_all_values()
シートのすべての値を取ってきています

## 新規シートの追加

```python
new_worksheet = sh.add_worksheet(title='new', rows=100, cols=100)
new_worksheet
```

add_worksheetでシートの追加
titleでシートの名前
rows=100, cols=100で100行×100行のシートを用意

これで新しいシートが出来たと思います

## 書き込み

```python
new_worksheet.update_cell(1,1, 'test value')
```

new_worksheetのA1にtest valueを書き込みました

### 参考資料

<https://www.udemy.com/course/python-web-api/learn/lecture/25073250#overview>
【Ruby】dry-validationでバリる

# 概要
Validationを良い感じにやってくれるgemを現在使っているため備忘録として記事を書いていく。
ここでは特に`dry-validation`と`dry-strcut`を解説！

# 使うまでの流れ
## 1.Gemをインストール

```bash
$ gem install dry-validation
$ gem install dry-struct
```

## 2.基底クラスを作成

```ruby
class ApplicationContract < Dry::Validation::Contract
  config.messages.default_locale = :ja # デフォルトメッセージを日本語に変換
  config.messages.backend = :yaml
  config.messages.load_paths << 'config/contract/error.yaml' # 任意のエラーメッセージを書くロケールファイルのpath
  EMAIL = /.@./ # メールバリデーション用
  PHONE_NUMBER = /\A[0-9+\-]+\z/ # 電話番号バリデーション用
end
```

```ruby
class Struct < ::Dry::Struct
  module Types
    include ::Dry.Types()
  end
end
```

## 3.ロケールファイルを作成する

```ruby
ja:
  dry_validation:
    errors:
      sample: 'エラーです'
```

# dry-validation編
## 実際の定義方法

```ruby
class Sample < ApplicationContract
  # 渡ってきたパラメーターをチェック
  params do
    required(:sample1).filled(:string) # sample1カラムは必須でstring型かどうかチェック
    required(:sample2).maybe(:string) # sample2カラムは必須で値が入っている場合はstring型かどうかチェック
    optional(:sample3).filled(:integer) # sample3は必須でなくてinteger型かどうかチェック
    required(:sample4).value(:string) # 厳密にstring型かどうかをチェック
  end

  # 自分好みなバリデーションチェック
  rule(:sample5) do
    if sample5
      key(:sample5).failure('サンプルエラー')
    end
  end
end
```

# dry-strcut編
## 実際の定義方法

```ruby
class Sample < Struct
  attribute :sample1, Types::String # string型で定義
  attribute :sample2, Types::Strict::String # string型で定義し間違ったタイプの値が渡されるエラーを起こす
  attribute :sample3, Types::Coercible::Integer # 強制メソッドを使用して属性を正​​しい型(integer)に変換
  attribute :sample4, Types::Params::Date['2022-4-8'] # HTTPパラメータ用の型
  attribute :sample5, Types::Params::Date['2022-4-8'] # HTTPパラメータ用の型
end
```

# 参考
[dry-rb/dry-validation](https://github.com/dry-rb/dry-validation)
[dry-rb/dry-struct](https://github.com/dry-rb/dry-struct)
[Rubyでvalidationするdry-validationの基本のキ](https://qiita.com/tasogarei/items/0ba8d12c7277aa363492)
[dry-struct](https://dry-rb.org/gems/dry-struct/1.0/)
[dry-types](https://dry-rb.org/gems/dry-types/1.2/)
[dry-rb/dry-structメモ](https://linyclar.github.io/rails_memos/libraries/dry-rb/dry-struct/)
[dry-rb/dry-typesメモ](https://linyclar.github.io/rails_memos/libraries/dry-rb/dry-types/)