OmniAICreator/Qiita-1.07M huggingface dataset:
QiitaのMarkdownパーサー変更にともなう注意点について

## はじめに

:::note
2022年2月24日にQiitaのMarkdownパーサーの変更を正式版としてリリースしました！
:::

パーサー変更に関する注意点や、変更を行った背景、今後については[Qiita Blog](https://blog.qiita.com/replace-markdown-parser-general/)にまとめていますのでご確認お願いします。

https://blog.qiita.com/replace-markdown-parser-general/

## 新しいMarkdownパーサーについて

これまでのQiitaでは、[Redcarpet](https://github.com/vmg/redcarpet)を拡張した[Greenmat](https://github.com/increments/greenmat)を用いてMarkdownのパースを行っていました。
今回のリリースでは、[CommonMarker](https://github.com/gjtorikian/commonmarker)を拡張した[Qiita Marker](https://github.com/increments/qiita_marker)へ変更いたします。

これにより今までと記法が変わっている箇所があります。今まではGitHub Flavored Markdown （以下GFM）に準拠していない記法もありましたが、今回の変更で準拠されるようになりました。

## 新しい記法に関する注意点

パーサーの変更によって細かな違いはありますが、**基本的なパース結果は今までと変わりありません**。

しかし、過去執筆した記事を編集しようとしたときに、想定外の箇所で見た目の崩れが起きてしまう可能性もあります。

せっかく書いた記事の表示が崩れてしまうのは困るので、可能な限り防ぎたいと考えています！
そこで、記事を書く時に注意すべき記法などについて、Qiita運営から5日間に分けて10記事のシリーズで投稿することになりました！
少しでも皆様が執筆する際の助けになればと思います。

（※ 主要な差分のみを確認したい方は以下のヘルプページをご確認ください。）

https://help.qiita.com/ja/articles/qiita-markdown#markdown

## GitHub Flavored Markdown(GFM) について

また一連の記事投稿では、GFMの記法についても説明していきます。
GFMはQiita以外でも使われているメジャーなMarkdown記法なので、馴染みのある方もいるのではないかと思います。

GFMの仕様は [GitHub Flavored Markdown Spec](https://github.github.com/gfm/) にまとめられているのですが、普段Markdownを書いている人でも「こんな書き方があったのか！」という驚きが、実は結構あります。

この機会にGFMについて知ってもらって、Markdownをあまり書いたことがない人でも楽しくMarkdownが書けるようになるようなきっかけになったらと思います。
もしかしたらQiita以外でも役立つ新たな発見があるかもしれません！

## 記事投稿シリーズのスケジュール

一連の記事投稿シリーズでは、実際の記事の中で見られた記法の違いなどについてピックアップして説明します。

- 1日目（2/8）
    - 1.本記事
- 2日目（2/9）
    - [2. 見出しについて](https://qiita.com/degudegu2510/items/a4dbf82d15d80d8bd17e)
    - [3. 引用、HTMLブロックについて](https://qiita.com/kyntk/items/4fd09e6fb041edeac2d9)
- 3日目（2/10）
    - [4. コードブロック、コードスパンについて](https://qiita.com/ohakutsu/items/d9ec356ce9bec4e83ef1)
    - [5. テーブルについて](https://qiita.com/aizawa213/items/9c7b1d3a41293e0e3812)
    - [6. リスト、タスクリストについて](https://qiita.com/kato-hiroaki/items/b8bcc1e3eb28facf7467)
- 4日目（2/14）
    - [7. 強調、打ち消しについて](https://qiita.com/fussy113/items/b40f60f6803aa2d5cfbc)
    - [8. 脚注ついて](https://qiita.com/phigasui/items/010f4c79ff47bbaaebdd)
- 5日目（2/15）
    - [9. リンク、オートリンクについて](https://qiita.com/mziyut/items/bc00ed7396bd60a717b8)
    - [10. ノート記法について](https://qiita.com/xrxoxcxox/items/0c91e5319c32434f2c6a)

## 最後に

パーサー変更に関する注意点や、変更を行った背景、今後については[Qiita Blog](https://blog.qiita.com/replace-markdown-parser-general/)にまとめていますのでご確認お願いします。

https://blog.qiita.com/replace-markdown-parser-general/

もし、ご意見・ご要望がありましたら[Qiita Discussions](https://github.com/increments/qiita-discussions/discussions)よりいつでもお寄せください。
ハッシュとdefault_procについて

##ハッシュでの[]の使い方
ハッシュでの[]はkeyに対応したvalueの値を返すんです。

```
h = {:hoge => "HOGE" , :fuga => "FUGA"}
p hash[:hgoe]
=> "HOGE"
```

##デフォルトの値をいれる。
先ほどのコードを使ってデフォルトの値を入れてみます

```
h = {:hoge => "HOGE" , :fuga => "FUGA"}
p hash[:hgoe]
=> "HOGE"

#デフォルトの値を入れてみる

h = {:hoge => "HOGE" , :fuga => "FUGA"}
h.default = "これはデフォルトだよ!"
p h[:foo]
=> "これはデフォルトだよ!"
```

##default_procとは
defaultのprocです。procに関しては、以前ぼくが解説した[こちら](https://qiita.com/IshidaKeisuke/items/f3919842179a82aa98d9)の記事を参考にしてみてください

ざっくりいうと、ブロックをオブジェクトにして使いまわせるようにしたものがProcというものなんです。
そのProcをデフォルトにしたものが、default_procというものです。
実際にコードを見ていきましょう。

```
h = Hash.new {|hash, key| "The #{key} not exist in #{hash.inspect}"}
p h.default              #=> nil
p block = h.default_proc #=> #<Proc:0x0x401a9ff4>
p block.call({},:foo)    #=> "The foo not exist in {}"
```
>[公式ドキュメントより](https://docs.ruby-lang.org/ja/latest/method/Hash/i/default_proc.html)

3行目で変数hで記述されたブロックをデフォルトのProcとして変数blockに格納しています。
そのProcを呼び出すには、callメソッドを使うんでしたね。なので、4行目ではcallメソッドが使われています。

##実際に問題を解いてみましょう
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2485775/d3349b41-782b-f539-f6b7-7202af51204d.png)
みなさんも考えながら解いてみてください

###選択肢➀
この選択肢だとエラーが起きます。
理由は、この記述の仕方はKeyに対応したvalueの値をとります。しかし、この選択肢では、valueの値が設定されていません。そのため、この選択肢では、不正解です。

###選択肢➁
この選択肢は正解になります。defaultメソッドは、keyとvalueそれぞれにデフォルトの値として何らかの値を入れるメソッドです。今回でいえばnilです。そのため、このコードを打つと、nilが返ってきます。

###選択肢➂
この選択肢だとエラーが起きます
default_procには、new以降のブロックがデフォルトとして入っています。そして、それをcallメソッドで、hには1、kには2をいれるように呼び出しています。callメソッドで意図的にブロックを呼び出して例外を発生させています。そのため、この選択肢では、不正解です。

###選択肢➃
この選択肢は正解になります。default_procメソッドを実行した時の値をnilにしています。つまり、ブロックの値をnilにしているんです。そのため、p hash[:a]と打った時に、nilが返ってくるんですね

以上より、正解は➀と➃でした。
何か間違いがございましたら、ご教示いただけますと幸いです。
日記；回すだけIII ②サポート・ソフト BLST01

　[日記；回すだけIII ①CANopenの準備](https://qiita.com/jamjam/items/e9dbf6f467683fda7683)で、CANバスに接続をしました。オリエンタルモーターのWebでは、次のソフト、解説書がダウンロードできます。執筆時点では、Modbus経由のモータ回転の解説書はありますが、CANopen用はありません。最後の英語の資料は、Object Dictionaryの一覧です。
　　https://www.orientalmotor.co.jp/products/detail.action?hinmei=BLMR5100K-A-B%2BBLVD-KRD&seriesCd=FJ01&additionalHinmei=CCM010B1AAF#option01

- サポート・ソフト BLST01
- BLVシリーズ Rタイプ 設置・接続編　HP-5139J.pdf
- BLVシリーズ Rタイプ 機能編　HP-5141J.pdf
- BLV Series R Type Driver CANopen Communication Profile　HP-5143E.pdf

###サポート・ソフト BLST01でできること
　PCとはUSBmicroのケーブルでコントローラと接続します。
　簡易設定で、CANバスのスピード、IDの変更ができます。デフォルトのID=1から10へ変更しました。また、Bitrateを一般的な1000kbpsに変更し、ドライバへ反映をクリックして有効にしました。
<img width="800" alt="ファイル名" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/407635/898006cb-8a0e-3055-4abb-57813ed0e114.png">

　CANopen通信ステータスモニタを有効にします。CANopenでは二つの状態遷移が実装されています。
<img width="700" alt="ファイル名" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/407635/8bde7c21-84f3-28f1-7f50-f4e75fa19f9f.png">


- Network Management (NMT)は、このツールでは「通信ステート」
- CiA402 ドライブプロファイルのState machineは、このツールでは「運転ステート」

で確認できます。このシステムでは、NMTはどうも自動で遷移するようなので、とくに何も触りません。
　運転ステートのデフォルトはSwitch on disableです。これをリモート運転のS-ONのONをチェックすれば、いくつもある遷移を一気にOperation enabledにしてしまいます。通常過電流などのエラーが起こるとFaultに落ちます。

<img width="600" alt="ファイル名" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/407635/1095870e-0fc6-e54b-f41e-42e1c498b952.png">



　このリモート運転の画面で、実際のモータの回転を行えます。リアルなPDOから読み取った位置や速度は、この画面の上部に表示されます。
　移動速度や加速度は、このモータに適した値が初期値で入っているので、Faultになることはないです。

###デバッグ用ツール
　波形の記録、描画機能
![2022-02-03 (8).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/407635/b4bc0eb1-959c-2e51-4da8-075bbde8f987.png)
　Object Dictionaryの一覧
![2022-02-03 (9).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/407635/06acf2f6-382f-ec7b-37b3-329d32c82056.png)
　モータのチューニング
![2022-02-03 (11).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/407635/1db31d06-94e2-bb15-e737-3c15b98a5b55.png)
データベースでよく使う変数or関数名orメソッド [随時更新予定]

# 
データベース上の操作と通常の操作をはっきり区別できてなく混乱のもとになっていたのでメモ、もしかしたら遺言になるかもしれまんが念の為のメモ

タイトルは仮

## 読み書き

データベース
store/restore 
insert/fetch

それ以外
save/load
write/read

## メソッド


```
def test():
    yield [1,2,34] # ここで一時停止
    print('aaaa')  # 2回目のnext実行後に出力される


def test2():
    gen = test()
    print(next(gen)) # [1,2,34]
    print(next(gen)) # 'aaaa'
```
```
[1, 2, 34]
aaaa
StopIteration
```
lightning-datatable

使用标准得lightning-datatable时得注意事项
1.如果没有设置td得width，那么默认得最大的值时1000px
比如说有一个table，她的长度是1500px
这个时候你有两列，一列设置宽度是100px，一列没有设置宽度。
那么画面上显示得最终效果是1100px
変数定義した配列を呼び出す時、存在しないインデックス番号だとどうなるか?

_コード_

```
var coffee=["moca","santos"];
console.log(coffee[1]);
console.log(coffee[7]);
```

_出力_
santos
undefined

配列やオブジェクトも一緒で、存在しない要素を取得しようとすると
値が定義されていない（存在しない）という意味の「undefined」と出力されます
例えばif文で

_コード_

```
if(coffee===undefined){ 
 console.log("品種は未設定です"); 
}else{ 
 console.log(coffee);
}
```

_意味_
変数coffeeがundefinedの場合
「品種は未設定です」と出力
変数coffeeがundefinedではなかった場合
変数coffeeが出力される

このような記述をした際、このif文の条件式で使われている「undefined」は文字列ではないため、「"」で囲わなくてもよくなります。
Linuxコマンド備忘録

作業中に躓いたところをまとめる


```
chown -R hogehoge image
```

したら、こんな警告が出てきて困った

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2513603/98a8b1e0-2782-acb9-5545-91c956835f45.png)

これで解決。sudoでスーパーユーザ権限で実行するみたい。

```
sudo chown -R hogehoge image/
```

参考サイト様
https://wa3.i-3-i.info/word11269.html
https://proengineer.internous.co.jp/content/columnfeature/8843#section100
ブラウザでディープコピーができるようになった

Chrome98 で `structuredClone`が来ました。
すでに Firefox では導入済みで、 ~~Safari もプレビュー版で導入されているので、近い将来にメジャーブラウザでディープコピーが簡単にできるようになりそうです。~~ [Safari 15.4 で実装](https://webkit.org/blog/12445/new-webkit-features-in-safari-15-4/#web-apis)されました。

[structuredClone API | Can I use... Support tables for HTML5, CSS3, etc](https://caniuse.com/mdn-api_structuredclone)

```js
const original = {
	x: 0,
	arr: [1, 2],
}

// 共有渡し
const sharing = original
//「浅い」コピー
const shallowCopy = {...original}
// ディープコピー
const deepCopy = structuredClone(original)

original.x = "x"
original.arr[0] = "x"

console.log(original)
// {x:"x", arr:["x",2]}

console.log(sharing)
// {x:"x", arr:["x",2]}

console.log(shallowCopy)
// {x:0, arr:["x",2]}

console.log(deepCopy)
// {x:0, arr:[1,2]}
```

なお、コピーできるオブジェクト｜リテラルは限定されていますので注意が必要です。
[構造化複製で動作しないもの | 構造化複製アルゴリズム - Web API | MDN](https://developer.mozilla.org/ja/docs/Web/API/Web_Workers_API/Structured_clone_algorithm#%E6%A7%8B%E9%80%A0%E5%8C%96%E8%A4%87%E8%A3%BD%E3%81%A7%E5%8B%95%E4%BD%9C%E3%81%97%E3%81%AA%E3%81%84%E3%82%82%E3%81%AE)


---


とはいえ、しばらくは lodash を使いそう。
C++　赤黒木のチェック関数

LLVMで使用されている平衡二分探索木である赤黒木のチェック関数についてのメモです。チェック関数を見ることで、赤黒木のルールの必然性が見えてきます。

## 二分探索木とは

赤黒木の前に二分探索木の説明を軽く行います。

二分探索木はノードごとに値が割り振られているとしたとき、枝を持つ全てのノードで下記の条件を満たします。

```
左ノード値  <  任意のノード値  <  右ノード値
```

二分探索木は二分探索[^二分探索]をする際、再帰的なコーディングが可能なデータ構造です。

[^二分探索]: ソート済みのデータを値の大小関係を用いて左右の値を見ていく手法。$O(\log{n})$の計算時間で実行できます。

### 平衡二分探索木とは

平衡二分探索木とは、二分探索木の一種で木の高さ（根からの階層の数）を自動的にできるだけ小さく維持しようとするものを言います。赤黒木の他にも様々なアルゴリズムが存在します。

## 赤黒木とは

赤黒木は、各ノードに「赤」または「黒」という「色」をつけた二分探索木です。
通常の二分探索木としての条件のほか、以下の条件を適応することで平衡を維持します。

1. 各ノードは赤か黒の色をもつ
- 根は黒である
- 葉はすべて黒である
- 赤のノードは黒ノードを2つ子に持つ
- 根から葉までの道に含まれる黒いノードの数は、葉によらず一定である

## 赤黒木のチェック関数

値の大小関係のチェックは行いません。構造的に赤黒木のルールを満たしているのかのみ、チェックします。

### rootノードのチェック

llvmの赤黒木では、Iteratorを扱う都合上、rootノードの親としてend_nodeが存在します。赤黒木のチェック関数では、赤黒木の構造チェックに合わせて、end_nodeのチェックも行います。

- end_nodeのチェック
    - ルートの親にend_nodeが存在する
    - ルートは親のend_nodeの左ノードである
- 赤黒木のチェック
    - ルートは黒である
    - ルートの持つノードがサブノードの条件を満たしている

ルートがNULLである場合は正しいノードとして、そこでチェックを終了します。

```cpp
template <class _NodePtr>
bool __tree_invariant(_NodePtr __root) {
  if (__root == nullptr)
    return true;
  if (__root->__parent_ == nullptr)
    return false;
  if (!__tree_is_left_child(__root))
    return false;
  if (!__root->__is_black_)
    return false;
  return __tree_sub_invariant(__root) != 0;
}
```

### サブノードのチェック

木の構造に問題がないか、**赤が黒の子を2つ持っているか**のチェックのみ、行います。

- 親子関係のチェック
    - 親子関係が適切につながっている
    - 左右のノードが同じではない
- 色のチェック
    - サブノードが赤ノードの場合は、左右が共に黒である
- 再帰的チェック
    - サブノードは、適切なサブノードを持つ

```cpp
template <class _NodePtr>
unsigned __tree_sub_invariant(_NodePtr __x) {
  if (__x == nullptr)
    return 1;
  if (__x->__left_ != nullptr && __x->__left_->__parent_ != __x)
    return 0;
  if (__x->__right_ != nullptr && __x->__right_->__parent_ != __x)
    return 0;
  if (__x->__left_ == __x->__right_ && __x->__left_ != nullptr)
    return 0;
  if (!__x->__is_black_) {
    if (__x->__left_ && !__x->__left_->__is_black_)
      return 0;
    if (__x->__right_ && !__x->__right_->__is_black_)
      return 0;
  }
  unsigned __h = __tree_sub_invariant(__x->__left_);
  if (__h == 0)
    return 0;
  if (__h != __tree_sub_invariant(__x->__right_))
    return 0;
  return __h + __x->__is_black_;
}
```

## 参考

https://github.com/llvm/llvm-project
Laravel email テスト設定メモ 私の備忘録 phpunit.xmlのMAIL_DRIVERの設定をsmtpにする

# Laravelはテスト環境ではメール送信をしない

ユニットテストでメール送信しても届かない。なんでかな、と思って調べてみたら、Laravelはテスト環境ではメール送信をしない様だ。それもそうだ、テストで本物のアドレスにバンバン送ってたら苦情が来そうだ。
ということで、公式のドキュメントでは以下のメールテストの方法が書かれていた。（現在Laravel6の作業をしているのでLaravel6のドキュメント参照しました。）
- ログに書き出す
- 特定のメールアドレスに転送する
- mailtrapを使う

.env_testingのドライバーをmailtrapにすれば動くかなぁと、思っていたのだが、ぜんぜん届かない。

# 結論
結局 **phpunit.xml** にMAIL_DRIVERの記述があって

```xml
<server name="MAIL_DRIVER" value="array"/>
```
を

```xml
<server name="MAIL_DRIVER" value="smtp"/>
```
に、したらunittestでもmailtrapにメールが届くようになった。
Laravelの設定ばかり探して、無駄に苦労してしまった。
とほほ。


# 以下ドキュメントを読んだときのメモ
https://laravel.com/docs/6.x/mail#mail-and-local-development

>
When developing an application that sends email, you probably don't want to actually send emails to live email addresses. Laravel provides several ways to "disable" the actual sending of emails during local development.

>>DeepLで翻訳
>>メールを送信するアプリケーションを開発する際、実際に生きているメールアドレスにメールを送信することは、おそらくないでしょう。Laravelでは、ローカル開発中に実際にメールを送信しないようにする方法がいくつか用意されています。



## Log Driverを使う方法

>Instead of sending your emails, the log mail driver will write all email messages to your log files for inspection. For more information on configuring your application per environment, check out the configuration documentation.
>>DeepLで翻訳
>>ログメールドライバは、メールを送信する代わりに、すべてのメールメッセージをログファイルに書き出し、検査します。環境ごとのアプリケーションの設定についての詳細は、設定ドキュメントを参照してください。


## Universal To

> Another solution provided by Laravel is to set a universal recipient of all emails sent by the framework. This way, all the emails generated by your application will be sent to a specific address, instead of the address actually specified when sending the message. This can be done via the to option in your config/mail.php configuration file:

>> DeepLで翻訳
>> Laravelが提供するもう一つの解決策は、フレームワークによって送信されるすべての電子メールのユニバーサル受信者を設定することです。この方法では、アプリケーションによって生成されたすべての電子メールは、メッセージを送信するときに実際に指定されたアドレスの代わりに、特定のアドレスに送信されます。これは、config/mail.php設定ファイルのtoオプションで行うことができます。

## Mailtrap
>Finally, you may use a service like Mailtrap and the smtp driver to send your email messages to a "dummy" mailbox where you may view them in a true email client. This approach has the benefit of allowing you to actually inspect the final emails in Mailtrap's message viewer.
>>DeepLで翻訳
>>最後に、Mailtrapとsmtpドライバのようなサービスを使って、メールメッセージを「ダミー」のメールボックスに送信し、本当のメールクライアントで表示することができます。この方法は、Mailtrapのメッセージビューワーで最終的なメールを実際に検査することができるという利点があります。
MLflowモデルレジストリのWebhookでMLOpsをスムーズに

[How to Streamline MLOps With MLflow Model Registry Webhooks \- The Databricks Blog](https://databricks.com/blog/2022/02/01/streamline-mlops-with-mlflow-model-registry-webhooks.html)の翻訳です。

:::note warn
本書は抄訳であり内容の正確性を保証するものではありません。正確な内容に関しては原文を参照ください。
:::

AI/機械学習が世の中でより広く受け入れられるにつれ、企業は価値を最大化するために機械学習モデルを迅速かつ大規模にデプロイする必要が出てきています。本日我々は、お使いのCI/CDプラットフォームと連携することで、モデルのライスサイクルを容易に自動化することができる[MLflowモデルレジストリWebhook](https://docs.databricks.com/applications/mlflow/model-registry-webhooks.html)を発表します。

モデルレジストリのWebhookを用いることで、新規モデルバージョンの作成、新規コメントの追加、モデルのステージの変更のようなモデルレジストリのイベントによって起動されるコールバックを登録することができるようになります。このコールバックを用いて、DatabricksにおけるMLOpsを実装するために自動化スクリプトを起動することができます。例えば、新規モデルバージョンが作成された際にCIのビルドを起動したり、モデルのプロダクションへの移行がリクエストされた際にSlackを通じてチームメンバーに通知を出すことが可能となります。MLのワークフローを自動化することで、開発者の生産性を改善し、モデルのデプロイメントを加速することで、皆様のエンドユーザー、組織の価値を増加させることができます。

Databricksをお使いの皆様は、MLflowモデルレジストリWebhookをパブリックプレビューとして利用できます。
![](https://databricks.com/wp-content/uploads/2022/02/model-registry-webhooks-1024x682.png)

# WebhookはMLflowモデルレジストリとのインテグレーションをシンプルにします

[MLflowモデルレジストリ](https://databricks.com/product/mlflow-model-registry)は、モデルデプロイメントのライフサイクルを管理する中央レポジトリを提供します。現在、MLチームはモデルレジストリのモデルを手動で管理しています。しかし、チームが成長し、多くのMLユースケースをカバーするようになると、モデルの数は増加し続け、これらのモデルを手動で管理することは非効率的、非現実的になります。多くのチームでは、変更を監視するために定期的にモデルレジストリにポーリングを行うアドホックなサービスを構築して、モデルデプロイメントのライフサイクルを自動化しています。モデルレジストリWebhookは、モデルレジストリでイベントが生じた際にリアルタイム通知を送信することでこの自動化をシンプルなものにします。CI/CDプラットフォームや事前定義済みのDatabricksジョブのワークフローを、Webhookで起動するように設定することができます。

# Webhookを用いたMLOpsユースケース

Webhookを用いて、MLflowモデルレジストリと連携するようにセットアップすることで、皆様の機械学習ワークフローを自動化することができます。例えば、以下のインテグレーションを実施するためにWebhookを使用することができます。

- モデルの新バージョンが作成された際、モデルを検証するためにCIワークフローを起動する。
- モデルのステージ変更リクエストを受け取った際、メッセージアプリケーションを通じて承認待ちのリクエストをチームに通知する。
- モデルのプロダクションへの遷移のリクエストがあった際、モデルの公正性、バイアスを検証するワークフローを起動する。
- タグが作成された際、自動でモデルをデプロイするデプロイメントパイプラインを起動する。

モデルデプロイメントライフサイクルを自動化することで、モデル品質の改善、作業重複の削減を行い、MLチームのそれぞれのメンバーが本来行うべきことにフォーカスできるようになります。MLflowモデルレジストリの先進的なユーザーの一部では、数百万のMLモデルを管理するためにすでにWebhookを活用しています。

# MLflowモデルレジストリWebhookを使ってみる

トライしてみようと思いましたか？MLflowのモデルレジストリWebhookの詳細、使い方に関しては、[AWS](https://docs.databricks.com/applications/mlflow/model-registry-webhooks.html)、[Azure](https://docs.microsoft.com/en-us/azure/databricks/applications/mlflow/model-registry-webhooks)、[GCP](https://docs.gcp.databricks.com/applications/mlflow/model-registry-webhooks.html)のドキュメントを参照ください。

**AWSドキュメントの日本語訳**

https://qiita.com/taka_yayoi/items/33c2c60a4ce2c5d37eee

### Databricks 無料トライアル

[Databricks 無料トライアル](https://databricks.com/jp/try-databricks)
SameSiteで認証できなくなった奴

## 開発環境

- フロントはReact、バックはLaravelのSPA
- フロントの開発はwebpack-dev-serverを立ち上げ、LaravelのAPIを叩きにいく感じ
- 本番環境はbuildしたJSを含むLaravelで動いている
- 認証はセッションで行う

## 序章

フロントの開発環境からログインできない事態が発生。
ログイン後も色々とAPIを叩いているので今更モック作ってとかも辛い状況。


## 結論

ログイン時にサーバーが発行したCookieをクライアントが受け取れていなかった。
認証はセッションで行なっているので、Cookieを受け取れないとForeverステートレスな状態。

## 原因
Chrome84からCookieの属性である`SameSite`のデフォルト値が`None`から`Lax`変更されたことにより、異なるオリジン間でCookieが受け取れなくなっていた。
明示的に`SameSite=None`を設定することでCookieを受け取れるようにはなるが、そうすると次はCookieの属性である`Secure`も必須となる。
> Secure 属性がついたクッキーは HTTPS プロトコル上の暗号化されたリクエストでのみサーバーに送信され、安全でない HTTP では決して送信されない

ちなみに`SameSite`の設定exampleがGitHubで公開されています

https://github.com/GoogleChromeLabs/samesite-examples


## なぜ今気がついたのか

- 前任者から引き継いだ後、プロジェクトがしばらく動いていなかった
- プロジェクトが動き始めた当初はログイン前のUI構築がメインだった

## どうするのか

ローカル環境をSSLする必要があるのかと思ったら`devserver`の`proxy`オプションで解決した。
同一オリジン扱いになるのでCORS問題も解決できそう。

https://webpack.js.org/configuration/dev-server/#devserverproxy

```js:webpack.config.js
module.exports = {
  //...
  devServer: {
    static: {
      directory: path.join(__dirname, 'public'),
    },
    port: 3000,
  },
    proxy: {
      '/api': 'http://localhost:5000',
    },
  },
}
```

上記の場合、`http://localhost:3000/api/user`へのリクエストは`http://localhost:5000/api/user` にプロキシされる。

### HTTPSで動作しているバックエンドサーバーの場合

```js:webpack.config.js
module.exports = {
  //...
  devServer: {
    proxy: {
      '/api': {
        target: 'https://other-server.example.com',
        secure: false,
      },
    },
  },
}
```

### 複数のパスをプロキシしたい場合

```js:webpack.config.js

module.exports = {
  //...
  devServer: {
    proxy: [
      {
        context: ['/auth', '/api'],
        target: 'http://localhost:5000',
      },
    ],
  },
}
```

## 参考資料

https://developer.mozilla.org/ja/docs/Web/HTTP/Cookies

https://developers-jp.googleblog.com/2019/11/cookie-samesitenone-secure.html

https://qiita.com/ahera/items/0c8276da6b0bed2b580c
#PowerPlatform デザインパターン 予約投稿

Power Platform で仕組みを作成するにあたり「自分だったらこうするかなー」というパターン、皆さん持っていませんか？そんな「自分だったらこういう設計にする！」という、所謂「デザインパターン」みたいなモノを記録しておこうかと思ったのでチャレンジする企画です。
今回は「SharePoint Online（以降、SPO）へ予約投稿したい」という要望に対するアプローチです。

# ユースケース
前述どおりなので、あえて細かいコトを説明する必要もないかな？と思いますが、念のため。

1. 投稿先はカスタムリスト。一方的に情報を発信する場所
2. 投稿者は「公開予定日時」を指定して事前に投稿したい
3. 指定した「公開予定日時」を超過するまで、その他ユーザーからは見られたくない
4. 「公開予定日時」は厳密でなくてよい。指定したタイミング”以降”で表示されれば OK

これぐらいのルールで続けます。

# アーキテクチャ
当方だったらどうするか？です。2パターン考えてみました。

## パターン１：Power Automate 単体型
SPO の該当リストへ、何らかの方法で「自分以外の権限を排除」した状態で、かつ「公開予定日時」を指定できる列へ値を入れてアイテムを作成させます。その状態であれば、アイテム投稿をしたユーザー以外は対象アイテムを閲覧することができなくなるハズです。

加えて、Power Automate で対象のリストを監視します。監視といっても、1日ｎ回のタイマー処理で問題無いでしょう。個人的には1回／日で良いんじゃないかと思っちゃいますが、管理する情報に依存するので適宜ご判断をってポイントですかね。タイマー処理が実行されたタイミングよりも「公開予定日時」が前の対象を抽出し、該当するアイテムに『投稿者以外が閲覧などできる権限』を付与すれば、あたかも予約投稿されたかのようになりますよね。
![画像1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215892/71a135f0-0fdd-7501-c9f7-dfc6ac2ae306.png)
上記アーキテクチャですが、詳細未検証なのでチョッと不安なのが「サイトの管理者」もアイテムの権限を切られていたら、フローからでもアイテムが見れないかもしれない・・・。そんな時は管理者も権限付与しておいてしまえば良いんですけどね。

ちなみに、アイテム登録時は手動じゃなくてアプリ等を経由しても良いと思います。シンプルな状態で説明したかったので上記イメージです。このパターンを紹介しておいてナンですが、個人的に現実的ではないと思ってますｗ 権限を切ったり付けたりするのって手間ですし、結果的に「アイテム単位の個別権限設定」になるので管理が煩雑になりますよね。局所的・限定的な利用シーンがあれば光るかもしれません。

### 参考URL
Power Automate で SPO のリストアイテム権限を操作する場合は下記が参考になると思います。

[Power Automate のフローで SharePoint リストアイテムの権限を操作してみる](https://idea.tostring.jp/?p=5427) ［[idea.toString();](https://idea.tostring.jp)］

[リスト アイテムとファイルのアクセス許可を管理し、Power Automateする](https://docs.microsoft.com/ja-jp/sharepoint/dev/business-apps/power-automate/guidance/manage-list-item-file-permissions?WT.mc_id=BA-MVP-5004053)

Power Automate でフィルターしたい場合はフィルタークエリーを利用すると幸せになれるかもしれません。

[【#PowerAutomate Tip's】フィルタークエリー (OData クエリ) メモ](https://qiita.com/yamad365/items/9a24bf7e034ea35bb996)

## パターン２：Power Apps ＋ Power Automate 複合型
パターン１がツラいなら登録画面を Power Apps で作ってしまえばいいじゃない、というのがパターン２です。こちらはイメージを確認するだけで、ある程度の内容が想像できるんじゃないかなー、と思っています。

![画像2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215892/da16a4b5-3b99-daea-f765-9e48fc893c95.png)

Power Apps で「公開予定日時」を含んだ登録画面を提供してデータ登録はアプリ経由で実施させる。
　↓
アプリからは一次領域としてのデータソース(イメージは Dataverse）へ格納。
　↓
パターン１のように Power Automate のフローで公開予定日時を監視。
　↓
公開予定日時を超過したタイミングで該当リストへアイテム登録。

一次領域のデータソースは Dataverse でなくても実現できます。ただし、選択したデータソースによって固有の弱点や制約などがあるでしょうから、その点を踏まえて適宜判断して設計・実装してください。
データソースを中継することによって細かな権限設定から解放されます。ゴールであるリストに対して、フローで書き込むユーザー以外は閲覧権限設定にしておけば基本問題ないかな、という状態になるでしょう。フローでリストに書き込むので、機械的に付与したい属性情報なども補完できますね。

# お断り
実装に関する詳細は記載しませんし、全ての意図も記載しておりません。あくまで”自分だったら、こんなアーキテクチャ”という考察記事です。そのため「ここがうまくデキないんですけど」等のご質問には対応いたしません。当方の方針として、自分たちで調べて、実装して試してみて、使ってみて初めて身につくモノがあると考えております。ですので、是非ともご自身で挑戦をしてください。
どうしても・・・、という場合は、お仕事として対応できる方（※当方含めて）にご相談ください。

「こういう設計のが良いのでは？」
「こういうデザインもあるよね！」
というご意見は大歓迎です。

# まとめ
欲しい仕組みは自分でつくる！（盟友 J リスペクト）ってノリで、考えてみました。

- 公開するタイミングをどう考えるか？（時限式をいかに作るか？
- 予約しておく＝どこかに仕込みの場所が必要

「時限爆弾式」と言い換えてもよいですね。”爆弾”という単語がアレですが、予約というよりも時限式って発想があると他にもパターンが作れそうかなぁ、ですねー。目的は「定めたタイミングで期待した動作をしてほしい」ですから、その本質を貫けば良いと思います。

それでは、皆さま、素晴らしい Power Platform Life を！
【Atcoder for PHP】二次元配列の標準入力について調べる。

はじめまして
趣味的な動機でAtcoderを初めて1か月。全然わからない日々が続いております。実は今現在、パイザラーニングに逃げているのが現状だったりします。

さて、競プロ初心者に最大のネック、標準入力
これについて、いろいろと調べていこうと思っています。
まずは直近で悔しかった二次元配列の取得と出力をPHPにて調査してみました。
他の標準入力についても、今後まとめていこうと思っています。


#二次元配列の取得方法

入力例

|x  |[0]|[1]|[2]|[3]|
|---|---|---|---|---|
|[0]|1  |2  |3  |4  |
|[1]|5  |6  |7  |8  |
|[2]|9  |10 |11 |12 |


###（１）データ数が分からないとき
こちらはパイザでも使える標準入力・・・など初心者向けに出てくるパターンです。
どんなデータがどれだけあるか分からないときは下記のコードを利用しましょう。

```
//1行づつ配列を取得
while ($line = fgets(STDIN)) {
    $tmp[] = trim($line);
}
// 1行目の配列をスペース区切りで取得
foreach ($tmp as $value) {
    $inp[] = explode(' ', $value);
}

//解説？
//whileで取得したデータ
echo $tmp[0];//出力結果：1 2 3 4
echo $tmp[1];//出力結果：5 6 7 8
//foreachで取得したデータ
echo $inputs[0][0]." ".$inputs[0][2]." ".$inputs[0][2]; 
//出力結果：1 2 3
echo $inputs[1][0]." ".$inputs[0][2]; 
//出力結果：5 7
```

###（２）データ数がわかるとき

入力例の最初の行にWとHが与えられる
H=3 W＝4 

|x  |[0]|[1]|[2]|[3]|
|---|---|---|---|---|
|[0]|3  |4  |   |   |
|[1]|1  |2  |3  |4  |
|[2]|5  |6  |7  |8  |
|[3]|9  |10 |11 |12 |

```
fscanf(STDIN, '%d %d', $h, $w);//あるいはデータ数で指示
//二次元配列として取得（[1]行目から取得）
for ($i = 0; $i < $h; $i++) {
    $m[] = explode(' ', trim(fgets(STDIN)));
}
```

#取得した2次元配列の出力
```そのまま出力
for( $k = 0; $k < $w; $k++ ) {
 for( $j = 0; $j < $h; $j++) {
         echo $m[$k][$j], ' ';
    }
    echo "\n";
}

```

```行列を入れ替えて出力
//二次元配列の出力
for ($k = 0; $k < $w; $k++) {
    for ($j = 0; $j < $h; $j++) {
        echo $inp[$j][$k], ' ';
    }
    echo "\n";
}

//（１）を利用した場合、for文の前に下記２行
//$h=$inp[0][0] +1 ;//最初の行を犠牲にするので＋１
//$w=$inp[0][1];
//のとき$j=1;とする。
```

他の出力方法についても今度調べてみよう。
そのうちスピードが速かった回答を分解して研究してみたい。

```
<?php
//1行目取得
list($h,$w)=explode(" ",trim(fgets(STDIN)));
$i=0;
while( $i<$h){
  $m[$i]=explode(" ",trim(fgets(STDIN)));
  ++$i;
}

//出力
$i=0;
while($i<$w){
  $ans=array();
  foreach($m as $v){
    $ans[]=$v[$i];
  }
  echo join(" ",$ans)."\n";
  ++$i;
}
```

むぅ？「join(" ",$ans)」？？？なんでこうなるんだ？
join・・・？

引き続き、他の標準入力についても、まとめていきたいと思っています。
PHPで競プロとかレアだもんね。
CentOS6でcertbot-autoが実行できず、Let's EncryptのSSL証明書が更新できない～2022年度版

#事象
2022年2月1日
centos6上で動くwebサイトのSSL証明書（ Let's Encrypt）の定期再起動がコケている。
ログを確認すると（certbot-autoの実行結果）

```
Bootstrapping dependencies for Legacy RedHat-based OSes that will use Python3... (you can skip this with --no-bootstrap)
yum is hashed (/usr/bin/yum)
To use Certbot on this operating system, packages from the SCL repository need to be installed.
Enable the SCL repository and try running Certbot again.
```

・CentOS6のサポートが終わって、リポジトリが死んでる
・Pythonのバージョンが3.6以上が必要

#アプローチ１
https://qiita.com/uecoeco/items/73f55f3ce192a093c1c0
を行って、yumを実行したところ・・・

```
[Errno 14] problem making ssl connection
```

というエラー。
調べた結果・・・
http://vault.centos.org/ は https://vault.centos.org/ にリダイレクトされるが、TLS v1.2 以上じゃないと接続できない。
CentOS 6 の openssl は TLS v1.2 に対応していないので、エラーになると・・・。
というわけで、baseurl を、http で接続できる国内のミラーサイト(下記など)に変更する

http://ftp.riken.jp/Linux/centos-vault/
http://ftp.jaist.ac.jp/pub/Linux/CentOS-vault/
http://ftp.iij.ad.jp/pub/linux/centos-vault/

#結局
必要なrpmが上記サイトにもなく、断念。。。
Jetpack Compose では同一方向にスクロール可能な Composable をネストできない

Android において、同一方向にスクロール可能なコンテンツをネストしたいというのはよくある話です。

具体的には、縦スクロール可能な`ScrollView`の中に`ListView`を置くという形ですね。

例えば以下のように、コンテンツ一覧を表示する Composable`ListContents`を作成し、それを複数表示する Composable`MultipleLists`を作成したいとします。

```kotlin
@Composable
fun MultipleLists(contents1: List<String>, contents2: List<String>) {
    Column(
        modifier = Modifier
            .fillMaxSize()
            .verticalScroll(rememberScrollState())
    ) {
        Text(text = "コンテンツ1")
        ListContents(contents = contents1)
        Text(text = "コンテンツ2")
        ListContents(contents = contents2)
    }
}

@Composable
fun ListContents(contents: List<String>) {
    LazyColumn {
        items(contents) {
            Text(text = it)
        }
    }
}
```

`Column`の`modifier`に`verticalScroll(rememberScrollState())`を付与し、縦スクロールを可能にしました。

これにより、`ListContents`が膨大になってコンテンツが画面に収まりきらなくても、画面全体を縦スクロールして閲覧できるようにしました。

しかし、これを実際に表示しようとすると、下記の例外が出力されます。

```
java.lang.IllegalStateException: Nesting scrollable in the same direction layouts like LazyColumn and Column(Modifier.verticalScroll()) is not allowed. If you want to add a header before the list of items please take a look on LazyColumn component which has a DSL api which allows to first add a header via item() function and then the list of items via items().
```

どうやら Jetpack Compose では、`LazyColumn`（`LazyRow`）またはスクロール可能な`Column`（`Row`）はネストできないようです。

# 解決方法

`ListContents`の中身を解体して、以下のように`item`や`items`を使って定義するのが良さそうです。

```kotlin
@Composable
fun MultipleLists(contents1: List<String>, contents2: List<String>) {
    LazyColumn(modifier = Modifier.fillMaxSize()) {
        item {
            Text(text = "コンテンツ1")
        }
        items(contents1) {
            Text(text = it)
        }
        item {
            Text(text = "コンテンツ2")
        }
        items(contents2) {
            Text(text = it)
        }
    }
}
```

一旦これで解決しましたが、他にもっとより良い方法があれば教えてください。
ネットワークの勉強メモ

■OSI参照モデル

・MACアドレスはOSI参照モデル第1層と第2層の話

・IPアドレスは第3・4層

・http・ssh・dnsは第7層

・TCP/IPモデルもそもそもはOSI参照モデルに対応している

・ロードバランサーは第4層や第7層（L4やL7と表現）
IPアドレスをもとに負荷分散させる場合はL4ロードバランサー
URLをもとに負荷分散させる場合はL7ロードバランサー


■MACアドレスとIPアドレス

・MACアドレスは48bit、IPアドレスは32bit

・MACアドレスは機器に割り当てられた番号、IPアドレスはインターネット上の住所

・MACアドレスは本当は可変、変えてはいけないけどソフトウェアを使って変えられる

・MACアドレスはNIC（Network Interface Card）に記載

・IPアドレス「127.0.0.1」は自分のIPアドレス。「localhost」と表現

・IPアドレス「0.0.0.0」はインターネットという意味
【これさえ読めばなんとなく違いがわかる】SpringBootの@〇〇Mappingとは？

## 今回比較する@〇〇Mapping

1.RequestMapping
2.PostMapping
3.GetMapping
4.DeleteMapping
5.PutMapping

## 使い分けるメリット

一目見てどんな処理をしているメソッドが判断できるため、可読性が上がる
例）Getであれば登録データの取得のメソッドetc... 

詳細は以下をチェック！

## 各@〇〇Mappingの使い方
### 1.RequestMapping
サイト全体の入り口部分。
例）Qiitaにアクセスする

### 2.PostMapping
新しいデータを登録する役割を果たす。（**INSERT処理**）
例）Qiitaにユーザー登録する／Qiitaに記事を投稿するetc...

### 3.GetMapping
登録されているデータを取得する役割を果たす。
例）Qiitaのトレンドから1件の記事を開くetc...

### 4.DeleteMapping
登録されている値を削除する役割を果たす。
例）Qiitaからユーザーを削除する／Qiitaへの投稿を削除するetc...

### 5.PutMapping
既存データの上書きをする役割を果たす。（**UPDATE処理**）
例）Qiitaの記事を修正する／Qiitaのユーザーデータを修正するetc...

## 参考

[https://dkssksk.com/springbootmapping/](https://dkssksk.com/springbootmapping/)
Apple PayのSuicaが突如「アップデート中」のまま使えなくなる不具合と格闘した

朝、改札を通ろうとするとiPhoneのSuicaがなぜか反応せず「ピンポーン」と止められてしまったので奮闘記を残す。

# TL;DR
おそらくSuicaアプリの更新に伴う不具合が原因。
一度、利用中のSuicaを削除し、再度同期することで復活するかも。

# 事象
エクスプレスカードに設定したSuicaが、改札で反応しない。
手動でWalletを起動すると、初期設定直後によく見る「カードをアップデート中」表示になっている。
Suica以外のカードも同様。
![1423D4B9-6E4B-47CE-BEC8-D0CB88D32800_1_201_a.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1633856/db9d0e6a-05a8-9ba9-c9f5-658306b6e475.jpeg)

# やってみたこと

OS再起動するも、効果なし。

ググると以下のAppleコミュニティ記事がヒット。
「私も困っています」数がめちゃ多く、未解決のままスレッド終了している模様。ヤバい。

https://discussionsjapan.apple.com/thread/251869491


試しにSuicaをApple Watchへ移動してみることに。
すると、Apple Watch側でSuicaのアクティベーションが終わらない模様。
ここでiPhone側のSuica以外のカードは復活。

ここでSuicaが原因なんじゃないかと感づく。
Watchへの移動失敗でSuicaは一度、両端末から削除された状態になったため、iPhoneのWalletアプリから改めてサーバー上のSuicaを同期して設定実施。
するとアクティベーション後、Suica復活！

# まとめ
Apple PayのSuicaはたまにこういう不具合ある気がする。
あと、いざ使えない時用に予備の物理ICカードやViewクレカなどは持ってた方がラクかも知れない。
（十年ぶり？に物理コインで切符なるものを買うハメになりました）
「オブジェクトは 'undefined' である可能性があります。」を解消する(初級) 

## ReactでTypeScriptを使用しているときに起きた初歩的なエラー

- タグのデータをステートに格納する処理

(どこに間違いがあるのかお分かりの方はこの記事をここで読み終えていただいて構いません。)

```
// ステート①を用意
const [tagsData, setTagsData] = useState<tag[]>();
// ステート②を用意
const [tags, setTags] = useState<tags>([]);

// idが当てはまったタグのデータをステート②にプッシュ
for (let tag of tags) {
    const tagsFilterByTag = tagsData.filter((tag: any) => tag.id === tag);
    tags.push(tagsFilterByTag[0]);
  }
```


下から3行目の`tagsData`で

```
const tagsData: tag[] | undefine
オブジェクトは 'undefined' である可能性があります。
```

と怒られる。

### なぜ...

コードを上から再確認すると

！！！！！！！

```
// ステート①を用意
const [tagsData, setTagsData] = useState<tag[]>();
```
### お分かりいただけただろうか。

useStateの引数に何も指定していないではないか。。

そこで

```
const [tagsData, setTagsData] = useState<tag[]>([]);
```

引数にArrayを入れておくとundefindではなくなった。
めちゃめちゃ初歩的なミスですが、今後もやらかしそうなので書いておきます。

ご意見、ご指導がございましたら順次ご対応させていただきます。

typescript初学者の参考になればと思います。
django.db.utils.OperationalError: FATAL:  ロール"<role_name>"は存在しませんと表示された時

# 目的
EC2インスタンスでDjangoのO/M, postgresql@10を使ってマイグレーションを行おうとしたところ、エラーが出たので備忘録。

```bash
python manage.py migrate
django.db.utils.OperationalError: FATAL:  ロール"app_admin"は存在しません
```
# 結論
存在していないと言われているロールをpostgresql@10シェル内で作成する。

# 問題
settings.pyのDATABASEの設定で指定している属性が存在していないこと。
私の場合は環境変数から読み込んでいるが、その環境変数に存在していないDB_NAME,DB_USER,DB_PASSWORDを指定していた。

```bash
DATABASES = {
    # PostgresSQL
    # get info from .env file
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': os.environ.get('DB_NAME'),
        'USER': os.environ.get('DB_USER'),
        'PASSWORD': os.environ.get('DB_PASSWORD'),
        'HOST': '',
        'PORT': ''
    }
}
```

# 具体的には
## デフォルトのpsqlというユーザー(ロール)でpostgresqlに接続する
```bash
$ sudo -u postgres psql
```

## ロールとデータベースをpostgresql@10シェル内で作成
**ロール作成時にpasswordをコンマで囲むことを忘れないように**

```bash
> CREATE ROLE <role_name> WITH LOGIN PASSWORD '<password>';
# 
> create database <database_name>;
> \q
```
## 環境変数を確認
環境変数の設定と先ほど設定したrole_name,password, database_nameが一致しているか確認し、変更がある場合は修正する

```bash
$ vi ~/.bash_profile
# ~/.bash_profileの修正を反映
$ source ~/.bash_profile
```

## もう一度マイグレーションしてみる
```bash
$ python manage.py migrate
Operations to perform:
  Apply all migrations: account, accounts, admin, auth, contenttypes, diary, django_ses, sessions, sites
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0001_initial... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying auth.0010_alter_group_name_max_length... OK
  Applying auth.0011_update_proxy_permissions... OK
  Applying accounts.0001_initial... OK
  Applying account.0001_initial... OK
  Applying account.0002_email_max_length... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying admin.0003_logentry_add_action_flag_choices... OK
  Applying diary.0001_initial... OK
  Applying django_ses.0001_initial... OK
  Applying sessions.0001_initial... OK
  Applying sites.0001_initial... OK
  Applying sites.0002_alter_domain_unique... OK
```

# 参考

https://qiita.com/wb773/items/248e6e083b2fe12e820a
ハッシュ値やランダムトークンが衝突する確率を求めるWebサービスを作った

# あらすじ
- ランダムトークンが衝突する確率について調べた
- 様々な条件で検証できるように、ハッシュ値（またはランダムトークン等）が衝突する確率を求めるWebサービスを作った



<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68927/303ae914-5152-dd47-1fda-f18666bbf729.gif" width="720" height="341" alt="動作キャプチャ">

# きっかけ
Webサービス等を開発していると、「ユーザーごとにユニークなトークンを発行したい」といったようなことが出てくるかと思います。
僕の場合は、

- ユーザーのIDを連番じゃなくてランダムなトークンにしたい
- ユーザーがパスワードリセットするときに、トークンを発行して認証したい

などの状況がありました。

データベース等を使っている場合は、カラムにユニーク制約を付けることで保障はできます。ですが、トークンが衝突してしまうと保存に失敗してしまうので、生成するときに衝突の確率は低くしたいです。
乱数とSHA-256等を使ってハッシュ値を生成するというのも考えられますが、SHA-256の場合だと64文字なので、URLとして表示するときに不格好だったり、レコード数が多い場合はその分容量が増えてしまうという問題があります。
トークンの長さを長くすれば衝突の確率が低くなるということはわかりますが、具体的にどのくらいの長さであれば安心できるのでしょうか。

# 誕生日のパラドックス
ある学校のクラスで30人生徒がいるとしたときに、その中で誕生日が同じ2人（以上）がいる確率はどのくらいでしょうか？

答えは約70%です。
誕生日は366通りあるので、30人程度なら同じ誕生日の人がいる確率はもっと低いと思ったかもしれません。
この結果が直感に反するので、[誕生日のパラドックス](https://ja.wikipedia.org/wiki/%E8%AA%95%E7%94%9F%E6%97%A5%E3%81%AE%E3%83%91%E3%83%A9%E3%83%89%E3%83%83%E3%82%AF%E3%82%B9)として知られています。

直感に反するということは、トークンの長さを決めるときに具体的な衝突確率を計算せずに決めるのは危険ということです。

# 様々な条件で検証できるようにWebサービスを作った
衝突確率を計算してみようと思ったときに調べてみたところ、計算方法は出てきますが、実際に数値を入れて計算してくれるというサービスはあまり存在しませんでした。無いのならば作ってしまえということで、作りました。

https://hash-web-e45df.web.app

具体的な計算方法は他に説明してくれるサイトがいくらでもあるので割愛します。

## ユーザーが100万人のときにユニークなトークンを発行する場合
実際の数値を入れて結果を見てみましょう。
ユーザーが100万人いて、ユーザーごとにユニークなトークンを1つ発行するとします。トークンはランダムな英数字としましょう。
そうすると、長さを10文字としたときに

> 組み合わせ数3656兆通りのハッシュ値を100万回生成したときに衝突が発生する確率は
> 7313分の1

ということで、10文字以上あれば衝突が発生する確率はそれなりに低いという事がわかります。ためしに生成回数を1000万回にすると、衝突が発生する確率は74分の1になります。それだと少し心配なので長さを12文字にすると確率は9.5万分の1ということで安心できそうだということがわかります。

# 余談 | 実装について
今回のサービスは勉強も兼ねてVue.js+Nuxt.jsで実装しました。Vue.jsを利用することで、フォームの入力からリアクティブに結果を表示するというようなアプリでも簡単に実装できて便利でした。このサービスは1ページだけなので恩恵は少ないかもしれませんが、Nuxt.jsのおかげで静的ファイルの生成までスムーズにできました。
ホスティングサービスはFirebaseを使っています。無料でここまでのことができるのはすごいですね。Firebase Realtime Databaseとかを使ったサービスも作っていきたいです。
衝突確率の計算は大きい数に対応するために近似を利用しています。なので小さい数だと誤差が大きいかもしれません。
実装のより細かい部分については、コメント等で要望があればまた別に書こうかなと思います。
バグ等の報告をしていただけると、僕が画面に向かって感謝の礼を10回ほど行いますのでよろしくお願いします。
2022/2/3主にITとかセキュリティの記事

「Emotet」に感染、関係者になりすましメールが送信 - 積水ハウス
https://www.security-next.com/133829

「IoTセキュリティ手引書」に改訂版 - 「NIST SP800-171」を反映
https://www.security-next.com/133831

macOS 12.2、バッテリー消耗で不具合の報告「一晩スリープでバッテリーが0%に」(1/31)
https://www.danshihack.com/2022/01/31/junp/macos-12-2-bug.html

地方公共団体に求められる セキュリティ対策と体制強化
https://www.projectdesign.jp/articles/42fea605-8be4-4e68-ab26-70cbbf611ef1

「Windows」のアップデート、適用には十分な接続時間が必要--MSが明らかに(2/1)
https://japan.zdnet.com/article/35182904/

Apple、macOSの脆弱性の報告者に約10万ドルの報奨金支払う(2/1)
https://news.mynavi.jp/techplus/article/20220201-2262439/

元・Java専門記者がLog4j 2脆弱性に見た「複雑性と魔神のかけら」　Javaの歴史とバザールの矛盾（1/6 ページ） - ITmedia NEWS(1/31)
https://newstopics.jp/url/19248157

Yahoo! JAPAN、欧州からの接続遮断へ　「法令順守の対応コスト面からサービス継続不能」(2/1)
https://www.itmedia.co.jp/news/spv/2202/01/news107.html

Windows版Excel、ユーザーからの要望に応えてある機能を高速化(2/1)
https://news.infoseek.co.jp/article/mynavi_2361969/?tpgnr=it

JVNVU#95979433
MIPS上で動作するOpenSSLにおけるBN_mod_exp()の誤った処理(1/31)
https://jvn.jp/vu/JVNVU95979433/

キャッシュレス決済悪用か　詐欺の疑いで男（34）再逮捕　パソコンなど145万円相当(1/31)
https://www.ctv.co.jp/news/articles/mrdsez8mxaq1uj79.html

Microsoft Listに小規模ビジネスや個人利用向けの簡易版。プレビューで提供中(2/1)
https://pc.watch.impress.co.jp/docs/news/1385137.html

Binance、ハッキング保護に10億ドルの安全資産基金を構築(2/1)
https://nextmoney.jp/?p=47846

自治体の情報セキュリティ対策で陥りやすい傾向と処方箋
https://www.projectdesign.jp/articles/a9107a4b-9abf-435a-bf25-31c00c54f54c

JVNVU#92602689
Samba vfs_fruitモジュールにおける拡張ファイル属性の安全でない処理による境界外読み書きの脆弱性(1/31)
https://jvn.jp/vu/JVNVU92602689/

誤変換、送信先ミス…やっちゃった！　メール誤送信に関する調査(1/31)
https://wotopi.jp/archives/124027

ネット詐欺撲滅へ、ヤフーが「サイバーセキュリティマンデー」実施(2/1)
https://www.tsuhannews.jp/shopblogs/detail/68291

「ヤマダポイントで購入」と偽る 現金詐取容疑で３人逮捕　県警　通販サイトに出店、自ら注文(2/1)
https://www.jomo-news.co.jp/articles/-/65480

「CASB」でシャドーITを撲滅(2/2)
https://xtech.nikkei.com/atcl/nxt/mag/nnw/18/092100119/011800005/

ファームウエア統一図るグーグル　ソフトウエア定義時代の終焉近し(2/2)
https://xtech.nikkei.com/atcl/nxt/mag/nc/18/052100111/012400068/

スマホユーザー襲う危ないSMS　スミッシングの新手口(2/2)
https://xtech.nikkei.com/atcl/nxt/mag/nc/18/052100113/012400074/

Pythonの波に飲まれたRuby　日本のケータイ敗戦との共通点(2/2)
https://xtech.nikkei.com/atcl/nxt/mag/nc/18/052100112/012400032/

不採算のブロードバンドに交付金　ユーザーの負担額はどうなる？(2/2)
https://xtech.nikkei.com/atcl/nxt/mag/nc/18/052100110/012100071/

無線LANの「Wi-Fi HaLow」や100km飛ぶ「ELTRES」、IoT無線のLPWAは多種多様(2/2)
https://xtech.nikkei.com/atcl/nxt/column/18/01930/012600003/

魅力に満ちた無法地帯メタバースの扱い、近未来社会の最大課題に(2/2)
https://xtech.nikkei.com/atcl/nxt/column/18/00065/00543/
【Firestore】collectionGroup の where で先祖ドキュメント（親ドキュメントなど）を指定する方法

以下のコードで ``a/b``ドキュメント以下の``collectionGroup``だけを取得できるみたいです。
（``a/b/c/d/${collectionGroup}``なども取得されるみたいです。）

```typescript
firestore
  .collectionGroup(collectionGroup)
  .where(FieldPath.documentId(), '>=', `a/b`)
  .where(FieldPath.documentId(), '<=', 'a/b0')
  .get();
```

# 調べた経緯

https://www.youtube.com/watch?v=bRhYq9fpDQk

この動画の 37:20 あたりの質問への回答として上記の方法が話されていました。

上記の方法を使ったコードがどこかに書いてあった、と話されていたので、調べた所、以下が見つかりました。

https://github.com/firebase/firebase-admin-node/issues/587#issuecomment-509316171

このコメントのリンク先が以下です。

https://github.com/googleapis/nodejs-firestore/blob/fc6f8392fd07ee93f5321be6808a78f7cdde3b2b/dev/system-test/firestore.ts#L1398

リンク先の関連部分のコードを抜粋します：

```typescript
const docPaths = [
  `a/a/${collectionGroup}/cg-doc1`,
  `a/b/a/b/${collectionGroup}/cg-doc2`,
  `a/b/${collectionGroup}/cg-doc3`,
  `a/b/c/d/${collectionGroup}/cg-doc4`,
  `a/c/${collectionGroup}/cg-doc5`,
  `${collectionGroup}/cg-doc6`,
  `a/b/nope/nope`,
];

// ...

let querySnapshot = await firestore
  .collectionGroup(collectionGroup)
  .where(FieldPath.documentId(), '>=', `a/b`)
  .where(FieldPath.documentId(), '<=', 'a/b0')
  .get();
expect(querySnapshot.docs.map(d => d.id)).to.deep.equal([
  'cg-doc2',
  'cg-doc3',
  'cg-doc4',
]);
```

---

余談：「collectionGroup 親ドキュメント 指定」でググったときに出て欲しかったので、タイトルに「親ドキュメント」って入れました。
ChromeDevToolsでスクリプトを終了させる隠しコマンド

## はじめに

Chrome DevToolsのブレークポイントで一時停止したとき、再開ボタンしかないと思っていませんでした？

![長押しアイコン](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311399/620aae06-fbc9-aac6-0fd8-aa280e765dee.jpeg)

スクリプト実行を一時停止や再開ではなく終了させる方法を紹介します。

## 終了させる方法

結論から言えば、再開ボタンを長押ししてください。スクリプト実行を一時停止すると再開ボタンが現れますが、この再開ボタンを**長押し**すると下側に終了ボタンが現れます。

**終了ボタン**を選べばスクリプト実行が終了します[^1]。

![スクリプト終了](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311399/4d2c56b4-d153-443c-90bd-c04976f093fe.jpeg)

終了ボタンはChrome 67（2018-05-29）からありました。[^2]

## 長押しアイコンの特徴

![長押しアイコン](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311399/620aae06-fbc9-aac6-0fd8-aa280e765dee.jpeg)

気づきにくいですが、長押しできるアイコンに特徴がありそうです[^3]。アイコンの右下の三角形です。

## さいごに

いかがでしたか？

ボタンの長押しは隠しコマンドのようですよね。

この情報が役に立つと思われた方はLGTMを押してくださると嬉しいです！ 

[^1]: JavaScriptのイベントループにおいて実行中だったタスクが終了するようです。
[^2]: [What's New In DevTools (Chrome 67) - Chrome Developers # Stop infinite loops](https://developer.chrome.com/blog/new-in-devtools-67/#stop)
[^3]: 他の長押しアイコンを見たことがなく、Chromeのなかで共通する特徴かは分からない。
muninのdynazoomのグラフが表示されないのを解決した

ネット上に同じ症状になってる記事がちらほら見えたけど、まぁ他にもこういうパターンがあるかもしれないから書き残しておきます。

#環境について
サーバOSはCentOS7、WebサーバはApacheになります。また、Apacheにはバーチャルホスト設定をしています。

#調査開始
まずApacheのエラーを確認するためにtailコマンドでどういうエラーが出ているのかを確認する

```
tail -f /var/log/httpd/error_log
```

この状態で実際にmuninにアクセスして画像クリックをして詳細ログを確認しようとする

最初に出ていたエラー文

```
 script not found or unable to stat: /var/www/cgi-bin/cgi, referer:～～～～（省略）
```

このあたりはcgiファイルが無いよ～的な内容っぽかったので、/var/www/cgi-bin/配下にmuninにあるcgiファイルをコピーしました。
#ファイルのコピー

```
cp -r /var/www/html/(各環境のパス)/munin/cgi /var/www/cgi-bin/
```

#再確認
再びアクセスを試みると、状況は変わりませんがログの内容が変化していたので確認する

```
munin-cgi-graph: Can't open /var/log/munin/munin-cgi-graph.log (Permission denied) ～～～～
```

こちらはパーミッションがおかしいみたいなので設定を確認する

#設定確認、変更

```
ll /var/log/munin
```

確認すると、munin-cgi-graph.logとmunin-cgi-html.logの所有者がmuninになっていたのでApacheに変更

```
 chown apache. munin-cgi-graph.log
 chown apache. munin-cgi-html.log
```

再びmuninにアクセスして確認するも、状況もログも変わらずおかしいなあ、と思ってディレクトリを確認してみる

```
ll /var/log/ | grep munin
```

muninディレクトリの所有者がmuninになっているのでApacheに変更

```
chown apache munin
```

この状態でmuninへアクセスすると無事グラフが表示されました！

#まとめ
今回の環境で勉強のためにバーチャルホスト設定を初めて行ったのと、リソースがカツカツなサーバの監視をするのにzabbixでは厳しいということでmuninを選んでみましたが、設定箇所があちこちにあるため特定に非常に時間がかかったと思います。
直接原因はディレクトリのパーミッションという初歩的な内容かもしれませんが、それ故に引っかかるポイントかと思って記事にいたしました。

また、参考にしたWebサイト様には大変感謝しております。ありがとうございました。

### 参考サイト様
https://rriifftt.hatenablog.com/entry/2015/03/23/155155

https://cod-sushi.com/munin-nginx-graph/
【TypeScript】引数の型定義について【React】

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

## 引数の型定義について

### 引数の型定義
ReactにおいてTypeScriptを用い、引数の型を指定するには以下のように記述します。


```Sample.tsx
import React from 'react';

export const Sample = () => {
  // 引数の後ろに`: number`とすることで型を指定
  const calcTotalFee = (num: number) => {
    const total = num * 1.1;
    console.log(total);
    // 実行ボタンをクリックするとコンソールに1100と出力される
  }

  // 以下の記述で数値以外を渡すようにするとエラーとなる
  const onClickSample = () => calcTotalFee(1000);
  return (
    <>
      <p>引数の型定義<p>
      <buton onClick={onClickSample}>実行</button>
    </>
  );
} 
```


```App.tsx
import React from 'react';
import { Sample } from './Sample';
 
export default fanction App() {
  return (
      <Sample />
  );
} 
```

上記の例では、画面に表示された「実行」ボタンをクリックするとコンソールに`1100`が表示されるような記述となっております。

Sample.tsxの`const calcTotalFee = (num: number)`で引数の型を`number`と指定しているため、`const onClickSample = () => calcTotalFee(1000);`に数値以外を渡すとエラーが発生します。

尚、`const calcTotalFee = (num: number)`で引数の型を指定しない場合には、`const onClickSample = () => calcTotalFee(1000);`に数値以外を渡してもJavaScriptが型を推定するため、エラーにはなりません。例えば、文字列を渡した場合でも`const total = num * 1.1;`で計算をしていることから数値に変換され、エラーにはならず、コンソールに`1100`が出力されます。

型を定義することで、定義した型以外の値が渡された場合に、実行する前にエラーが確認でき、画面で動かした際にエラーとなることを防ぐことができます。
かぐーる（Windows10、Python3.9）

# はじめに
クジラ・イルカの識別をやっていこうかなと。

<!---
Google Colabratory上ではデータセットダウンロード時にToo Many Requestsが出てしまったので、パソコン版GoogleDriveを使用するメモ。
--->

https://www.kaggle.com/c/happy-whale-and-dolphin/overview

# 開発環境
- Windows 10 PC
- Python 3.9
- Google Drive (2TB)
- Google Colaboratory

<!---
- パソコン版GoogleDrive
--->

# 実装
1．Google Drive上にKaggle/happy-whale-and-dolphinフォルダを作成しておき、マウントしてカレントディレクトリを移動する

~~~py
import os
from google.colab import drive
drive.mount('/content/drive')
os.chdir("/content/drive/MyDrive/Kaggle/happy-whale-and-dolphin")
~~~

2．KaggleのサイトからAccountページのCreate New API Tokenをクリックし、kaggle.jsonをダウンロードする

3．ダウンロードしたkaggle.jsonをGoogle Driveに置いておき、環境変数の設定をする

~~~py
import json
f = open("/content/drive/MyDrive/kaggle.json", 'r')
json_data = json.load(f) 
os.environ['KAGGLE_USERNAME'] = json_data['username']
os.environ['KAGGLE_KEY'] = json_data['key']
~~~

※ファイル選択する方法もある

~~~py
from google.colab import files

uploaded = files.upload()

for fn in uploaded.keys():
  print('User uploaded file "{name}" with length {length} bytes'.format(
      name=fn, length=len(uploaded[fn])))

!mkdir -p ~/.kaggle/ && mv kaggle.json ~/.kaggle/ && chmod 600 ~/.kaggle/kaggle.json
~~~

4．kaggleをインストールして、アップグレード

~~~
!pip install kaggle
!pip install --upgrade --force-reinstall --no-deps kaggle
~~~

※下記のアップグレードだと、Warning: Looks like you're using an outdated API Version, please consider updating (server 1.5.12 / client 1.5.4)が出て、うまくデータセットがダウンロードできなかった

~~~
!pip install kaggle --upgrade
~~~

5．データセットをダウンロード

~~~
!kaggle competitions download -c happy-whale-and-dolphin
!unzip happy-whale-and-dolphin.zip
~~~

結局、Colaboratoryのディスク分しかダウンロードできなそう。。。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63863/fafff767-196e-687f-bcee-2d488cab14a0.png)

お疲れ様でした。

# パソコン版GoogleDriveを使う（失敗）
1．パソコン版GoogleDriveを導入する

https://support.google.com/a/answer/7491144?hl=ja

2．anaconda promptでkaggle apiをインストール

~~~
pip install kaggle
~~~

3．Kaggle -> Account -> Create New API Token 

ダウンロードしたkaggle.jsonをC:\Users\<ユーザー名>\.kaggle/の中に置く

~~~
(py39) G:\マイドライブ\Happywhale>kaggle competitions download -c happy-whale-and-dolphin
Traceback (most recent call last):
  File "c:\users\good_\anaconda3\lib\runpy.py", line 194, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "c:\users\good_\anaconda3\lib\runpy.py", line 87, in _run_code
    exec(code, run_globals)
  File "C:\Users\good_\anaconda3\Scripts\kaggle.exe\__main__.py", line 4, in <module>
  File "c:\users\good_\anaconda3\lib\site-packages\kaggle\__init__.py", line 23, in <module>
    api.authenticate()
  File "c:\users\good_\anaconda3\lib\site-packages\kaggle\api\kaggle_api_extended.py", line 164, in authenticate
    raise IOError('Could not find {}. Make sure it\'s located in'
OSError: Could not find kaggle.json. Make sure it's located in C:\Users\good_\.kaggle. Or use the environment method.
~~~

4．データセットをGドライブにダウンロードする

~~~
kaggle competitions download -c happy-whale-and-dolphin
~~~

ダウンロード中。
結局、ローカルドライブの空き容量分しか使えず。。。
js-cookieのパッケージをバージョンアップ(v3)したらgetJSON()が使えなくなっていた

v3から削除された見たいです。

https://github.com/js-cookie/js-cookie/releases/tag/v3.0.0

> Removed built-in JSON support, i.e. getJSON() and automatic stringifying in set(): use Cookies.set('foo', JSON.stringify({ ... })) and JSON.parse(Cookies.get('foo')) instead.

組み込みオブジェクトメソッドを使いましょうとのことです。

```before.ts
import { set, getJSON } from "js-cookie";

// save json to cookie
const data = { /* なにかオブジェクトを */ };
set(JSON.stringify('some-key', data));

// load json to cookie
const dataFromCookie = getJSON(key);
```

```after.ts
import { set, get } from "js-cookie";

// save json to cookie
const data = { /* なにかオブジェクトを */ };
set(JSON.stringify('some-key', data));

// load json to cookie
const dataFromCookie = JSON.parse(get(key));
```
npm ERR! code E404からやっと抜け出せた；；

<h2 id="part-fe71e5fe0d9f5840">ハマりにハマったnpm install</h2>

<p>npm install でずっと「npm ERR! code E404」から抜け出せずにいたが半日後にやっと解決・・・</p>

<p>プログラミングを学び始めた自分にとってかなり辛かった・・・</p>

<h2 id="part-3df5ebd1290a93bf">npm ERR! code E404</h2>

<pre><code>$ npm install --save-dev typescript ts-loader webpack webpack-cli webpack-dev-server

npm ERR! code E404
npm ERR! 404 Not Found - GET http://registry.npmjs.org/typescript
npm ERR! 404 
npm ERR! 404  'typescript@latest' is not in the npm registry.
npm ERR! 404 You should bug the author to publish it (or use the name yourself!)
npm ERR! 404 
npm ERR! 404 Note that you can also install from a
npm ERR! 404 tarball, folder, http url, or git url.

npm ERR! A complete log of this run can be found in:
npm ERR!     /Users/username/.npm/_logs/2022-02-02T15_55_11_968Z-debug.log
</code></pre></p>



<h4>解決策</h4>

<pre><code>$　rm /home/colleagueName/.npmrc
</code></pre></p>

[参考URL](https://rapaccinim.medium.com/surviving-the-npm-err-404-with-private-packages-b413d80fb860)

<p>自分はこの方法でやったらインストールできるようになりました！</p>


<p>どうやら.npmrcファイルを削除すればよかったらしいです。</p>

<p>なんかいじっちゃったんですかね？真相は分かりません・・・</p>
【解決】Pycharmのvenvでbad interpreter: No such file or directory と表示される。

## 結論
xcode-select --install

## 概要
PyCharmのvenv環境でjupyter notebookを起動しようとしたところ`bad interpreter: No such file or directory`と表示されjupyter notebookが起動されない。
原因を探るため、PyCharmでinterpreterとして設定していたPythonのpathを指定して-Vしてみると

```
$ /usr/bin/python3 -V
xcrun: error: invalid active developer path (/Library/Developer/CommandLineTools), missing xcrun at: /Library/Developer/CommandLineTools/usr/bin/xcrun
```

と出てバージョンが表示されない。
なぜだ…と思いエラーメッセージでググると、この現象はMacOSのメジャーアップデート後によく起きがちなことらしく、`xcode-select --install`を実行すれば解決するそうな。

確かに最近Montereyにアップデートしたばかりだ…
ということで実際にやってみた。

```
$ xcode-select --install
xcode-select: note: install requested for command line developer tools
$ /usr/bin/python3 -V
Python 3.8.9
```

PyCharmでinterpreterとして設定していたPythonはバージョン表示されるようになった。
PyCharmでもvenvがつくられるようになった。
めでたしめでたし。