OmniAICreator/Qiita-1.07M huggingface dataset:
更に pagepank で じゃんけん と トランプ と 軍人将棋 をやってみた

# はじめに
https://qiita.com/superrino130/items/42ac760b0f202cefe6bc

前回の記事を投稿後、Ruby版networkxに携わることがあったので、更にやってみた。
# じゃんけん
```ruby
require_relative 'lib/networkX'

h = {
  1 => [2],
  2 => [3],
  3 => [1]
}
g = NetworkX::DiGraph.new
h.each do |k, v|
  v.each do |ve|
    g.add_edge(ve - 1, k - 1)
  end
end
puts NetworkX.pagerank(g)

# output
0.3333333
0.3333333
0.3333333
```
https://ja.wikipedia.org/wiki/%E3%81%98%E3%82%83%E3%82%93%E3%81%91%E3%82%93

じゃんけんは三つ巴ですので、同じ得点になります。
# トランプ
```ruby
h = {
  1 => [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13],
  2 => [1],
  3 => [2],
  4 => [2, 3],
  5 => [2, 3, 4],
  6 => [2, 3, 4, 5],
  7 => [2, 3, 4, 5, 6],
  8 => [2, 3, 4, 5, 6, 7],
  9 => [2, 3, 4, 5, 6, 7, 8],
  10 => [2, 3, 4, 5, 6, 7, 8, 9],
  11 => [2, 3, 4, 5, 6, 7, 8, 9, 10],
  12 => [2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
  13 => [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
}
```
要素`h`のみ表示しております。

|index|絵柄|得点|
|--:|--:|:--:|
|1|A|0.2160026|
|2|2|0.0769230|
|3|3|0.0222672|
|4|4|0.0389576|
|5|5|0.0408504|
|6|6|0.0445303|
|7|7|0.0488713|
|8|8|0.0542792|
|9|9|0.0612033|
|10|10|0.0704314|
|11|J|0.0834375|
|12|Q|0.1033930|
|13|K|0.1388527|

トランプのルールとして、数の大きい方が勝ちとしますが、`A`のみ`2`を除くすべての数に勝ちます。
すると、`A`の得点が一番大きいのですが、`2`の得点が`10`より大きく算出されています。
# 軍人将棋
```ruby
h = {
  1 => [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15],
  2 => [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
  3 => [4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
  4 => [5, 6, 7, 8, 9, 12, 13, 14, 15],
  5 => [6, 7, 8, 9, 12, 13, 14, 15],
  6 => [7, 8, 9, 12, 13, 14, 15],
  7 => [8, 9, 12, 13, 14, 15],
  8 => [9, 12, 13, 14, 15],
  9 => [12, 13, 14, 15],
  10 => [4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15],
  11 => [4, 5, 6, 7, 8, 9, 12, 14, 15],
  12 => [13, 14, 15],
  13 => [11, 14, 15],
  14 => [1, 15],
  15 => [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 14]
}
```
|index|絵柄|得点|
|--:|--:|:--:|
|1|大将|0.1585483|
|2|中将|0.1036655|
|3|少将|0.0807769|
|4|大佐|0.0485714|
|5|中佐|0.0433120|
|6|少佐|0.0391522|
|7|大尉|0.0357737|
|8|中尉|0.0329713|
|9|少尉|0.0306064|
|10|飛行機|0.0629422|
|11|戦車|0.0528652|
|12|騎兵|0.0285820|
|13|工兵|0.0334844|
|14|スパイ|0.0575546|
|15|地雷|0.1911934|

https://ja.wikipedia.org/wiki/%E8%BB%8D%E4%BA%BA%E5%B0%86%E6%A3%8B#%E5%8B%9D%E6%95%97%E8%A1%A8

軍人将棋に馴染みのない方もいらっしゃると思いますが、引き分けを双方向のエッジとしています。
予想通り、`飛行機`や`スパイ`の得点が高いです。

勿論、ここでは移動能力（地雷は動かない、飛行機は飛び越せる等）を評価していないです。

後、一般的な将棋の場合、手番のある方が勝つ（駒をとれる）ので、`pagerank`の手法ですと得点がすべて同じになるため、別の手法が必要になると思われます。
# まとめ
+ pegerank に更に詳しくなった
ＡＩと言うもの、ＡＩ人材と言うものを、いかにわかりやすく伝えるか（自論）

　AIについて、まったく知見のないマネージャークラスに対して、ＡＩと言うもの、ＡＩ人材と言うものを、いかにわかりやすく伝えるかと言うのが本テーマである。

# AIのニュース
　話の流れとしては、最新の話題からインパクトを伝えて、まずは興味の矛先を向けることが大事である。このテーマは、いろいろ選べるが、まずは個人的に自分自身が、インパクトを持って伝えたいことを言う方が良い。

## AIが“描いた”作品、約4800万円で落札。予想落札価格の43倍
https://bijutsutecho.com/magazine/news/market/18719
　この話題を選んだのは、最新のＡＩ革命であるGAN（Genative Adversireal Network　敵対性ネットワーク）の話題からインパクトを伝えたい（この時点で、アルファ碁を選んだ時点で、あなたのＡＩ知識は陳腐化している）
　このニュースは、ＡＩが初めて人間を超える創造性を発揮した事例であるが、ここでもっと伝えたいことは、この絵の中に通常は画家のサインが入るべきところに、謎の数式が入っていることだ。この時点で、そもそもＡＩとは、いったいどんな企業なのか、どんなエンジニアなのか？　はたまたどんな高級スペックのＰＣなのか。。。など。そもそもＡＩって、何なのかを考えるのに、十分すぎる説明となっている。つまり、ＡＩとは言うのは、突き詰めて言えば、数学なのである。

## AIを開発した人物
　歴史を紐解けば、その時々で革命が起こっているが、やはり最近のハイテク企業を支えているのは、ごくごく一部の天才だと言うところに落ち着く。主要な人物を挙げておくと、いかに人材の確保が大事かと言うことが伝えられて、大切にされるかも・・・

　最近の話題は、イアングッドフェロウさんかなあ・・・
　OPENAI　→　Google →　Apple　すごいキャリアだわ　https://japan.cnet.com/article/35135310/

　ちょっと前ならAlexNetのアレックス・クリジェフスキーさん　→　この人もGoogle
　　https://xtech.nikkei.com/it/atcl/column/16/111800262/112100004/
　
　ＪＤＬＡを設立された松尾さんも挙げとくか　→　一応、Ｇ検定持っているので
　https://ledge.ai/jdla-ai-for-everyone/
　
## 特許の話へ
　企業のマネージャは、まず理系の話をしても通じないことが多い。なので切り口を変えて、ＡＩ→研究者の取り合い→特許の取り合いという切り口で話した方がいい。その方がエンジニアの得意領域とバッティングしないため。この中にいろいろＡＩのポイントを入れてあげると、ちょっと知ったような気になって、きっと喜ぶはず（あくまで自分の所属の場合）

 バッチノーマライゼーション  　https://www.slideshare.net/YosukeShinya/ss-125937523

 ノーマライズって何と言うところは、懇切に教えてあげた方がいい。つまり評価関数の変化がないと、結局、ＡＩは勉強できないと言うこと。ここ結構大事。あんまり、話すとドツボにハマるかもしれないので、ここは話題は少なめにして良し

https://knpt.com/contents/ai/2019.12.10.pdf

## AIエンジニアというもの
　ここからが、自分の立ち位置を明確にしていくプロセスに入る。

以下は否定すべきこと
* ＡＩエンジニアは、便利屋さんではないことを主張
* マジシャンでもないことを主張
* Pythonプログラマでもないことを主張

以下は主張すべきこと
* ＡＩエンジニアは、データアナリストと言うことを主張
* 統計解析エンジニアであることを主張
* 「その気になれば、簡単な問題はEXCELで解決できますよ」と、すこし脅してみる　
→　いやあ、そういわないでＡＩでやってよ～と、外面を気にして降りてくれば、この交渉は終了する
知財戦略

知財活動統合システム

情報共有システム　事業計画、テーマレビューなど共有データ
特許解析システム　patentsight　パテントマップ、テキストマイニング、特許定量評価（パテントスコア）→　研究、事業スタッフへ開放
特許検索システム　mapios　　国内、海外検索
特許管理システム　palcom　　出願、権利、維持、権利消失に至るまでの情報管理、他社の問題特許の把握

知財レビュのフォーマット統一　→　事業部間比較、経営戦略上の活用


研究開発戦略
・自社の技術力分析、研究開発テーマ設定
・自社技術の標準化を目指す、戦略的な権利取得
・共同研究パートナー選定の判断材料として、知財情報を活用

知財戦略
・事前調査　他社動向調査（パテントマップ作成）


特許戦略策定に必要な情報

自社
研究計画、特許性に関係（影響）する公知技術、実施発明（物，用法，製法）
開発計画（発売時期、発売国）
委託計画、内容（製造販売）
開発に影響する他社特許（出願
関連する契約

他社
製品開発予定
学会文献発表
特許契約
製品
紛争係争

その他



U'Method | アイピーファイン株式会社のブログ - Ameba

知的財産部門と開発部門の戦略的協働 キヤノンの事例から 

YKS手法では特許群が稼ぐ力を評価し、PQ手法では特許権の金銭的価値を評価 ...

ISIPU 知財活用戦略研究所
アレンジカード(100枚) - ISIPU 知財活用戦略研究所
オリジナルのメソッド診断の進め方 - ISIPU 知財活用戦略研究所

戦略的データベース（SDB)の構築
特許情報のQFDへの活用

パテント・ポートフォリオの構築方法（その7） - ものづくり 




－－－－－－－－－
技術を生かすビジネスモデル：技術経営（MOT）の基礎知識　経営のフレームワーク

イノベーション
・他社に製品で差別化する「プロダクト・イノベーション」
・開発・製造・物流などで他社が真似できない革新的なプロセスを実現する「プロセス・イノベーション」


知的財産マネジメントとは？　秘匿　公開　知財出願権利化
知財ロードマップ　IRM
新規事業を目指す事業戦略マップ (TIG法) による可視化と評価への活用事例
＝＝＝＝＝＝＝＝＝＝＝＝
パテントマップEXZ とは | インパテック株式会社
パテントマップ作成ソフト Pat Easy Z（P.E.Z）の概要
＝＝＝＝＝＝＝＝＝＝＝＝

野崎
https://monoist.itmedia.co.jp/mn/articles/1006/04/news101_3.html

電中研、知的財産報告書を公開　知的財産の「アウトカム」をでき ... 環境ベースマップ作成支援GISツール.



EDINET
J-GLOBAL

戦略データ・ベース（SDB）の活用と特許マップ. 旭化成

『技術者・研究者』と『特許・知財部門』との連携の取り方と知財人材の育成・強化
～部門間の”溝”をどのように埋め、実務と戦略を進めるか
https://www.gijutu.co.jp/weblibraryadv/webb_1425.htm
ＡＩ勉強になりそうなリンク（自分用）

https://www.slideshare.net/xavigiro/generative-models-and-adversarial-training-d2l3-insightdcu-machine-learning-workshop-2017?next_slideshow=75496658

https://qiita.com/typecprint/items/035478748a811dd29721
ターミナルでRailsを起動するとPostgreSQLが突然エラーになった。

#エラー内容
~~~
ActiveRecord::ConnectionNotEstablished (could not connect to server: No such file or directory
	Is the server running locally and accepting
	connections on Unix domain socket "/tmp/.s.PGSQL.5432"?
~~~

##結論、私の場合、下記で解決しました。
postmaster.pidの削除。

~~~
rm /usr/local/var/postgres/postmaster.pid
~~~

#原因

PostgreSQLを停止すると削除されるが、macOSが正常に終了しなかった場合、プロセスID(PID)が残ったままでエラーになる場合があるとのこと。確かにターミナルをちゃんと終了せずにMacを閉じた覚えがあります。

#その他やったこと
PostgreSQLの起動、停止、リスタート
上記をすると解決した記事を見て、最初にやってみました。
ですが効果がなかったので、pidを削除しました。
削除してから起動、停止、リスタートを試すと正常に起動した経緯です。
[JavaScript]正しい配列のコピー

## 挨拶
Web系エンジニアへの就職に向け学習をしております、ひろやすと申します。
今回の記事は、JavaScriptの復習をしていて配列の正しいコピーの仕方を知ったので、学習のアウトプットとして投稿しようと思いました。
配列のコピーの仕方を知らない方の参考になれば幸いです。

## 正しい配列のコピー

```javascript
const array = ["ねずみ", "うし", "とら"];

// スプレッド構文を利用してカッコの中で配列の要素を取り出している
const arrayCopy = [...array];
```

## 間違った配列のコピー

```javascript
const array = ["ねずみ", "うし", "とら"];
// arrayを参照している
const arrayCopy = array;

// arrayの値が変更するとarrayCopyの値も変わってしまう
array[0] = "いぬ"

console.log(arrayCopy);
// ["いぬ", "うし", "とら"]
```

## 筆者の悪い例
最後に、正しい配列のコピーの仕方を知らなかった以前の私が書いていたコードを載せておきます。
多分、こんなにまわりくどいコードを書く人はいないと思うけど...笑
```javascript
const array = ["ねずみ", "うし", "とら"];

const data = [];

// forEach文で要素を一つずつ配列に挿入している
array.forEach((a) => {
  data.push(a);
};
```

最後までお読み頂きましてありがとうございます。
obnizでTypeScriptでオムロン環境センサのデータを取得してみる

# はじめに
ちょっと最近何年ぶりかに[obniz](https://obniz.com/ja/)に触れる機会があった。気づいてみればOSバージョンは3.xになっていて発売当初に買ったデバイスはアップデートしないとほぼ役に立たない感じになっていた。ただアップデートしてみると[node.jsで動く](https://www.npmjs.com/package/obniz)わTypeScriptをサポートしてるわで使いやすい環境になっている

[無線デバイスも多くサポート](https://obniz.com/ja/doc/reference/obniz-ble-wi-fi-gateway/ble-devices)されているのでBLE系デバイスを実験するのも悪くなさげ。ということでリハビリがてら[2JCIE-BU](https://components.omron.com/jp-ja/products/sensors/2JCIE-BU)からデータを取ってみることにした。当然TypeScriptで！

# やってみた

まず2JCIE-BUに電源が入っていてデータが飛んでいることが前提。obnizはWiFiにつないでObniz ID表示状態にしておく

以下で開発環境を整えてエディタを開く

```shell
mkdir omron_2jcie && cd $_
yarn init
yarn add obniz
yarn add -D typescript ts-node @types/node
yarn run tsc --init
mkdir src
vi src/index.ts
```

コードは以下。`[OBNIZ ID]`はobnizに表示されているIDに読み替える

```index.ts
import Obniz from "obniz";

const OBNIZ_ID = '[OBNIZ ID]';

const obniz = new Obniz(OBNIZ_ID);
obniz.onconnect = async () => {
  if (!obniz.ble) return false;
  const Device = Obniz.getPartsClass('2JCIE');
  await obniz.ble.initWait();
  obniz.ble.scan.onfind = (p) => {
    if (Device.isDevice(p)) {
      const data = Device.getData(p);
      if (data) {
        if (typeof data === 'object') {
          const envs = [];
          for (const key in data) envs.push(`${key}: ${data[key]}`);
          const ret = envs.join("\n");
          console.log(ret);
          obniz.display?.clear();
          obniz.display?.print(envs[0]);
        }
      }
    }
  };
  await obniz.ble.scan.startWait();
};
```

データはJSONで返ってくるので適当にパースして`console.log()`で表示した。obniz側では多すぎて見切れるので一番最初の温度のみ表示した

実行する

```shell
yarn run ts-node src/index.ts

yarn run v1.22.17
$ /Users/user/work/omron_2jcie/node_modules/.bin/ts-node src/index.ts
temperature: 20.48
relative_humidity: 38.54
light: 230
barometric_pressure: 1013.6
sound_noise: 44.62
etvoc: 843
eco2: 2060
```

![PXL_20220214_021929105.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61995/4b9b4b35-f765-a1d9-f09a-c3aac1f289ee.jpeg)

環境データとれた。obniz端末のほうも温度が表示された。

# パーツの型まわりが微妙だった件

コード書いてて気になったことが一点。`Obniz.getPartsClass()`で取得した2JCIEクラスインスタンスの型付がどうしてもうまくいかなかかった

```index.ts
const Device = Obniz.getPartsClass('2JCIE');
```

Deviceは`any`となるので以降のコード補完がなくて不便。[パーツのソースコード](https://github.com/obniz/obniz/blob/45c22403fbef85e18d59325505e0da52728a9904/src/parts/Ble/2jcie/index.ts#L332{)を見る限り`OMRON_2JCIE`でいいのかもと

```index.ts
const Device: OMRON_2JCIE = Obniz.getPartsClass('2JCIE');
```

としてみたがダメだった。定義されてないか。どうやるのが正しいんだろ？わかるひといらっしゃったら教えてください

どうやったらいいんだろとちょっと探して[getPartsClass()の中身](https://github.com/obniz/obniz/blob/75b1a277c539fb4d47dcfbfd9a28b3c640c8bb2e/src/obniz/ObnizParts.ts#L62)を見てみたら固定で`any`を返すようになっていた。これはパーツの型を返すようにしてほしいなあ


# おわりに

発売当初はサイロ感が強くてあまりいい印象を持てなかったobnizだけどなんか自由感が増して楽しくなった感があります。TSまわりもだんだん強化されていくことを期待してちょいちょい使ってみたいと思いました^^
無線LAN


|規格|周波数帯|最大通信速度|呼称名|
|:---|:------|:----------|:----|
|IEEE802.11b|2.4GHz|11Mbps||
|IEEE802.11g|2.4GHz|54Mbps||
|IEEE802.11a|5GHz|54Mbps||
|IEEE802.11n|2.4GHz|600Mbps|Wi-Fi4|
|IEEE802.11n|5GHz|600Mbps|Wi-Fi4|
|IEEE802.11ac|5GHz|6.9Gbps|Wi-Fi5|
|IEEE802.11ad（WiGig:ワイギグ）|60GHz|6.8Gbps||
|IEEE802.11ax|2.4GHz|9.6Gbps|Wi-Fi6|
|IEEE802.11ax|5GHz|9.6Gbps|Wi-Fi6|


**無線LANにおけるデータの暗号及びセキュリティ方式**

|用語|説明    |セキュリティ強度|
|:---|:------|:--------------|
|WEP:Wired Equivalent Privacy|RC4と呼ばれる暗号化アルゴリズムを元にした共有鍵暗号方式で、IEEE 802.11で採用された。秘密鍵には40bitまたは128bitのデータを使用する。|△|
|WPA:Wi-Fi Protected Access|Wi-Fi Allianceが2002年に制定したセキュリティシステムで、暗号化と認証の組み合わせ。暗号化プロトコルにはTKIPを使用。エンタープライズ(EAPを利用したID,パスワード認証を使用)、パーソナル（PSK ”Pre Shared Key, 事前共有鍵”による暗号化方式を使用)の2種類がある。|△|
WPA2:Wi-Fi Protected Access2|Wi-Fi Allianceが2004年に制定したセキュリティシステム。AES暗号に対応し、WPAより堅牢なセキュリティ方式。WPAと同様にエンタープライズ及びパーソナルの2種類がある。|○|
|WPA3:Wi-Fi Protected Access3|Wi-Fi Allianceが2018年に制定したセキュリティーシステム。「KRACKs」と呼ばれる脆弱性を無効にするSAE（Simultaneous Authentication of Equals、同等性同時認証）ハンドシェイクによる防護や辞書攻撃・総当たり攻撃からの防護を実現したWPA2よりも強固なセキュリティー方式。WPA、WPA2と同様にエンタープライズ及びパーソナルの2種類がある。|◎|
|IEEE802.11i|IEEEが規定する無線LANにおけるセキュリティに関する規格。WPAは標準化が遅れた802.11iに先行してWi-Fi Allianceが策定した基準であり、その後標準化された802.11iの必須機能をサポートしたWPA2が策定された。|○|
|TKIP:Temporal Key Integrity Protocol|パケット毎に暗号鍵を自動生成する暗号化プロトコル|△|
|AES:Advanced Encryption Standard|米商務省標準技術局(NIST)によって2001年に米国政府の標準暗号化技術として認定された方式。|◎|
|CNSA:Commercial National Security Algorithm|米NSA（National Security Agency：国家安全保障局）が定めた暗号スイート。WPA3エンタープライズで採用されている。|

**アクセス制御の方法**

|用語|説明    |セキュリティ強度|
|:---|:------|:--------------|
|SSID:Service Set ID（ESSID (Extended SSID)もほぼ同義）|無線LANにおけるアクセスポイントの識別子(32文字)、グループ名。|△|
|ANY接続拒否機能|SSIDが空白または”any”が設定されているクライアントからの接続要求を拒否する機能|△|
|SSID隠蔽機能|ビーコン信号にSSIDを含めない機能|△|
|MACアドレスフィルタリング機能|送信元MACアドレスによる、アクセスポイントに対するクライアントのアクセスを制限する機能|△
|IEEE 802.1x認証|RADIUSサーバーによるクライアント認証機能|◎|
|認証･検疫システム|ネットワークに設定したセキュリティポリシーにより、クライアントの隔離／治療を行うシステム|◎|
条件分岐について

# 条件分岐
条件分岐とは、条件に応じてプログラムの流れを切り替える仕組みのことです。


## if文
if文は条件や値によって処理を分岐させ、一致する場合の処理と、一致しない場合の異なる処理を記述するための方法です。条件が一致する場合を「真（true）」、条件が一致しないことを「偽（false）」と言います。

```sample1.js
const num = 70;
if (num > 80) {
    console.log("numは80より大きいです。");
}else{
    console.log("numは80以下です。");
}
//=> "numは80以下です。"
```

## else if
記述した条件が満たされた時に実行されます。

```sample2.js
const num = 70;
if (num > 80) {
    console.log("numは80より大きいです。");
} else if (num >= 60) {
    console.log("numは60～80の間です。");  
} else {
    console.log("numは60未満です。");
}
// => "numは60~80の間です。"
```
numの値が条件よりも満たしていなかったら。

```sample3.js
const num = 30;
if (num > 80) {
    console.log("numは80より大きいです。");
} else if (num >= 60) {
    console.log("numは60～80の間です。");  
} else {
    console.log("numは60未満です。");
}
// => "numは60未満です。"
```
どちらも満たされない場合は以下が実行されます。


## ==　と　=== の違いについて

```sample.js
const a = "1";
const b = 1;
console.log(a==b);
console.log(a===b);
// => true
// => false
```
等値演算子の評価結果は常に、比較が真かどうかに基づいて Boolean 型の値になります。
### 等値演算子は`==`で表現されます。
文字列と数値の比較の場合、文字列を数値に変換してくれます。
## 厳密等価演算子`===`で表現されます。
オペランド(数式を構成する要素のうち、演算の対象となる値や変数、定数などのこと)同士が、型を変換することなく、厳密に等しいならば真を返します。文字列は数値に変換されないためfalseになる。
複数の条件分岐も可能です。

## 演算子

-  > 大きい	
-  >= 以上	
-  <  小さい	
-  <=	以下	
- ==	等しい	
- !=	等しくない

##　比較演算子
比較演算子は2つの値が等しいか異なるかを評価し、true（真）もしくはfalse（偽）を返します。通常これらは論理式（論理結合子の適用対象となる記号列のこと）の中で使います。



##　真偽値

```sample5.js
const number = 25;
console.log(number >= 1);//true
```
真偽値の条件式にはtrueかfalseしかありません。条件式が成立する場合はtrue、条件式が成立しない場合はfalseをコンソールは返します。

## trueとみなされる値とfalseとみなされる値
###falseとみなされる値
- false 
- 0
- On
- ""(空文字)
- null
- undefined
数値の0や文字列の空文字、nullやundefinedなどはfalseとみなされます。

```sample6.js
  if (0){
  console.log('true');
}else{
  console.log('false');
}
//=> false
```
### trueとみなされる値
- true
- 10
- "mouth"
- [10, 20]
0以外の数値や空文字以外の文字列、他にオブジェクトは全てtrueとしてみなされます。


```sample7.js
if ('Yes'){
  console.log('true');
}else{
  console.log('false');
}
```
if文の条件式にYesを記述しました。空文字以外の文字列はtrueとみなされます。

## まとめ
第三者が見てもっとわかりやすいコードを心がけたいと思います。
robocopyコマンドで簡単バックアップ

### 新しいパソコンにデータ移行
情報システム部門やパソコンを販売する業者の方は、これからパソコンの入れ替えによるデータ移行作業が多く発生するのではないでしょうか。　　
Windowsマシンで手軽にバックアップができるコマンドを作成いたしました。
※コードを実行される際は事前にテストを行い、問題がないことを確認されてからご利用ください。

### 検証環境
エディション: Windows10 Pro  
バージョン: 21H1  
OSビルド: 19043.1466

### コマンドの説明
①環境変数の指定
デファルトのバックアップ元はuserprofileのデータです。

②バックアップ先
バックアップ先を指定してください。
指定されない場合は実行を中止します。
パスはダブルクォーテーションで囲ってください。

③実行の確認
「Yes」と入力しないと実行されません。
大文字のY
小文字のe
小文字のs
です。

④実行
・エラーが起きると、問題のあるデータはすっ飛ばす仕様にしています。リトライしたい場合は/R:0の数字を変更してください。
・/XDにて特定のフォルダを除外しています。


以上です。
ご活用いただければ幸いです。   

```backup.bat
@echo off
set answer=No
set dir_source=%userprofile%
set dir_destination=No

rem バックアップ元の確認
echo バックアップ元は【%dir_source%】です。

rem バックアップ先の指定・確認
set /p dir_destination="バックアップ先をフルパスで指定してください。　：　"
if %dir_destination%==No (
  echo バックアップは中止されました。
  pause
  exit
) else ( 
  echo バックアップ先は【%dir_destination%】です。
)

rem バックアップの実行
set /p answer="バックアップ実行するには　Yes　と入力してください。　：　"
if %answer%==Yes (
  echo バックアップを開始します。
  pause
  robocopy %dir_source% %dir_destination% /mir /R:0 /XD %dir_source%\AppData %dir_source%\Cookies "%dir_source%\Local Settings" "%dir_source%\Application Data" "%dir_source%\NetHood" "%dir_source%\PrintHood" "%dir_source%\Recent" "%dir_source%\SendTo" "%dir_source%\Templates" "%dir_source%\スタート メニュー" "%dir_source%\My Documents"
  echo バックアップが終了しました。
  pause
) else ( 
  echo バックアップは中止されました。
  pause
  exit
)
```
SpringBootで現在実行中のプロファイル(Profile)を取得する

## 設定する方法はあるけれど、取得する方法ってなんだっけ

SpringBootには、設定内容を実行環境ごとに切り替えるプロファイル機能があります。
特定のプロファイルが有効なときだけ機能させるアノテーション `@Profile` がありますが、現在有効なプロファイルってどれだっけ…？を取得するには、`org.springframework.core.env.Environment` を使います。

* SpringのApplicationContextからEnvironmentを取得する
* Springの管理下にあるBeanならEnvironmentは `@Autowired` ですぐに参照できる

### 利用例

```java
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;


@Controller
@RequestMapping("/")
@AllArgsConstructor
@Slf4j
public class SampleController {
	
	Environment environment;  // これでっす(・ω・)
	
	@GetMapping("")
	public ModelAndView display(ModelAndView mnv) {
		
		// Profileは複数指定が可能なので、String[]になる
		String[] activeProfiles = environment.getActiveProfiles();

        // 例えばプロファイルによっては動いてほしくないとき、の例
        if (Arrays.stream(environment.getActiveProfiles()).filter(profile -> Objects.equals(profile, "sample")).findFirst().isPresent()) {
            // プロファイルにsampleが指定されていたら～～～～
        };


		// ....(中略)....
		return mnv;
	}
}

```
addEventListenerについて

何かイベントが起きた時に指定の処理を実行させたい時
「addEventListener()」
というメソッドを使います。

_構文_

```
対象要素.addEventListener(イベントの種類,処理実行関数);
```
記述方法は上記構文のような、
「document」等の対象要素に対して
「addEventListener」を宣言して
第1引数に取得するイベントの種類（クリックした時、キーボードキーを押した時等）を指定して
第2引数に関数を指定して「第1引数イベント種類」が発生した時、関数内に書かれた処理を実行する
という意味になります。

_例コード_

```
document.addEventListener('keydown',function(event){
  console.log(event.key);
});
```
上記例コードは先ほどの構文にのっとり記述しています。
「document」に対して、
「addEventListener」を宣言し、
「keydown」で、「キーボードキーを押す」というイベントが発生した時に、
「function(event){console.log(event.key);}」で、押したキーの「キーコード」をコンソールに出力する
という意味になります。
【Project Euler】Problem 86: 直方体の最短経路

* 本記事は[ProjectEuler](https://projecteuler.net/about)の「100番以下の問題の説明は記載可能」という規定に基づいて回答のヒントが書かれていますので、自分である程度考えてみてから読まれることをお勧めします。

### 問題 86.直方体の最短経路

原文 [Problem 86: Cuboid route](https://projecteuler.net/problem=86)

**問題の要約：最大値Mの3辺が整数の直方体で、頂点SからFへ壁を伝って行く最短経路の長さがやはり整数になる場合が何通りあるか数える。その数が初めて$10^6$を超えるような最小のMを求めよ**

* 例として初めて2000通りを超えるMは100。　M=99の時は1975通り

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1922159/6dcdc83d-c040-5281-3557-63ff51619eeb.png)

以下のステップで考えて行きます。

* 3辺$a \le b \le c \le M$とする。例題では$(a,b,c) = (3,5,6)$
* 直角三角形の底辺として$c$をカウントアップして行き、高さは$a+b$を$2c$までループする
* このとき斜辺$\sqrt{(a+b)^2+c^2}$が最短経路になりこれが整数となる場合が解の候補
* $a+b$の$a \le b \le c$の条件で$a$と$b$の取りえる値を数える、$(a+b)$と$c$の大小関係で以下のように変わってきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1922159/1c6f51e2-db47-492b-32b2-64c7185a0a34.png)

これをプログラムにしたのがこちら。

```python 
import itertools
# a<=b<=c<=M
count = 0
for c in itertools.count(3):
  for ab in range(3,2*c+1):
    rt = (ab**2 + c**2)**(1/2)
    if rt == int(rt):
      count += ab // 2 if (ab <= c) else (2*(c+1) - ab)//2
  if count > 10**6:
    print(f"Answer: {c},  count={count}")
    break
```

（開発環境：Google Colab）
[Typescript] 型ガード 〜isに関して〜

# 型ガードとは？
参考: https://typescript-jp.gitbook.io/deep-dive/type-system/typeguard

> Type Guardを使用すると、条件ブロック内のオブジェクトの型を制限することができます。

簡単に言うと、複数の型が想定されるものを条件分岐で判断すること
`typeof`や`instanceof`などが当てはまる

```ts
function doSomething(x: number | string) {
    if (typeof x === 'string') { // Within the block TypeScript knows that `x` must be a string
        console.log(x.subtr(1)); // Error, 'subtr' does not exist on `string`
        console.log(x.substr(1)); // OK
    }
    x.substr(1); // Error: There is no guarantee that `x` is a `string`
}
```

```ts
class Foo {
    foo = 123;
    common = '123';
}

class Bar {
    bar = 123;
    common = '123';
}

function doStuff(arg: Foo | Bar) {
    if (arg instanceof Foo) {
        console.log(arg.foo); // OK
        console.log(arg.bar); // Error!
    }
    if (arg instanceof Bar) {
        console.log(arg.foo); // Error!
        console.log(arg.bar); // OK
    }

    console.log(arg.common); // OK
    console.log(arg.foo); // Error!
    console.log(arg.bar); // Error!
}

doStuff(new Foo());
doStuff(new Bar());
```

上記の例のように引数などで複数の型が想定される場合に、それぞれの型の場合で処理を分岐したい時に利用することができます

# is演算子とはなにか？
ref: https://zenn.dev/tsuboi/articles/0bace937cbd0787191cb
> is演算子を使うことで、条件式を切り出すこと(スコープから離れること)が可能となります。is演算子は、開発者が TypeScript に対して型を教えるための機能で、ユーザー定義型ガードと呼ばれます。関数の返り値を引数 X is Tとアノテートすると、条件がtrueを返す場合はXはTであり、falseを返す場合はTではないとTypeScriptに指示することになります。

複数の型をis演算子を使うことで返り値をその型として扱うことができます

```ts
function doSomething(x: number | string) {
  const isString = (x: number | string): x is string => typeof x === "string";
  if (isString(x)) {
    console.log(x.toUpperCase());
  } else {
    console.log(x.toFixed(2));
  }
}
doSomething("max"); //MAX```
```
isStringは、返り値をx is numberとアノテートしているため、trueを返した場合はargはnumberであると見做されます。そのため、ifブロックのなかではxがstringに絞り込まれています。この機能を使うことで、型ガードを関数として切り出すことが可能になり、複雑な型ガードを実装することも可能となります。

```ts
//is演算子による型ガード
const isString = (arg: number | string): arg is string => typeof x === "string";
function doSomething(x: number | string) {
  if (isString(x)) {
    console.log(x.toUpperCase());
  } else {
    console.log(x.toFixed(2));
  }
}
doSomething("max"); //MAX
```

```ts
type User = { username: string; address: { zipcode: string; town: string } };
type Guest = { username: string; };

const isUser = (arg: User | Guest): arg is User => {
	const u = arg as User;
	return (
		typeof u?.username === 'string' &&
		typeof u?.address?.zipcode === 'string' &&
		typeof u?.address?.town === 'string'
	);
};
```

このisUser関数ではisの型述語が使われているが、つまりはその返り値が true だったときにその型がUserであることをコンパイラに示唆する。 false であればGuestであることを示唆する。

```ts
const u: unknown = JSON.parse('{ "username": "patty" }'); 

if (isUser(u)) {
	console.log(`${u.username} lives in ${u.address.town}`); 
}else{
	console.log("It's Guest");
	console.log(`${u.username} lives in ${u.address.town}`); // Guestにはaddressがないのでコンパイルエラー！
}
```

# ユースケース「配列の`string | undefined[]`からundefinedを取り除く」

```ts
const items: (string | undefined)[] = ['一番', undefined, '2番', undefined, '3番']

const stringItems: string[] = items.filter((item): item is string => typeof item == 'string')
```

このように指定してあげるとfilterが判断できるのでstring型の配列として扱うことができます
Githubで2021年8月からのトークン認証で、今回に限ってなぜか失敗してしまう場合の解決方法

## 動作環境
- Github
- MacOS

## 前提
- Githubではセキュリティー強化のためにパスワード認証が2021年8月13日から廃止されました。
- このため、Githubへpush, pullするためにはトークンベースの認証、または、SSHベースの認証が必要になりました。
- このとき、トークンベースの認証をしようとして`git pull`などを実行した際に、失敗すると以下のようなエラーが表示されます。
- [github公式のお知らせページ](https://github.blog/2020-12-15-token-authentication-requirements-for-git-operations/)

## エラーの表示は以下の通り
```
Username for 'https://github.com': hogehoge
Password for 'https://hogehoge@github.com': 
remote: Support for password authentication was removed on August 13, 2021. Please use a personal access token instead.
remote: Please see https://github.blog/2020-12-15-token-authentication-requirements-for-git-operations/ for more information.
fatal: Authentication failed for 'https://github.com/xxxxxxxxx/yyyyyyy.git/'
```

## 一般的な解決方法
- 一般的には、Githugのトークン認証を設定すれば解決します。ブラウザでGithubにアクセスして以下のように設定をすると、トークン認証が成功します。
  - [参考サイト](https://stackoverflow.com/questions/68775869/support-for-password-authentication-was-removed-please-use-a-personal-access-to)
 
```
Click here - https://github.com/settings/tokens

Step 1 - Open GitHub and log in with your credentials.

Step 2 - Click on the Setting menu.

Step 3 - From the Setting menu click on Developer Settings

Step 4 - From the Developer Settings, menu click on Personal access token

Step 5 - From the Personal access token click on the Generate new Token button.

Step 6 - Now fill up required details like Note, Expiration, Select scopes. And then click on Generate Token button.

Step 7 - After that new token has been generated. Copy that generated token and use this token to access Git with username and token.
```

- そんなことは、既に知っているが、今回に限ってなぜかエラーになる人のために、この記事を書きました。以下をお読みください。

## ここより下はこんな人が対象

- [x] 上記の内容は既に知っていて、もう過去に何度もgithubのトークン認証を成功させているのに、今回に限ってなぜか失敗する。
- [x] MacOSで、git pull, git cloneなどが失敗する。  
- [x] トークンの期限が切れているわけではない。
- [x] トークンの期限が切れているわけではないが、とりあえず新しいトークンを作成して実行してみるも、やはり失敗する。
- [x] repoにチェックをちゃんと入れている。
- [x] 他のウェブサイトをいくつも検索したが、トークン認証がうまくいかない原因がわからない。
- [x] macOSから実行していて、ターミナルからグループで利用しているEC2サーバにsshログインして、`git pull`をした際に発生している。
- [x] とっとと仕事を終わらせたい。

## 解決方法
- 自分が発見した解決方法は、MacOSのターミナルを一度閉じて、再度ターミナルから`git pull`を実行したら解決しました。
  - ターミナルのタブを閉じて、新しいタブから実行しただけでも成功しました。 
  - なんともあっけない解決方法ですが、トラブっている最中はそれどころではありません。

## KeyChainAccessについて
- その他、MacOSではKeychainで削除しきれてない設定があってもgithubのトークン認証に失敗する例もあるようですが、自分の場合はKeychainについては、一切触らずに解決しました。
  - [アカウント設定でアクセストークン入れてもエラー](https://community.atlassian.com/t5/Sourcetree-questions/%E3%82%A2%E3%82%AB%E3%82%A6%E3%83%B3%E3%83%88%E8%A8%AD%E5%AE%9A%E3%81%A7%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B9%E3%83%88%E3%83%BC%E3%82%AF%E3%83%B3%E5%85%A5%E3%82%8C%E3%81%A6%E3%82%82%E3%82%A8%E3%83%A9%E3%83%BC/qaq-p/1777840)
【Project Euler】Problem 85: 長方形の数

* 本記事は[ProjectEuler](https://projecteuler.net/about)の「100番以下の問題の説明は記載可能」という規定に基づいて回答のヒントが書かれていますので、自分である程度考えてみてから読まれることをお勧めします。

### 問題 85.長方形の数

原文 [Problem 85: Counting rectangles](https://projecteuler.net/problem=85)

**問題の要約：長方形の格子に置ける長方形の数を数えたとき、その数と$2*10^6$との差が最小になるような長方形の格子の面積を求めよ**

この2x3の例では置ける長方形の数は合計18となります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1922159/db45ced6-97ac-aab7-624b-2ab2f09f2075.png)

図をよく見ると、置ける数は右下らかの座標を$(x,y)$とすると$x \times y$となっていることが分かります。したがっておける長方形の数はその合計なので素直に実装するとこうなります。

```python 
import itertools
def countRect(xsize, ysize):
  return sum([x*y for x,y in itertools.product(range(1,xsize+1),range(1,ysize+1))])

Mil = 2*10**6
minDif = 100
for x,y in itertools.combinations(range(5,202),2):
    count = countRect(x, y)
    dif = abs(Mil - count)
    if dif < minDif:
      minDif, minArea = dif, x*y
print(f"Answer: {minArea}")
```

ここまで規則的だと和の式も出せそうなのでやってみます。

```math 
\large \sum_{x=1}^{X} \sum_{y=1}^{Y} x \times y \\
= \large \sum_{x=1}^{X} x \times  \sum_{y=1}^{Y}  y \\
= \large \frac{x(x+1)}{2} \times \frac{y(y+1)}{2} \\
= \large x(x+1)y(y+1)/4
```

ということで**countRect**を以下のように書き換えるとかなり速くなります。

```python 
def countRect(x,y):
   return x*(x+1)*y*(y+1)//4
```

（開発環境：Google Colab）
データベーススペシャリスト試験に合格しました

令和 3 年度のデータベーススペシャリスト試験に合格しましたので、体験記です。

得点は

<img width="400" alt="score.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/178830/1756e08c-fe03-c71d-6f3a-4102db41afb1.png">

でした。

## 受験者のスペック

- 現在はモバイルアプリエンジニア
- 古の時代にサーバサイドで追加改修での DB 設計レベルは経験あり
- よほど複雑でなければ集計系のクエリ作成で問題はないはず (要 Google)
- [半年前に NW 合格](https://qiita.com/hugehoge/items/1013283c172dbfd6d0bc) (したので、午前 Ⅰ 試験免除)

## 受験まで

参考書は以下を利用しました。

https://www.shoeisha.co.jp/book/detail/9784798167770

ありがたいことに、購入者向けに過去問が回答シート付きでダウンロードできます。
データベーススペシャリストだと作図問題用の回答シート用意が大変なので、これは非常にありがたかったです。

受験までは比較的余裕があったのですが、他のことに気を取られたというのもあり、1ヶ月前から本格的に参考書を進めつつ過去問を回しました。

参考書の内容的にも概念データモデルと関係スキーマの学習がほとんどだったのですが、過去問を解く限りでは、8割くらいは取れそうだが回答時間が少し足りないような状況でした。

これに加えて試験前日に、8割は取れそうといっているが部分点の配分が怪しいという謎の不安感に苛まれて、最終的には概念データモデルと関係スキーマの大問を捨てるという決断で試験当日を迎えることとなりました。

## 受験当日

会場はやや遠目だったのですが、午前 Ⅰ 試験免除だったので、朝は余裕を持って会場に迎えました。

### 午前 Ⅱ

午前 Ⅱ についても過去問を回してきたので、時間的にも苦労することなく特に問題なくパスしたと思いきや、採点結果を見ると想定より低かったので、この時点でちょっと怪しい雰囲気だったようです。

### 午後 Ⅰ

前述のように、概念データモデルと関係スキーマの大問を捨てたのですが、結果的には大失敗でした。。

大問1つが問題文の時点で嫌な予感＋実際に回答でもだいぶ詰まってしまったので、おそらく概念モデルと関係スキーマの大問を取りに行った方が良かったと思われます。

感触的には、よくて50点台後半でギリギリアウトといった具合で、試験前日での決断に失敗した感が強かったです。

### 午後 Ⅱ

午後 Ⅰ の失敗から若干テンション低めでの試験となりました。

こちらについても、概念データモデルと関係スキーマは捨てて、もう1問の回答で臨んだのですが、感触としては午後 Ⅰ よりは良かったです。

ただ、採点結果としては想定より低く、大失敗したと思っていた午後 Ⅰ より低かったので、色々と噛み合ってない部分が多かったようです。

## 感想

結果としてはギリギリ合格でした。

反省点としては、ほとんどを概念データモデルと関係スキーマの学習に時間を充てたにも関わらず、試験本番前日にそれを捨てるという決断が大失敗だっというのが一つ。
 NW 試験のときもそうだったのですが、全体的に回答時間不足に陥りがちなので、過去問の回し方が足りなさそうというのが一つです。

今回は、試験対策中心で DB 設計や SQL 周りの純粋な学習があまりできなかったので、資格に負けないようこのあたりの知識も再度学習していければと思います。
HomebrewでChromeDriverをインストールする（関連Pythonライブラリの説明もあり！）

ChromeDriverは、seleniumを使ってGoogleChromeブラウザを自動操作したり、ウェブスクレイピングするために必要なツールですね。

HomebrewでChromeDriverをインストールする手順を説明します！

5分もあれば終わる作業でした👍

## この記事でわかること
- HomebrewでのChromeDriverのインストール方法
- HomebrewでのChromeDriverのアンインストール方法
- ChromeDriverに関連するPythonライブラリ

## そもそもHomebrewとは・・・
Homebrew（ホームブルー）は、macOS（またはLinux）用パッケージマネージャーにです。

インストール方法や、基本的な使い方はこちらに記載していますので、まだインストールしていないという方やどう使うか分からないという方は是非一度ご覧ください！

https://qiita.com/toranoko92114/items/10976bad711fd0987dd3

## まずはChromeDriverがインストールできるかを確認

```bash
% brew search chromedriver
==> Formulae
chrome-cli

==> Casks
chromedriver
homebrew/cask-versions/chromedriver-beta
```
==> Casksに『chromedriver』がありますね。

今回はこちらをインストールしていきましょう！

## ChromeDriverのインストール方法

ChromeDriverをインストールするためのコマンドは以下になります。

```bash
% brew install --cask chromedriver 
```

```bash
% brew install --cask chromedriver 
==> Downloading https://chromedriver.storage.googleapis.com/98.0.4758.80/chromedriver_mac64_m1.zip
######################################################################## 100.0%
==> Installing Cask chromedriver
==> Linking Binary 'chromedriver' to '/opt/homebrew/bin/chromedriver'
🍺  chromedriver was successfully installed!
```
実行して結果に『chromedriver was successfully installed!』と表示されていれば、インストールが完了しています。

とても簡単ですね🙌

## ChromeDriverのアンインストール方法

ちなみにアンインストール方法は以下のコマンドを実行してあげればOKです👍

```bash
% brew uninstall --cask chromedriver 
==> Uninstalling Cask chromedriver
==> Unlinking Binary '/opt/homebrew/bin/chromedriver'
==> Purging files for version 93.0.4577.63 of Cask chromedriver
```

## ちなみに・・ChromeDriverはアップデートが大変

ChromeDriverは、Chromeのバージョンアップがある度にアップデートをしないといけないというのがかなり面倒だし、大変なんですよね。

これまで動いていた自動化プログラムが急に動かなくなったと思ったら、Chromeのバージョンアップがあったことが原因だったりとか・・・

いちいち原因究明するのも面倒ですし、ChromeDriverをアップデートするのも面倒ですし。

そんな課題を解決してくれるPythonのライブラリがあります！

『webdriver_manager』というライブラリなのですが、これを使うことでChromeDriverのバージョンが古かったら勝手にバージョンアップしてプログラムを実行してくれるという優れものです。

そのインストール手順とか使い方などをこちらの記事にまとめていますので、是非ご覧下さい👍

https://qiita.com/toranoko92114/items/f83b4e4b79194afaaa4a

ChromeDriverのインストール方法と関連ライブラリの紹介は以上になります！
Google検索の右のボックスが表示されない

2022年2月初旬ごろから以下のような事象に遭遇しました。

![画面収録-2022-02-14-11.39.07.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1142160/2ef0d6db-b623-6266-c76f-881d8b53a196.gif)

バレンタイン仕様ですね！！

いえ、そこではなく......右側に注目です。

Google検索では、人物名や企業名など用語を入力すると、「アンサーボックス」と呼ばれるボックスが表示されます。

私は、こちらをよく利用しており、わからない用語等を調べる際に、時間の節約となるため、重宝していました。しかし、ここ最近、何らかの不具合により一瞬だけ表示され、その後消えてしまうという状態となっていました。

##調査
とりあえずデベロッパーツールを開いてコードを確認。色々調べてみると、ページの最下部にひょっこり移動していることがわかりました。

恥ずかしかったのでしょうか？

![スクリーンショット 2022-02-14 11.49.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1142160/77d7f2b5-c429-1743-5899-23610e609451.png)

##原因
初めはGoogleの問題かと思いました。しかし、天下のGoogle様がこのようなバグを残すはずがありません。

こういった突発的に発生するブラウザの不具合は、大抵が``「拡張機能」のせい``です。
ということで、拡張機能を一つずつ無効にしていくことで原因を突き止めましょう。

今回、私の環境で原因となっていた拡張機能は、[What Font - find font](https://chrome.google.com/webstore/detail/djgfpbegnihdgbngpmhjnlchgglngcdn)でした。
※サポートが終了し？リンクが切れていますので、クリックしても404ページに飛びます。

##終わりに
この拡張機能は既に提供が終了しているようで、その影響でサポートが切れてしまったのが原因でしょうか。
他の拡張機能へ移行しましょう。
React: react-router-dom (v6) の使い方

こちらのページを参考にしました。
[React Router v6 はじめでもわかるルーティングの設定方法の基礎](https://reffect.co.jp/react/react-router-6)

次のようーなページを作成します。

ホーム
![router_aa.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/179446/03096ccd-eb1a-2d21-5aef-2eeed6c90b25.png)

紹介
![router_bb.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/179446/878771bd-3e61-823a-8f65-7769a59c08a2.png)

連絡先
![router_cc.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/179446/251ae6b0-d7ca-3dce-c7aa-451dd28f68c5.png)

#プロジェクトの作成#

```bash
npx create-react-app proj01
```

インストールされたモジュールの確認

```bash
cd proj01
```

```text
$ npm list --depth=0
proj01@0.1.0 /home/uchida/tmp/feb14/proj01
├── @testing-library/jest-dom@5.16.2
├── @testing-library/react@12.1.2
├── @testing-library/user-event@13.5.0
├── react-dom@17.0.2
├── react-scripts@5.0.0
├── react@17.0.2
└── web-vitals@2.1.4
```

react-router-dom をインストール

```bash
npm install react-router-dom
```

バージョンを確認

```text
$ npm list --depth=0
proj01@0.1.0 /home/uchida/tmp/feb14/proj01
├── @testing-library/jest-dom@5.16.2
├── @testing-library/react@12.1.2
├── @testing-library/user-event@13.5.0
├── react-dom@17.0.2
├── react-router-dom@6.2.1
├── react-scripts@5.0.0
├── react@17.0.2
└── web-vitals@2.1.4
```

次のプログラムを差し替えます。

```text
src/App.js
src/index.js
```

```js:src/App.js
import { Routes, Route } from 'react-router-dom';

import Home from './routes/home';
import About from './routes/about';
import Contact from './routes/contact';
import NoMatch from './routes/nomatch';

function App() {
  return (
    <div className="App">
      <h1>Hello React Router v6</h1>
<ul>
        <li>
          <a href="/">ホーム</a>
        </li>
        <li>
          <a href="/about">紹介</a>
        </li>
        <li>
          <a href="/contact">連絡先</a>
        </li>
      </ul>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/contact" element={<Contact />} />
	 <Route path="*" element={<NoMatch />} />
      </Routes>
    </div>
  );
}

export default App;
```

```js:src/index.js
import ReactDOM from 'react-dom';
import App from './App';

import { BrowserRouter } from 'react-router-dom';

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

次のコードを作成します。

```text
src/routes/home.js
src/routes/about.js
src/routes/contact.js
```

```bash
mkdir src/routes
```

```js:src/routes/home.js
function Home() {
  return <h2>トップページ</h2>;
}

export default Home;
```

```js:src/routes/about.js
function About() {
  return <h2>このページについて</h2>;
}

export default About;
```

```js:src/routes/contact.js
function Contact() {
  return <h2>連絡先のメールアドレス</h2>;
}

export default Contact;
```

サーバーの起動

```bash
npm start
```

ブラウザーで、
http://localhost:3000
にアクセス
Arduino IDE で ESP32 を利用しようとしてpython-serialがなくて動かない件（ubuntu20.04LTE）

Arduino IDE で ESP32 のモジュールを利用しようしてpythonのimport serial が通らなくて困った件があり、解消しましたので、記載します。


Arduino IDE での ESP32 モジュールの利用では、pythonを利用して、
更にimport serialしていますが、動きませんでした。
遅ればせながらの次のことでした。
pythonコマンドは、ubuntu20.04では、python2.7にリンクされています。
しかしながら、python2.7は、2020年1月1日でサポートを終了しており、
必要とされるpython2.7でのpip、およびpipを利用したpyserialのインストールはできなくなっています。


ご自身のほかの環境として、特段python2を利用する必要がないならば、
pythonコマンドが、python2にリンクされていることを修正するために
> sudo apt install python-is-python3

を入れてしまえばpythonコマンドでpython3にリンクされて、
これで、動くようになるということです。




Special Thanks to
　https://chromebook.nomad-life.net/1923577/%E3%80%90chromebook_linux%E3%80%91arduino-ide%E3%81%A7esp32%E3%82%92%E4%BD%BF%E3%81%86
  https://stackoverflow.com/questions/60762378/exec-python-executable-file-not-found-in-path
Golangで実装したAPIをCloud Runにデプロイする

## 本記事の内容

* Go言語で実装したAPIをCloud Runにデプロイする
* gcloud cliのインストール
* artifact registryへのdocker imageのpush

## はてなブログにて掲載しています。

https://moritomo7315.hatenablog.com/entry/go-cloudrun-deploy
Oracle ApexでCRUD画面を爆速開発 - その10（単表ではなく、結合を含むクエリのInteractive Gridでデータ更新する） 

# シリーズ
[Oracle ApexでCRUD画面を爆速開発 - その1（構成周り、一覧表示）](https://qiita.com/harukb/items/bad041174f59a9f07141)
[Oracle ApexでCRUD画面を爆速開発 - その2（詳細表示）](https://qiita.com/harukb/items/d4996b5a9a11ee2f3165) 
[Oracle ApexでCRUD画面を爆速開発 - その3（登録/更新処理,Validation,アクセス制限など）](https://qiita.com/harukb/items/f63c9035de34282320d6) 
[Oracle ApexでCRUD画面を爆速開発 - その4（選択リストの内容で一覧表示内容を更新させる）](https://qiita.com/harukb/items/7b86706f010f886b89f0) 
[Oracle ApexでCRUD画面を爆速開発 - その5（Classic Reportのヘッダの上にグループヘッダ行を作る）](https://qiita.com/harukb/items/2746a766228da48c93ed)
[Oracle ApexでCRUD画面を爆速開発 - その6（Classic Reportのレコード条件に応じて行の背景色を変える）]
(https://qiita.com/harukb/items/b976f3fad9d58bdb4587)
[Oracle ApexでCRUD画面を爆速開発 - その7（積み上げグラフを作成する）]
(https://qiita.com/harukb/items/725a8fedf077bbc3c106)
[Oracle ApexでCRUD画面を爆速開発 - その8（ロード時の条件に応じて別ページにリダイレクト） ]
(https://qiita.com/harukb/items/cad739a77254a47fb669)
[Oracle ApexでCRUD画面を爆速開発 - その9（地図表示してReportと連動してみる） ]
(https://qiita.com/harukb/items/e36f5739fea205dc068c)


# 今回の主な内容
- 単表ではなく、結合を含むクエリのInteractive Gridでデータ更新する


※ ~~知らなかったので~~ わ！す！れ！て！た！のでまとめておきます。

## データ構造

TODOUFUKEN(1) - (0..n)CITYの関係です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/607651/b3624d8b-e387-ae79-6226-b82f022bba5d.png)

## やりたいこと
外部結合を含んだクエリから作成したIGを編集して駆動表（CITY）のレコードのみを更新したい

例）青根キャンプ場⇒まぼろし

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/607651/d910a791-a799-0246-f463-59c1db818771.png)

# 失敗パターン

## データソース
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/607651/946afe0f-91e5-2133-732f-4212ad1e402f.png)

## 保存時のエラーメッセージ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/607651/73e57238-f4cb-ba33-c8c4-f4acf0892a2c.png)

# 成功パターン

## データソース
結合先のTODOUFUKENテーブルのカラムは参照のみで更新対象外なので「問い合わせのみ」に指定する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/607651/6ca638b4-e309-879f-b24c-4466081dc04c.png)

## 保存時の成功メッセージ

保存されました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/607651/22bd5cb8-b603-55b2-1013-30849950614f.png)

## 念のためデータを確認

OK！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/607651/c2151bf7-e92d-61a9-690e-2d33f3828afd.png)
正規表現で半角スペースを許容

# ケース
半角英字で入力するフォームで半角スペースがバリデーションにかかってしまっていた

# 対処
正規表現に` ` (半角スペース)を含める

```
/^[a-zA-Z ]*$/
```

# 説明
正規表現において半角スペースはエスケープの必要がなく、
通常の文字と同様に扱うことができる。
そのため、半角スペースをそのまま正規表現に含めてあげれば対象にできる。

# 参考
https://uxmilk.jp/50502
1.1(1) 日本語をread_csv(encoding='shift_jis')で読み見込めない時

日本語を含むcsvファイルを読み込む場合は、`encoding='shift_jis'`を指定して、`pd.read_csv('data.csv', encoding='shift_jis')`と書くのは定石です。
しかし、それでもエラーとなってしまう場合があります。
例えば、以下のようなcsvファイルです。

```
# 例1: 'shift_jis'でエラーとなる
  clm0 clm1   clm2  clm3
     1    Ⅰ  APPLE   りんご
     2    Ⅱ  apple   リンゴ
     3    Ⅲ  aPPLe   ﾘﾝｺﾞ
     4    Ⅳ  Apple   林檎

```
どれも日本語と英語のように見えますが、`'shift_jis'`　ではエラーとなります。
しかし、以下は　`'shift_jis'`　で読み込めます。

```
# 例2: 'shift_jis'でエラーとならない
  clm0 clm1   clm2  clm3
     1    I  APPLE   りんご
     2   II  apple   リンゴ
     3  III  aPPLe   ﾘﾝｺﾞ
     4   IV  Apple   林檎
```

何が違うのでしょうか？そしてどうすればいいのでしょうか？

ここでは、もとのcsvファイルを確認しても、おかしい部分が見当たらず、「何がおかしいの！(怒)」、という場面で対処する方法を説明します。上の２つの例が、大きなヒントとなります。
これを知っておくと、大きな時間短縮になります。

#トピック
* 原因
* 対処方法
* 他にどんなencodingがあるか
* 参考ページ<br>https://shilabo.com/python/web_self/a001_000/a001_001/



#原因
多くの場合、ファイルの中の日本語の文字が原因です。
例えば、以下の２つの文字をご覧ください。

文字1: Ⅰ, Ⅱ, Ⅲ, Ⅳ
文字2: I, II, III, IV

文字1は日本語です。1とタイプして全角変換したものです。2,3,4も同様です。環境依存文字と出てくるのではないでしょうか。
文字2は英語です。I(英大文字のアイ)とV（英大文字のヴィ）の組み合わせです。

文字1のような日本語が含まれていると、`'shift_jis'`では読み込めません。
他に、①, ②, ③　(1,2,3とタイプして全角で変換）も文字1と同じ扱いで、読み込めません。

#対処方法
元のファイルを見て、どう見ても日本語と英語であり、上記が原因であれば、以下で対処できます。

`encoding='cp932'`を指定します。

つまり、

```
df = pd.read_csv('data.csv', encoding='cp932')
print(df)
```
と書きます。これで①,②,③といった文字も読み込み可能です。

```
#   clm0 clm1   clm2  clm3
#0     1    ①  APPLE   りんご
#1     2    ②  apple   リンゴ
#2     3    ③  aPPLe   ﾘﾝｺﾞ
#3     4    ④  Apple   林檎

```

但し、**一番いいのは、'shift_jis'で読み込めないような文字は使わない**（日本語変換する①やⅣなどは使わない）ことです。或るは、日本語を一切使わず英語だけにするのが一番いいです。Pythonは日本語が苦手です。

しかし、現実には、会社の共有ファイルでは、過去に作成された「見せるための」（分析のためではない）ファイルからデータを抽出する必要も多々あるでしょう。

`encoding=shifut_jis`でうまくいかない場合は、`encoding='cp932'`で対処を試みてください。

#他にどんなencodingがあるか
以下のPyhonのドキュメント(英語)を調べると、日本語に関しては次のものが用意されてます。(リストをJapaneseでフィルター）
https://docs.python.org/3/library/codecs.html#standard-encodings

しかし、実際には、**普通は`'shift_jis'`を使い、うまくいかない場合は'cp932'を試してみる**、程度です。
それ以外は使いません。（少なくとも筆者は使った事がありません）

![encoding_in_api_doc.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2027725/b080404f-b1a5-c872-9f87-7f4ff1d6b38e.png)

#参考ページ
* 株式会社シラボのHPです。書籍のように必要な技術が掲載されています。
<br>https://shilabo.com/
<br>https://shilabo.com/python/web_self/a001_000/a001_001/
<br>
* pandasのAPIドキュメント
<br>https://pandas.pydata.org/docs/reference/api/pandas.read_csv.html
* Pythonのドキュメント
<br>https://docs.python.org/3/library/codecs.html#standard-encodings
MacOS 12 MontereyにアップグレードしたらAmazonLinux 2にsshログインできなくなったとき

# 症状

```bash
% ssh YOUR_HOSTNAME@USER
Unable to negotiate with YOUR_HOSTNAME port 22: no matching key exchange method found. Their offer: diffie-hellman-group-exchange-sha1,diffie-hellman-group14-sha1,diffie-hellman-group1-sha1
kex_exchange_identification: Connection closed by remote host
Connection closed by UNKNOWN port 65535
```



# 環境

クライアント
- MacOS 12(Monterey)


ホスト
- AmazonLinux 2

# 原因

AmazonLinuxのssh鍵が古くて、新しいMacOSではセキュアではないとして非対応のため


# 対応

`~/.ssh/config`の任意の`Host`に以下を追記

```
  KexAlgorithms +diffie-hellman-group1-sha1
  Ciphers +aes128-cbc,3des-cbc,aes192-cbc,aes256-cbc
```
[Swift]なぜIdentifiableつけるの？

#この記事を書いた理由
現在Swift UIの勉強をしていますが、Listを作成する機会がとっても多い。
その中で頻出するIdentifiableについて、「頻出だけどあまり理解できていない・・・」と思っていたので、1人でも私と同じような人がいなくなればと思って今回記事にまとめています。
まだ勉強中なので間違えていましたらご指摘いただけますと幸いです。

#目次
[1．Identifiableとは?](#Identifiableとは?)
[2．Identifiableが無い時のエラーについて](#Identifiableが無い時のエラーについて)
[3．実装してみる](#実装してみる)
[4．まとめ](#まとめ)



#Identifiableとは?
そもそもIdentifiableとは何でしょうか。
結論から言うと、**構造体に準拠するコンテンツにidを振ることを可能にするプロトコル**です。

まだちょっとわかりにくいですよね。
順を追って説明していきます。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
プロトコルについての記事はこちら　→　現在作成中
構造体についての記事はこちら　→　現在作成中
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

#Identifiableが無い時のエラーについて
Listを作成する際に以下のエラーが出たことないでしょうか。
 `Initializer ‘init(_:rowContent:)’ requires that ‘xxxx’ conform to ‘Identifiable’`

これは、「イニシャライザー init(_:rowContent:) は、xxxx が identifiable に準拠している必要があります。」という内容になり、**以下のような場合に発生します**。
<details><summary><b>コードの説明（折りたたみ）</b></summary><div>
下記は　let animals = ["犬","猫","猿"]　でanimalsという配列を作り、中に犬、猫、猿を格納しています。
次に　List(animals){ animal in Text(animal) }　で　配列animalsからListのような繰り返し処理でコンテンツを作ろうとしています。
</div></details>


    struct ContentView: View {
        let animals = ["犬","猫","猿"]
        var body: some View {
            List(animals){ animal in
                Text(animal)
             }
         }
     }

ではなぜエラーが出てしまうのでしょうか？

理由はListの性質にあります。Listのような繰り返し処理でコンテンツを作った場合、そのコンテンツの<strong>データを更新する必要が出た際にどのコンテンツを更新するのか、SwiftUI側がわからないといけません。</strong>
そのため繰り返し処理で作成されるコンテンツには、これが更新されるよー！と指定できるように予めidを振ってあげる必要があるのです。

つまり上記のエラーが出たということは、そのidを振ることができないということを伝えているのです。

#実装してみる
では実際に、エラーが出ない形にすると以下のようになります。

    struct ContentView: View {
        let animals = [Animal(id: 1, name: "犬"), Animal(id: 2, name: "猫"), 
           Animal(id: 3, name: "猿")]
        var body: some View {
            List(animals){ animal in
                Text(animal.name)
            }
        }
    }

    struct Animal: Identifiable {
        let id: Int
        var name: String
    }
先ほどListで表示しようとしていたnameの配列は、Animalという構造体で定義するようにして、AnimalはIdentifiableに準拠し、idというプロパティを持つようにしました。

こうすることでSwiftUI側はプロパティidを使ってどのコンテンツを更新すればいいのかを判断できるようになります。

#まとめ
簡単に言うと、英訳どおりIdentifiable自体には**識別可能にする**効果があります。
Identifiableプロトコルに準拠した構造体は、idを持っている＝idを振ることができる ということになります。
つまり、idを振らせることでSwiftUIへ識別可能にするのです。
Dockerを起動したら、docker-usersをグループにいれてと言われたときの対処方法

Windows11でDocekrをインストールして、こんなメッセージがでてしまい、
Dockerが起動できない状況に遭遇しました。回避策を自分用にメモを残します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2503521/d442cdf2-3d8b-7248-77c8-5f2d82474017.png)
調べてみたところ、

コマンドプロンプトを管理者として実行し、下記コマンドを実行することでグループに追加させることができるようです。

net localgroup docker-users user /ADD

上記の意味は、ローカルユーザーのuserにdocker-usersを追加する。
userのところを適宜変更して頂くと良いかと思います。

【余談】
コンピュータの管理から、ローカルユーザーとグループ（ローカル）を
GUIで編集している解説は見つかるのですが自分のWindowsでは、
コンピュータの管理にローカルユーザーとグループ（ローカル）は
表示されていなかったので、この方法では解決できませんでした。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2503521/cf856b18-2b0b-8815-24ae-c0e2ee5b3f0a.png)

Windowsキー＋Rで、lusrmgr.msiを起動してみたら、
Windows10以降は使用できませんとのこと。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2503521/5f0cdcd3-4250-fad2-cb8c-251d704c1feb.png)

その後、コントロールパネルのユーザーアカウントなどいろいろGUIを見てみましたが
ローカルユーザーをグループに追加する方法がみつからず、
コマンドラインでグループにローカルユーザーを追加する方法が見つかったので、
自分のメモとして記載しておくことにしました。

引用サイト
https://teratail.com/questions/362607
https://qiita.com/toro_ponz/items/d75706a3039f00ba1205
https://win2012r2.com/2021/05/19/%E3%80%8C%E3%83%AD%E3%83%BC%E3%82%AB%E3%83%AB%E3%83%A6%E3%83%BC%E3%82%B6%E3%83%BC%E3%81%A8%E3%82%B0%E3%83%AB%E3%83%BC%E3%83%97%E3%80%8D%E3%81%AE%E9%96%8B%E3%81%8D%E6%96%B9/
openvpn@centos

証明書はopensslでやる。
サーバの秘密鍵を作る→csr作成→caで署名
ここは何度もやったので省略

opensslの設定はほぼデフォルト
configの置き場は 

```
/etc/openvpn/server/
```
configはサンプルをコピーして作る。

```
# cp /usr/share/doc/openvpn/sample/sample-config-files/server.conf /etc/openvpn/server/
```

なるべくデフォルト設定を使う。

以下のファイルが必要

```
ca ca.crt #VPNサーバの証明書を発行（署名）するCAの証明書
cert server.crt #VPNサーバの証明書
key server.key #VPNサーバの秘密鍵
```

```
#   openssl dhparam -out dh2048.pem 2048
dh dh2048.pem # DHパラメータ
```

```
#   openvpn --genkey --secret ta.key
#
# The server and each client must have
# a copy of this key.
# The second parameter should be '0'
# on the server and '1' on the clients.
tls-auth ta.key 0 # This file is secret # tlsキー、後ろの数字は0がサーバー、1がクライアント
```

dhパラメータをeasyrsaで作るように説明している情報が多いがsampleコンフィグにopensslのコマンドが書いてあるのでそれを使う。

configを載せておく。 
サーバ証明書とクライアント証明書での接続

#サーバ（コメント部分削除）

```
port 1194
proto udp
dev tun
ca ca.crt
cert server.crt
key server.key
dh dh2048.pem
server 10.8.0.0 255.255.255.0
ifconfig-pool-persist ipp.txt
push "route 192.168.55.0 255.255.255.0"
keepalive 10 120
tls-auth ta.key 0
persist-key
persist-tun
status openvpn-status.log
log-append  openvpn.log
verb 4
explicit-exit-notify 1
```

push... は、アクセス先のlanに相当するサブネット。
ここへのアクセスのみをトンネリングする。

外部ファイル(server.confと同じフォルダ /etc/openvpn/server に置く） 

```
ca.crt
server.crt
server.key
dh2048.pem
```

pushは接続先のプライベートネットワークセグメント（アクセス先）
加えてサーバ側のfirewalld設定やip forwardingの設定が必要
ログ出力設定はデフォルトで無効だが有効にした。

クライアント（windows） 

```
client
remote vpn.example.com
port 1194
proto udp
dev tun
tls-auth ta.key 1
persist-tun
persist-key
verb 4
mute 10

<ca>
-----BEGIN CERTIFICATE-----
（省略、サーバ証明書にサインしたCAの証明書）
-----END CERTIFICATE-----
</ca>
<cert>
-----BEGIN CERTIFICATE-----
（省略、クライアント証明書）
-----END CERTIFICATE-----
</cert>
<key>
-----BEGIN RSA PRIVATE KEY-----
（省略、クライアントの秘密鍵）
-----END RSA PRIVATE KEY-----
</key>
```

```
client
```

が必要

外部ファイル

```
ta.key
```


#クライアント（iPhone 15.2.1, Mac Monterey Version12.2）

```
client
remote vpn.example.com
port 1194
proto udp
dev tun
persist-tun
persist-key
verb 4
mute 10
key-direction 1
<ca>
-----BEGIN CERTIFICATE-----
（省略、サーバ証明書にサインしたCAの証明書）
-----END CERTIFICATE-----
</ca>
<cert>
-----BEGIN CERTIFICATE-----
（省略、クライアント証明書）
-----END CERTIFICATE-----
</cert>
<key>
-----BEGIN RSA PRIVATE KEY-----
（省略、クライアントの秘密鍵）
-----END RSA PRIVATE KEY-----
</key>
<tls-auth>
-----BEGIN OpenVPN Static key V1-----
（省略、ta.keyの中身）
-----END OpenVPN Static key V1-----
</tls-auth>
```
ポイントは、 tls-auth ta.key 1 と書くところは ta.keyの内容を

```
<tls-auth>
...
</tls-auth>
```

に貼り付けて、

```
key-direction 1 
```

(client側を意味する）を書く。
TypeScript: 型定義ファイルの解決がCIでだけ失敗する問題が発生しました

TypeScriptのプロジェクトで、なぜかローカルではコンパイルが通るけど、CIではコンパイルがコケる問題が発生しました。

## 問題が発生した状況

### CIでコンパイルしているが、そこでコケる

- このプロジェクトでは、CIで`tsc`を実行して型チェックを行っています。
- CIを回したら、「error TS2503: Cannot find namespace 'NodeJS'」というエラーが発生しました。
    - これは、`NodeJS`という型が見つからないというコンパイルエラー。

### ローカルではコンパイルが通る

- ローカル開発環境で`tsc`するとCIで発生しているようなコンパイルエラーは発生しませんでした。
    - 「僕の環境だと動くんですけど」状態

## ローカル開発環境のディレクトリ構成

この問題の原因はローカル開発環境のディレクトリ構成が関わっていました。

ローカル開発環境では、`~/projects`にプロジェクトごとにディレクトリを作る構成になっています。`projects`ディレクトリは、複数のプロジェクトを置くだけのスペースで、これをGitHubにpushしたりはしていません。あくまでプロジェクトを整理するために設けたディレクトリです。

今回問題が起こったプロジェクトは`~/projects/myproj`です。

```yml
~/projects
├── myproj # 問題の起こったプロジェクトのプロジェクトルート
│   ├── .git
│   ├── index.ts # ❶
│   ├── node_modules 
│   │   └── @types # ❷
│   │       └── lodash
│   └── tsconfig.json
├── node_modules
│   └── @types
│       └── node # ❸
├── other_project1 # ┐ 
├── other_project2 # ├ 他のプロジェクトのディレクトリたち(今回は関係ない)
└── other_project3 # ┘ 
```

index.ts(❶)では、概ね次のような`NodeJS.ProcessEnv`の型を参照するコードがあります。

```ts:index.ts
type A = NodeJS.ProcessEnv;
```

`myproj`が単体で、index.tsが正しくコンパイルされれるためには、`~/projects/myproj/node_modules/@types/node`があるべきでしたが、インストールされていませんでした(❷)。

代わりに、`myproj`ディレクトリのひとつ上のディレクトリ`projects`になぜか`node_modules`があり、その中に`@types/node`がインストールされていました(❸)。

この`~/projects/node_modules/@types/node`があるため、`myproj`に`@types/node`が無くても、`index.ts`は問題なくコンパイルがされていました。

一方で、CIの環境にはプロジェクトルートより上のディレクトリに`node_modules`は存在していないため、`@types/node`が見つけられず、コンパイルエラーになっていました。

## TypeScriptのモジュール解決

TypeScriptのモジュール解決は、細かく言うといろいろあるのですが、ざっくりいうとNode.jsのモジュール解決を模倣するモードがあります。

https://www.typescriptlang.org/docs/handbook/module-resolution.html

https://nodejs.org/api/modules.html#all-together

そのモードでは、`@types/node`型定義ファイルを探す場合、次の順でパスを登っていって探します。

1. <strong>/path/to/projects/myproj/</strong>node_modules/@types/node
1. <strong>/path/to/projects/</strong>node_modules/@types/node
1. <strong>/path/to/</strong>node_modules/@types/node
1. <strong>/path/</strong>node_modules/@types/node
1. <strong>/</strong>node_modules/@types/node

この順で見つからなければコンパイルエラーになります。今回のCIでコンパイルエラーになったのは、5まで登っても`@types/node`が見つからなかったためです。

`myproj`にちゃんと`@types/node`をインストールしていれば、ステップ1で型定義ファイルが見つかりコンパイルが通っていたはずです。

今回、ローカルでコンパイルエラーにならなかったのは、ステップ2の`@types/node`が採用されていたためです。

## 一時的な解決策

一時的な対処は、`myproj`に`@types/node`をインストールするだけです。

## 根本的な解決策

こういうミスが二度と起きないようにするには、不必要なnode_modulesを上のディレクトリに作らないようにすることです。これは個人的に行える対策です。適当なファイルを`node_modules`という名前で作っておけば、うっかり`npm install`や`yarn install`を行っても処理が失敗します。

```sh
echo 'ここにはnode_modules作らない' > ~/projects/node_modules
```

プロジェクト単位では`typeRoots`を明示的に設定したほうが良さそうです。

```json:tsconfig.json
 {
   "compilerOptions": {
     "typeRoots": ["./node_modules/@types"],
   }
 }
```

これを設定しておくと、型定義ファイルを探すのはプロジェクト配下の`node_modules/@types`だけになり、`../node_modules/@types`、`../../node_modules/@types`、...といった登って探すといったことが行われなくなります。