OmniAICreator/Qiita-1.07M huggingface dataset:
Ruby で文字数を数える

# これは何？

Ruby で `"shuﬄe"` を 7文字と数える方法を思いつた。
`"shuﬄe"` ってのはこういうやつ。

```ruby:ruby
"shuﬄe".then{ [_1.chars, _1.size ] } #=> [["s", "h", "u", "ﬄ", "e"], 5]
```

それだけだとしょぼいので、文字の数え方という一般的な話題を装った記事にしてみた。

そもそも文字とはなにかという問題もあったりはするけれど、そのあたりは

https://qiita.com/Nabetani/items/8c69bdd8060f2503683c

に書いた。

# 数える方法

## バイト数

バイト数は文字数じゃないわけだけど、バイト数を数えるメソッド `String#bytesize` がある。

ruby の場合、UTF-16 にしても バイト列であることには変わらない

```ruby:ruby
"hoge".bytesize #=> 4
"hoge".encode("utf-16le").bytesize #=> 8
"shuffle".encode("utf-16le").bytesize #=> 14
"shuﬄe".encode("utf-16le").bytesize #=> 10
"𩸽".encode("utf-16le").bytesize #=> 4
```

`"shuﬄe"` は、`ﬄ` (U+fb04) という ユニコードキャラクタを含んでいる。
`"𩸽"` は、いわゆるサロゲートペア文字。ほっけ。Java の `char` や Windows の `wchar_t` だと 二個になる。

## コードポイント数

コードポイントの数を数えるともうちょっと文字数らしくなる。
`String#size` または `String#length` を使う。

```ruby
"hoge".size #=> 4
"hoge".encode("utf-16le").size #=> 4
"shuffle".size #=> 7
"shuﬄe".size #=> 5
"𩸽".encode("utf-16le").size #=> 1
"👨‍👩‍👦".size #=> 5
"た\u{3099}んこ\u{3099}".tap{ p [_1, _1.size] } #=> ["だんご", 5]
"だんご".tap{ p [_1, _1.size] } #=> ["だんご", 3]
```
とはいえ。unicode 結合文字列がある場合など、コードポイント数とパット見の文字数が合わなくなる。

## 正規化してコードポイント数

`String#unicode_normalize` をすると「た」+ U+3099 が「だ」になる

```ruby
"だんご".unicode_normalize.size #=> 3
"た\u{3099}んこ\u{3099}".unicode_normalize.size #=> 3
"ぬ\u{3099}".unicode_normalize.size #=> => 2
"👨‍👩‍👦".unicode_normalize.size #=> 5
"shuﬄe".unicode_normalize.size #=> 5
```

しかし「ぬ」のような濁点を付けた字にコードポイントがない場合はコードポイント数が減らないし、`"shuﬄe"` が 7文字になったりもしない。

## Grapheme cluster の数

`String#grapheme_clusters` を使うと、だいぶ文字数っぽくなる

```ruby:ruby
"👨‍👩‍👦".grapheme_clusters.size #=> 1
"ぬ\u{3099}".grapheme_clusters.size #=> 1
"shuﬄe".grapheme_clusters.size #=> 5
```

が、それでも `"shuﬄe"` は 5文字のまま。

## 突然の `capitalize`

ところで、`String#capitalize` というメソッドがある。

こういうやつ。

```ruby:ruby
%w(jazz JAZZ ǳsessz ǱSESSZ).map(&:capitalize)
#=> ["Jazz", "Jazz", "ǲsessz", "ǲsessz"]
```

最初の文字を大文字にする。
と思うとちょっと正しくなくて、最初の文字を文頭にふさわしい文字にして、残りを文頭以外にふさわしい文字にする。

ハンガリー語などで使われるらしい ǳ (U+01F3) という文字は、大文字 Ǳ (U+01F1) の他に、大文字でも小文字でもない「title case」の ǲ (U+01F2) がある。ruby の `String#capitalize` はこれに対応していて、ǳ (U+01F3) で始まる語を `capitalize` すると ǲ (U+01F2) で始まるようになる。

で。

`"shuﬄe"` の 4コードポイント目の `ﬄ` を `capitalize` すると

```ruby
"ﬄ".capitalize #=> "Ffl"
```

となる。「最初の f だけが大文字」という合字は、少なくともユニコード内にはないのでしかたなくばらばらにする。という意外な実装になっている。

というわけで、これを使うと `"shuﬄe"` を 7文字だと数えることができる。

```ruby
"shuﬄe".chars.map{ _1.capitalize.size }.sum #=> 7
```

まあ `capitalize` じゃなくて `upcase` でもいいんだけどね。

## それでも ㌠ は 1文字

ここまで色々やってきたけど

* `"くま㌠"` # 3文字？ 7文字？
* `"2㎧"` # 2文字? 4文字？
* `"≠″ｬ｣ﾚ"` # 「ギャル」と読む。5文字？ 3文字？
* `"㍻元年"` # 3文字? 4文字?

と、色々ある。

# そもそも

そもそも、文字数が知りたいというとき。
その文字数がなんのために必要なのかがわからないとどの数え方が正解なのかはわからない。

Windows のファイル名なら、およそ文字数らしくないけど、 UTF-16LE のバイト数の半分が正解と思う。

入力フォームの制限なら、フォームに入れた文字列が到達する先のデータベースの都合に合わせるのが正解。

デザインの観点でパット見で文字多すぎにならないようにということなら、文字数ではなく、その場所で使われるフォントでピクセル数を数えるのが正解かもしれない。

# というわけで

というわけで。
文字数の数え方にも色々あるよ。という話でした。
再インストールのためのMySQLの完全アンインストール（Windows11）

MySQLのインストールに失敗し、再インストールできない状況に陥った方向けにMySQLの完全アンインストールの方法（クリーンアップ方法）についてお伝えします。

１．MySQL Installerから全コンポーネントについてRemoveを実施する。
![mySQL_Installer.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2600620/bf8cab78-7487-d7db-ba50-ab1a5014bd63.png)
Removeが完了した後にInstaller自体のRemoveもするかどうか聞かれるので、Yesにチェックし、Installer自体もアンインストールする。

２．Windows11のアプリ（システム設定）でインストールされているアプリにMySQLのコンポーネントがないか確認する。あればアンインストールする。

３．以下のフォルダのMySQLを削除する。
```
　　C:\Program Files
　　C:\Program Files (x86)
　　C:\ProgramData
```
４．環境変数PathにMySQL関連の設定があれば削除しておく。

５．コマンドプロンプトから以下のコマンドでMySQLのサービスを削除する
```
sc delete MySQL80
```

以上の手続きでWindowsにインストールされているMySQLは完全にアンインストールされます。MySQLのインストールの途中で失敗してインストール不可となった方は是非、上記方法にてMySQLを完全にアンインストールし、クリーンな環境にしてから、MySQLのインストールを始めから実施すれば、インストールに成功するでしょう。

MySQLのインストールでトラブっている方に幸運あれ。
論理設計についての復習＆備考禄

# この記事の読者対象
- 最近DB設計について学習した初学者の方
- 設計の流れは分かるけどどういった段階があるのかを忘れてしまった方

# そもそも論理設計とは
『論理設計とはシステム開発のプロセスのうちの一つである』

:::note info
システム開発とは
ソフトウェアやハードウェアの要素を組み合わせて特定の目的や機能を持つシステムを作り上げるプロセスのこと。
:::
# システム開発と論理設計

システム開発には4段階ある
**①要件定義**
**②設計**
**③開発**
**④テスト**

この②に入ってくるのが **「論理設計」**

# 論理設計の手順
論理設計にも4つの段階がある

:::note info
エンティティとは
実勢会にある実態や概念のこと。これをRDBMS内にのルールであらわしたものが「エンティティ」
:::

**①エンティティの抽出**

システムにどのような「エンティティ」が必要になるかを抽出する工程。システムが満たすべき
機能を決める工程（要件定義）と重なるところもある。

**②エンティティの定義**
「エンティティ」がどのようなデータを保持するかを決める。
ここで「キー」という列(属性）を定義することが重要
「キー」があることでそのキーが辞書で言うところの「索引」となり
そのレコードを探しやすくなる。

**③正規化**
テーブルの利用をスムーズに行えるようにする作業のこと。
データの登録、変更、削除が整合性を保ったまま行えるように
エンティティのフォーマットを整理する。

正規化をする上で重要な3ステップ

第一正規形…主キーを設定し一つのセルには一つの値しか含まれない二次元表を作る

第二正規形…は主キーと一部の要素の相関性（部分関数従属）を取り除き完全関数従属になるまでテーブルを分割すること

第三正規形…は第二正規形で分割したテーブルの主キー以外の項目の同士で依存関係を持っているものを別テーブルに分けること

**④ER図の作成**
正規化を行うとエンティティ(テーブル)の数が増えるので
そのエンティティ（テーブル）の関係性の図(これをER図と言う)を作成し
「エンティティの見取り図」をつくるのがER図の作成です
Intel MacからM2 Macに買い換えたのでHomebrewを再設定してみる

5年ぶり？に MacBookを買い替えたのでセットアップ！
TimeMachineから復元しようと試みるも安定の**できない**（たぶん一度も出来たことがない気がする）
なので新旧を直接繋げて復元完了！

## 準備

現在の `/usr/local` にインストールしたパッケージを `Brewfile` に保存
新しいインストール先の `/opt/homebrew` を作成

```
# Brewfileの作成（バックアップ）
$ cd ~
$ brew bundle dump

$ cd /opt
$ sudo mkdir homebrew
$ sudo chown ${USER}:staff homebrew
```

## インストール

[参考](https://docs.brew.sh/Installation#alternative-installs)

```
$ git clone https://github.com/Homebrew/brew homebrew

$ eval "$(homebrew/bin/brew shellenv)"
$ brew update --force --quiet
$ chmod -R go-w "$(brew --prefix)/share/zsh"
```

## Brewfile（バックアップ）の適用

```
$ cd ~
$ brew bundle install --file Brewfile
```
クラウドセキュリティについて

昨今クラウドセキュリティの需要が高まってきていることが各企業のセキュリティインシデントから見受けられる。

最も最近の事例ではトヨタの「クラウド環境の誤設定による顧客情報の漏洩」が記憶に新しい。

参考：[クラウド環境の誤設定によるお客様情報の漏洩可能性に関するお詫びとお知らせについて](https://global.toyota/jp/newsroom/corporate/39174380.html)

以下、個人的に考えいる「クラウド環境によくある設定ミス」を挙げてみる。
・意図しないポートの開放（例：HTTP:80, Telnet:23）
・DBやStrageの非暗号化もしくはパブリックがアクセス可能な状態
・アプリケーションの認証設定不備

これらの発生要因としては環境構築時に「<font color="Red">デフォルト設定の放置</font>」や「<font color="Red">人為的ミス</font>」にが一般的であると考える。
クラウドサービスは非常に有用性が高く、簡単に多くの設定ができるというメリットの反面、企業単位で利用する場合は<font color="Red">定期的な設定値の見直し</font>の機会を設けることで未然にインシデント発生を防ぐことが大切であろう。

また、クラウドベンダーが公開しているマニュアルを理解するためにはある程度の慣れが必要である為、すでに知っているサービスであっても自身の理解とマニュアルと照らし合わせしてみるのは今後新規サービスを利用する際に活きてくると思う。


以上、今後もクラウドセキュリティに関する雑談や技術的内容をマイペースに更新していきたいと思う。
【Python】ヴィジュネル暗号の実装

# はじめに
ヴィジュネル暗号の実装例（Python）です。

# ヴィジュネル暗号とは？
平文のアルファベットのインデックス番号と、鍵のアルファベットのインデックス番号を足し、２６で割った時の余りのインデックス番号に対応するアルファベットに変換する。

- 平文　（元の文字列）：ABCDXYZ
- 鍵（暗号化に使う文字列）：MORNING
- 暗号文（暗号化した文字列）：MPTQFLF

※ 参考図（wikipediaより引用）
![スクリーンショット 2023-05-16 19.36.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1850625/0afa41a1-ff48-42e8-4e8e-a1ea64811ec3.png)


# 実装
```python:vigenere_cipher.py
import string

# 大文字での実装
ALPHABET = string.ascii_uppercase

# 元の文字列と同じ長さの文字列を作成する関数
def generate_key(message: str, keyword: str) -> str:
    key = keyword
    remain_length = len(message) - len(keyword)
    for i in range(remain_length):
        key += key[i]
    return key

# 暗号化用の関数
def encrypt(message: str, key: str) -> str:
    result = ""
    # インデックス番号と中身（char）を分けて取得
    for i, char in enumerate(message):
        # 大文字のアルファベット以外のものがある場合、暗号化せずそのまま返す。
        if char not in ALPHABET:
            result += char
            continue

        # 暗号化((元の文字列のインデックス番号 + 鍵の文字列のインデックス番号) % アルファベットの長さ))
        index = (ALPHABET.index(char) + ALPHABET.index(key[i])) % len(ALPHABET)
        result += ALPHABET[index]

        # unicodeのコードポイントを使って実装する場合
        # result += chr((ord(message[i]) + ord(key[i])) %
        #               len(ALPHABET) + ord("A"))

    return result

# 復号化用の関数
def decrypt(cipher_text: str, key: str) -> str:
    result = ""
    for i, char in enumerate(cipher_text):
        # スペース等はスキップする
        if char not in ALPHABET:
            result += char
            continue

        index = (ALPHABET.index(char) -
                 ALPHABET.index(key[i]) + len(ALPHABET)) % len(ALPHABET)
        # 復号化したアルファベットをresultに追加していく
        result += ALPHABET[index]

        # unicodeのコードポイントを使って実装する場合
        # result += chr((ord(cipher_text[i]) - ord(key[i]) +
        #               len(ALPHABET)) % len(ALPHABET) + ord("A"))

    return result

# テスト
if __name__ == "__main__":
    # 元の文字列
    t = "OPERATION STRIX"
    # 元の文字列と同じ長さのキー（文字列）を作成
    k = generate_key(t, "BECKY")
    # print(generate_key("OPERATION STRIX", "BECKY"))
    # 作成したキーを使って暗号化する
    e = encrypt(t, k)
    print("暗号化した文字列：" + e)
    # 復号化する
    d = decrypt(e, k)
    print("復号化した文字列：" + d)

```

# 参考
https://www.udemy.com/course/python-algo
Account Engagement (旧Pardot)でLPを作った備忘録

はじめてAccount Engagement ランディングページを使ってみたところ、使う上で知りたいことがsalesforce公式のヘルプページですらまとまって置いてなくて非常に苦しんだので、自分用備忘録としてまとめておくことにしました。
なお、職場ではpi.pardot.comをみんな使っているので「Pardot」呼びで統一します。
## 前提
### 背景
- フォーム付きのLPを作る指示が出た。
- 上司「Salesforceと連携しやすいようにPardotのランディングページ機能(以下PardotLP)を使ってね」
- PardotLPとはテンプレートさえ作れば後はノーコードでランディングページを量産できる機能である。
- しかし会社の方もPardotLPを実際に使ったことがあるわけでもないので検証も兼ねて作ることになった。
### 知りたいこと
- PardotLPの編集の自由度（要素入れ替えやフォームのカスタマイズが容易か？）
- テンプレート作りがどのくらい簡単か（ダルかったら続けたくないので）
- 対応していないプロパティが無いか（HTMLメールの仕様がトラウマになっている）
- できればCSSはHTMLファイルと分けたいが可能か（styleタグ内に100行も書きたくない）
## 調べてわかったこと
- フォームを入れるときはiframeを使わなくてもHTMLファイル上でフォームを入れたい箇所に変数`%%content%%`を挿入すれば入れられる。
- フォームのHTMLタグはレイアウトテンプレート本体のHTMLタグとは別の場所に入れる（レイアウトテンプレート作成ページ内でタブが分かれている）
- Pardot上で編集可能にしたい項目は要素の開始タグに`pardot-region="わかりやすい名前"`と属性を入れるとその要素がPardotのエディタで編集可能になる。
例) `<p pardot-region="text1">lorem ipsum</p>`でpタグの要素が編集可能な要素として認識される。
挿入対象のタグによって編集内容が変化し、実際に使った範囲だと
**hタグ・pタグ**：1行テキスト（スタイル指定や改行ができない）
**imgタグ**：画像の差し替え・代替テキスト
**divタグ**：ブログやメルマガみたいな入力欄（スタイル指定や改行、画像やテーブルの挿入など自由度が高い）
ソースは若干汚れるが、テキストを入れる箇所はdivで囲うのが良さそう。

## やったこと
### CSSファイルのアップロード
CSSはPardotダッシュボードのコンテンツ>ファイルにアップロードして、発行されたファイルURLをlinkタグで読み込ませる（元々相対パスで入れていた部分と差し替える）と、レイアウトテンプレートへ読み込ませられた。
ちなみにHTMLファイルはレイアウトテンプレート作成ページのエディタにコピペで流し込むしか無い。
### フォームのHTML編集
送信ボタンの位置調整をした。
デフォルトの仕様ではフォーム内の入力エリアのすぐ下に送信ボタンが出て、その下にフォーム最下部のテキストが出る仕様になっている。
このボタンとテキストの位置を逆転させたいが、フォームメニューのレイアウトテンプレートからは項目順序を変更することができない。（罠）
ランディングページのレイアウトテンプレート編集画面で「フォーム」タブを開くと項目位置や入力エリアを囲むタグなどを変更できる。
### フォームのCSS装飾
普通に装飾と、必須項目の見え方の変更（デフォルトだと見づらい黄色で＊と出るのみ）をした。
装飾に最低限必要なclass・idは[公式のヘルプページ](https://help.salesforce.com/s/articleView?id=sf.pardot_forms_css_classes.htm&type=5)に書いてある。
各項目個別のclassはフォームを作る際に個別に設定することもできるが、毎回classを設定するのも面倒なので、一括でやれそうな方法を探した。
1. 必要な項目を満たすフォームをとりあえず作る
1. 作ったフォームをプレビューし、検証なりソースなりからclass名を見つける

かなり力押しだけどこれでいけた。
あとはそのclassに対して普通にpaddingとかborderとか設定したらフォーム本体はできあがり。
必須項目には`required`というclassが付与されているので、
```css
.required::after {
        content: "必須";
        font-weight: 600;
        color: red;
}
```
みたく書けば必須項目のみに「必須」と赤字で表示が出るようになる。
### プレースホルダーの表示
PardotLPのレイアウトテンプレート作成ではjQueryをcssと同様に使用できるので、jQueryで記述して表示させた。
idはclassと同様にソースを見ればどうにかなる。
## おわりに
- PardotLPは全然怖くないのでどんどん使うと良い
- ただ見た目や作りが凝ったもの、量産しないものは普通にスクラッチで作ったほうが早いしきれいにできる
- PardotLPが向いているのはフォームがメインかつ中身だけ変えて量産するもの。
例) ホワイトペーパー、資料請求、セミナー案内など
- Salesforceのヘルプページの構造本当にどうにかしてほしい。情報が探しづらすぎる。
【Xcode ショートカットキー】command ＋「数字」で左から選択【意外と知らない】

以下のXcodeの画像を見ると

![スクリーンショット 2023-05-16 20.01.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/359201/626a972f-19b2-09a7-1003-25971b21029b.png)

左から

「Show the Project navigator」
「Show the Source Control navigator」
「Show the Symbol navigator」
「Show the Find navigator」
「Show the Issue navigator」
「Show the Test navigator」
「Show the Debug navigator」
「Show the Breakpoint navigator」
「Show the Report navigator」

が並んでいると思います。

Xcodeを触り慣れていない初心者の方は
カーソルをわざわざ移動させて
「特定のnavigator」を選択しているかなと思います。

しかし、それでは非効率なので
command ＋「数字」で選択しましょう。

command ＋ 1 →「Show the Project navigator」
command ＋ 2 →「Show the Source Control navigator」
command ＋ 3 →「Show the Symbol navigator」
command ＋ 4 →「Show the Find navigator」
command ＋ 5 →「Show the Issue navigator」
command ＋ 6 →「Show the Test navigator」
command ＋ 7 →「Show the Debug navigator」
command ＋ 8 →「Show the Breakpoint navigator」
command ＋ 9 →「Show the Report navigator」

ちなみに、「command ＋ 0」だと以下の画像が

![スクリーンショット 2023-05-16 20.13.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/359201/004afeb5-9ce6-7fae-218f-7ba99abeee36.png)

以下の画像のように閉じます。

![スクリーンショット 2023-05-16 20.13.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/359201/b0713e2c-ae53-529b-032e-dcd48b21e39f.png)
Rubyにおけるmoduleのメソッド名衝突の挙動

# はじめに

`Ruby`における`module`のメソッド名衝突の挙動動についてまとめました

# Ruby における module とは

`module`は、コードを整理し、再利用可能な機能を定義するために使用できる、
`Ruby`のプログラミングの概念です

モジュールは`include`ステートメントを使用してクラスに取り込むことができ、
取り込まれたモジュールの定義は現在のスコープに適用されます

# 複数モジュールでのメソッド名の衝突

複数のモジュールで同じ名前の関数が定義されている場合、
**最後に取り込まれたモジュールの関数**が使用されます

```ruby
module ModuleA
  def print
    'a'
  end
end

module ModuleB
  def print
    'b'
  end
end

class Printer
  include ModuleA
  include ModuleB
end

printer = Printer.new
printer.print  # => "b"
```

## 衝突の回避 

`alias_method`を利用して、`ModuleB`をincludeする前に別名に変換する


```ruby
module ModuleA
  def print
    'a'
  end
end

module ModuleB
  def print
    'b'
  end
end

class Printer
  include ModuleA

  alias_method :printA, :print

  include ModuleB
end

printer = Printer.new
printer.print  # => "b"
printer.printA # => "a"
```

# さいごに

モジュールを使用することで、コードを整理し再利用可能な機能を定義することができますが、
複数のモジュールで同じ名前の関数が定義されていないか注意が必要です
自然言語AI 超簡単入門２

# GPTの動作原理（つづく単語の予測）

GTPは次の言葉を予測するシステムということを前回説明しました。これはどのように行われているのでしょうか、そのメカニズムを簡単な事例で説明したいと思います。

言葉は事前の情報を受けて次の言葉がある場面が多いこともあり、GPTの基本的な動作原理は、与えられた単語列に続く単語を予測することです。つまり、ある単語の後に最も確率が高いと予測される単語を選びます。この予測は、単語の周囲の単語の分布からコンテキストを把握し（単語の分布確率）、それに続く単語を予測することで行われます。

### 単語を予測する事例
「私は四川ラーメンを食べた、とても..」という文の場合、次に続く最も確率が高い言葉を予測するには、GPTはそのコンテキストを理解し、事前に学習した知識を利用します。

「四川ラーメンを食べた」に続くと、「とても」が来ると、その次に続く単語は、ラーメンの特徴や感想を表す形容詞が多くなると予測されます（これは事前学習から傾向をつかんでいたためです）。具体的には、「とても辛かった」、「とても美味しかった」、「とても独特だった」などの形容詞が来る確率が高いです。

逆に、「とても」という文脈から、具体的な名詞や動詞が直接続く確率は比較的低いと予測されます。例えば、「とても車」や「とても走る」などは使用例が低いため、GPTはこれらの選択肢を低確率と判断します。

GPTが単語を選択するときには、このように事前のコンテキストと学習データを基に最も確率が高いと判断される単語を選びます。その結果、自然でよく使われる、そして文脈に適した文を生成することができます。

### 会話がフレキシブルに予測できる＝事前情報で変化する単語予測

ところでGPTは会話に応じた回答をしています。どのようなメカニズムで動いているのかを見てみましょう。

GPTは与えられた情報に基づいて次に来る単語を予測します。したがって、前の文脈によっては、次に来る単語の予測が変わります。それを以下の例で説明します。

1.「（食べた四川ラーメンは花椒が使われている。花椒は辛いよりもしびれる。）私は四川ラーメンを食べた、とても..」

この場合、「しびれる」や「特徴的な」などの単語が予測されます。なぜなら、花椒がしびれる感じをもたらすという情報が先に与えられているからです。

2.「（食べた四川ラーメンに花椒が使われていることはしらない。花椒は辛いよりもしびれることは知らない。唐辛子は辛くそれががはいっていることだけを知っている。）私は四川ラーメンを食べた、とても..」

この場合、「辛い」や「スパイシーな」などの単語が予測される可能性が高くなります。なぜなら、唐辛子が辛さをもたらすという情報が先に与えられているからです。

これらの例から、GPTがどのように事前情報を利用して次に来る単語を予測するかがわかります。つまり、GPTは与えられた情報によって、次に来る単語の予測を動的に調整しています。


## 間違える（＝嘘を吐く）メカニズムとその対処法について

まとめますと、現在のAI（GPT）は文脈をとらえるために以下を実施しています。
文脈を掴む
・意味をとらえるために周囲の単語の分布を調べる
・そのセンテンスだけでなく長い文の出現を記憶することでその単語のニュアンスをつかむ
・センテンスなどについてはアテンションメカニズムで文脈が何に注意しているかをつかむ

文を出力する
 ・これらから次の続く単語を確率的に予測して出力する。

これらであればかなり文書ができそうですが実はうまくいかない場面がいくつかあります。
それはAIが学習しているデータとそれをどのように扱うかの問題です。
このことが原因でいわゆる「嘘をつく」という現象が起きてしまいます。
では、どのようなメカニズムで起こるのでしょうか、それを対処する方法はないのでしょうか。
次回 自然言語AI 超簡単入門３ではそれについてみてみたいと思います。




https://qiita.com/bonten999/items/8c14e694f0d6cc7ba675?fbclid=IwAR1mycpin7esNYlmjRHBaOqyMFug4EhnSb6aVX0a-b1l6PYOalBJKVmi2zk
【ターミナル】特定のリポジトリにラクにcdする方法

特定のリポジトリに移動しようとする際に
１個ずつcdするのは流石に面倒かなと思います。

そこで以下の画像のように特定のリポジトリ
(ここでは「OriginalCocktailReviewApp」)を
ドラッグしてcd の右にドロップします。

そうすると、ラクに特定のリポジトリに移動できます。

![スクリーンショット 2023-05-16 16.53.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/359201/065be1d1-e154-72e0-be32-24273d40287b.png)
ActiveRecord カラムのプライベート化

# はじめに
DB で利用しないカラムが発生して、プライベート化するということで他の方が対応していた

コードレビュー時に「ほんとにこれでいいの？」と思ったので検証および問題点の対応策を考えてみました

# ActiveRecord のカラムについて
`ActiveRecord`は、`Ruby on Rails`の一部であり、
オブジェクト指向型データベースマッピングの実装を提供します

`ActiveRecord`を使用すると、
データベーステーブルの行が Ruby オブジェクトとして扱われます

`ActiveRecord`モデルのカラムには、
通常`public`アクセス権が与えられており、どの部分からでもアクセスすることができます

# 他の方の対応内容
`ActiveRecord`では、モデルクラスの各カラムには通常、
自動的にアクセサメソッドが定義されます

しかし、その中には`DB`上で利用しなくなってしまったカラムが含まれる場合があります

このような場合、アクセサメソッドを`private`にすることで、
予期せぬ変更が加えられるのを防止することができます

検索してもこのような内容が多くでてきます


```Ruby
class Sample < ApplicationRecord
  private
  attr_accessor :unused_column
end
```

# 問題点
`attr_accessor`を`private`にしても、クラス外からはアクセスできませんが、
ハッシュ構造でアクセスする場合は`private`化が効果を発揮しません。

```Ruby
class Sample < ApplicationRecord
  private
  attr_accessor :unused_column
end

sample = Sample.new()
sample.unused_column = "foo" # => NoMethodError (private method `unused_column=' called for #<Sample:0x00007fd0cc104e10>)
sample[:unused_column] = "foo"
```


# 解決策
## hash アクセスを禁止する
`[]=`メソッドをオーバーライドして、ハッシュアクセス自体を禁止することで、
`private`なカラムを保護することができます。

```Ruby
class Sample < ApplicationRecord
  private
  attr_accessor :unused_column

  def []=(key, value)
    raise 'Hash access is not allowed' if key == :unused_column

    super
  end
end
```

## 影響範囲
テストコードのデータ生成に`FactoryBot`を使用している場合には影響があります

`FactoryBot`では`create`時に`unused_column=`のメソッドが呼び出されるため、
別の方法を使用する必要があります

例えば、以下のように`trait`を定義することで、
`create`時に`unused_column`に値を設定することができます

```Ruby
class Sample < ApplicationRecord
  def []=(key, value)
    raise 'Hash access is not allowed' if key == :unused_column

    super
  end

 private
 attr_accessor :unused_column
end

FactoryBot.define do
  factory :sample do
    trait 'unused_column_foo' do
      before(:build) do |sample|
        sample.send('unused_column=', 'foo')
      end
    end
  end
end
```

以下のように`create`メソッドを使用してサンプルオブジェクトを作成することができます

```Ruby
sample = FactoryBot.create(:sample, :unused_column_foo)
```

# おわりに
`Rails`における`ActiveRecord`カラムのプライベート化についてまとめてみました

これによって、モデルのカラムを適切に保護し予期しない変更を防ぐことができます

色々な事情でDBの構造の変更が難しいケースの場合の参考になれば
【比較】「赤のmodified」と「緑のmodified」の違い

以下の画像のように「赤 のmodified」と「緑 のmodified」があります。

![スクリーンショット 2023-05-16 17.14.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/359201/8c1279a2-5cfb-a7dc-ea49-e989f18b151a.png)

両者にはどのような違いがあるかと言うと、
「赤」が「修正されている状態」で、
「緑」が「コミット対象」という違いがあります。

「緑のコミット対象」とは以下の
XcodeのSource Controlからコミットしようとしている画像のように
チェック対象にチェックが入っているイメージです。

![スクリーンショット 2023-05-16 19.39.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/359201/63e2b068-1afd-2b6c-7eaa-f83a02a65f4f.png)
PlaywrightでHTTPリクエストをモック

webアプリをe2eでテストするときに頭を悩ませるのがAPIサーバとの通信です。
localやCI上でどこにエンドポイントを用意するかとか、
DBの状態はどうしておけばいいなど考えることがたくさんあります。

それを解決する一つの手段として、HTTPのリクエストをモックすることが考えられます。
フロントエンドからはAPIサーバと通信しているように見える状態を作ります。

### playwrightでの実装方法

playwrightでお馴染みの「page」オブジェクトを使います。

```
page.route(`http://localhost:3000/api/v1/hoge`, async (route) => {
  const method = route.request().method()
  if ('GET' === method) {
    const response = { message: 'ダミーデータだお' }
    await route.fulfill({
      status: 200,
      contentType: 'application/json',
      body: JSON.stringify(response),
    })
  }
})
```

この場合、フロントエンド側で
`http://localhost:3000/api/v1/hoge`
にGETでアクセスした時に
`{ message: 'ダミーデータだお' }`
というJSONが返されることになります。

### それってe2eテストなの？

厳密にはe2e（End to End）テストとは言えないのかも知れませんが、フロントエンドのコードベースだけで完結するのでお手軽です。

e2eとは別の呼称が欲しいですね。

##### 厳密なe2eと比べてのメリット

- apiサーバを起動しておかなくて良い
- DBを用意しなくて良い
- 通信コストがなくなるのでテストも速くなる
- 自由にAPIのレスポンスを定義できるので、レアケースの処理もテストしやすい

##### デメリット

- ダミーのデータを使うので、都合の良いシチュエーションだけの検証となりがち
- 実際のAPIレスポンスの形が変わったら、ダミーのデータも変えないといけない（ダブルメンテ）
- APIへのリクエストが正しいかの検証にはならない
STM32F767でKnight Rider(ナイトライダー)で遊ぶ



x 過去ログを見よ



D0からD7まで


![o_cop526.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/840352/fee4ad17-9808-8d85-9b07-8394e27a13f5.jpeg)




```c++




//GPIO_Knight_Rider1_test_767_1

#define DW digitalWrite 


void GPIO_D0_D7(int s)
{

  digitalWrite(7, (s>>7) & 0x01);   // turn the LED on (HIGH is the voltage level)
  digitalWrite(6, (s>>6) & 0x01);   // turn the LED on (HIGH is the voltage level)
  digitalWrite(5, (s>>5) & 0x01);   // turn the LED on (HIGH is the voltage level)
  digitalWrite(4, (s>>4) & 0x01);   // turn the LED on (HIGH is the voltage level)
  digitalWrite(3, (s>>3) & 0x01);   // turn the LED on (HIGH is the voltage level)
  digitalWrite(2, (s>>2) & 0x01);   // turn the LED on (HIGH is the voltage level)
  digitalWrite(1, (s>>1) & 0x01);   // turn the LED on (HIGH is the voltage level)
  digitalWrite(0, s & 0x01);       // turn the LED on (HIGH is the voltage level)
  
} //GPIO_D0_D7


void setup() {
  // initialize digital pin LED_BUILTIN as an output.
  pinMode(PB7, OUTPUT);
  pinMode(0, OUTPUT);
  pinMode(1, OUTPUT);
  pinMode(2, OUTPUT);
  pinMode(3, OUTPUT);
  pinMode(4, OUTPUT);
  pinMode(5, OUTPUT);
  pinMode(6, OUTPUT);
  pinMode(7, OUTPUT);


} //setup

void loop() {

  digitalWrite(PB7, HIGH);   // turn the LED on (HIGH is the voltage level)
  GPIO_D0_D7(0x01);

  delay(1000);                       // wait for a second

  digitalWrite(PB7, LOW);    // turn the LED off by making the voltage LOW
  GPIO_D0_D7(0x02);

  delay(1000);                       // wait for a second

  digitalWrite(PB7, HIGH);   // turn the LED on (HIGH is the voltage level)
  GPIO_D0_D7(0x04);

  delay(1000);                       // wait for a second

  digitalWrite(PB7, LOW);    // turn the LED off by making the voltage LOW
  GPIO_D0_D7(0x08);

  delay(1000);                       // wait for a second

  digitalWrite(PB7, HIGH);   // turn the LED on (HIGH is the voltage level)
  GPIO_D0_D7(0x10);

  delay(1000);                       // wait for a second

  digitalWrite(PB7, LOW);    // turn the LED off by making the voltage LOW
  GPIO_D0_D7(0x20);

  delay(1000);                       // wait for a second

  digitalWrite(PB7, HIGH);   // turn the LED on (HIGH is the voltage level)
  GPIO_D0_D7(0x40);

  delay(1000);                       // wait for a second

  digitalWrite(PB7, LOW);    // turn the LED off by making the voltage LOW
  GPIO_D0_D7(0x80);

  delay(1000);                       // wait for a second



} //loop



```
Flutterのin_app_purchaseで購入されたAndroid定期購入アイテムで払い戻しが行われる

## 記事を書こうと思ったきっかけ

開発しているアプリでin_app_purchaseを使用しているのですが、Androidで定期購入アイテムが購入された3日後に払い戻しが行われていました。

## 仮定
https://pub.dev/packages/in_app_purchase の右のバーから行けるRepository (GitHub)のexampleを使用していると仮定します。

## 結論
/example/lib/main.dartを開きます。

・import `'package:in_app_purchase_android/billing_client_wrappers.dart';`を記述

_listenToPurchaseUpdated関数を変更します
```main.dart
Future<void> _listenToPurchaseUpdated(
      List<PurchaseDetails> purchaseDetailsList) async {
    for (final PurchaseDetails purchaseDetails in purchaseDetailsList) {
      // 省略
      if (purchaseDetails.pendingCompletePurchase) {
        await _inAppPurchase.completePurchase(purchaseDetails);
        // 必ず追加！！追加しないと払い戻しが行われる！！
        BillingClient client = BillingClient((_) {});
        await client.acknowledgePurchase(purchaseDetails.verificationData.serverVerificationData);
      }
    }
}
```

## 大変だったこと
とにかくFlutterのAndroidの払い戻しに関する情報が少なすぎる

## 解決した方法
まず、Googleにメールで問い合わせました。すると、以下のような返事が返ってきました。

「この度は Google Play デベロッパー サポートにお問い合わせいただきありがとうございます。
お問い合わせいただきました Google Play Billing Library の購入確認の件につきまして、ご連絡いたします。Google Play Billing Library バージョン 2.0 以降をご利用の場合、すべての購入を 3 日以内に承認していただく必要がございます。購入が適切に承認されなかった場合は払い戻しが行われます。

購入をご確認になるには、まず購入ステータスが購入済みであるかどうかをチェックします。購入が保留中である場合は、保留中のトランザクションを処理するに記載されている説明に沿って、購入を処理していただく必要がございます。onPurchaseUpdated() または queryPurchases からの購入の場合は、アプリが利用権を付与する前に、購入をさらに確認していただく必要がございます。

購入をご確認になると、アプリはユーザーに利用権を付与できます。利用権を付与した後、アプリは購入を承認する必要があります。この承認により、購入の利用権を付与したことが Google Play に通知されます。

利用権を付与して購入を承認するプロセスは、消費不可アイテムの購入か、消費可能アイテムの購入か、定期購入かによって異なります。

消費可能アイテムの場合、consumeAsync() メソッドが承認要件を履行し、アプリがユーザーに利用権を付与したことを示します。このメソッドにより、アプリは 1 回限りのアイテムを再購入可能にすることもできます。
消費不可アイテムの購入を承認するには、Google Play Billing Library の BillingClient.acknowledgePurchase() または Google Play Developer API の Product.Purchases.Acknowledge をご使用ください。購入を承認する前に、アプリは Google Play Billing Library の isAcknowledged() メソッドまたは Google Developer API の acknowledgementState フィールドを使用して、購入がすでに承認されていないかをチェックする必要があります。
定期購入は、消費不可アイテムと同様に処理されます。定期購入の承認には、Google Play Billing Library の BillingClient.acknowledgePurchase() または Google Play Developer API の Purchases.Subscriptions.Acknowledge をご使用いただけます。
購入の承認に関する詳しい手順と例につきましては、技術文書「Google Play Billing Library をアプリに統合する」（https://developer.android.com/google/play/billing/billing_library_overview#acknowledge）をご参照ください。

他にご不明な点などございましたらお知らせください。

何卒宜しくお願いいたします。」

そして、BillingClient.acknowledgePurchase()に対応するInAppPurchaseのメソッドを探しました。

すると以下のページに情報がありました。
https://pub.dev/packages/in_app_purchase/versions/0.5.1+2/changelog

原文：

「Add a new BillingClient.acknowledgePurchase API. Starting from this version, the developer has to acknowledge any purchase on Android using this API within 3 days of purchase, or the user will be refunded. Note that if a product is "consumed" via BillingClient.consumeAsync, it is implicitly acknowledged.」

翻訳：

新しいBillingClient.acknowledgePurchase APIを追加します。このバージョンから、開発者はこのAPIを使用してAndroidで購入した場合、購入から3日以内に承認しなければならず、さもなければユーザーに返金されます。BillingClient.consumeAsyncによって商品が「消費」された場合、暗黙のうちに承認されることに注意してください。

## 最後に
https://pub.dev/packages/in_app_purchase

このページでacknowledgeすることの重要性を強調してくれ、、、
C++を使ってシューティングを作った話 #1

# はじめに
今回の記事は **#1 エントリーポイントからシーンの構造** についてです。
#0:https://qiita.com/komugikoShimizu/items/9e9a18232d2d5bf7db6a
#2:https://qiita.com/komugikoShimizu/items/efd31f4fa683fce4e9c1
#3:https://qiita.com/komugikoShimizu/items/6f0cc002f9f7fbd30760


# 今回の実装について
今回はまずエントリーポイントから更新処理を繋げていくような設計を目指します。
Main()
↓
SceneManagerの更新処理
↓
Sceneの更新処理

のようにUpdateが繋がれて呼ばれるものが完成形です。

また、Sceneのインスタンスを保管するのは1つだけとし、統一的に更新処理を実装できるのも条件です。

# 実際の実装
まず、Mainでwhileを回し、シーン管理クラスの更新処理を実行します。

```C++ Main.cpp
int Main()
{
    SceneManager instance = SceneManager();
    
    while (true)
    {
        instance.SceneUpdate();
    }
}
```

この様な実装をすることで、SceneManagerにも更新処理を実装することができるようになります。

```C++ SceneManager.cpp
bool SceneManager::SceneUpdate()
{
    activeScene.SceneUpdate();
}

```

これでシーンを実装する準備が整いました。

# シーンの実装
この処理をなるべく見やすく実装するために、シーンは統一的に扱いたいです。

そのために継承を使用します。

``` C++ SceneBase.cpp
class SceneBase
{
    public :
        virtual void SceneUpdate();        // シーン更新用処理
        virtual SceneType GetSceneType();  // 継承先がどのクラスであるかを知るため(enum class)
}
```
これを基底クラスとして実装して
``` C++ TitleScene.h
class TitleScene : public SceneBase
{
    public :
        void SceneUpdate() override;
        SceneType GetSceneType() override;
}
```
こうすることで　SceneBase型のポインタさえあれば
すべてのシーンを統一的な使うことが可能になりました。

ただこれではシーン移動ができないので
コンストラクタでSceneType型のポインタを取得して変更を行うことで
SceneManagerにシーン移動を伝えることができます。

```C++ TitleScene.cpp
TitleScene::TitleScene(SceneType* ptr)
{
    this->ptr = ptr;
}

void TitleScene::SceneUpdate()
{
    if (/*ここでシーン変更の条件を記述*/)
    {
        ptr->/*ここで任意のシーンのタイプ*/;
    }
}
```

このようにすることでシーン移動をすることができるようになりました。
あとはSceneManagerにその内容を記述すればシーン遷移が実装できます。


```C++ SceneManager.h
class SceneUpdate
{
    public :
        void SceneUpdate();
    private :
        SceneBase* activeScene;
        SceneType sceneType;
}
```
```C++ SceneManager.cpp
void SceneUpdate()
{
    if (sceneType != activeScene->GetSceneType())
    {
        activeScene->~SceneBase();
        
        switch(sceneType)
        {
            // ここで各種シーンをactiveSceneに設定
        }
    }
}
```

#2 へつづく...
C++を使ってシューティングを作った話

# はじめに
今回はC++を使ってシューティングゲームを作ったお話です。
**面白く作る**　というより　**自分がやってみたいことをする**
ために制作しました。

このプロジェクトは
**「普段Unityで何気なく使ってるものを実装してみたい」**
**「C++で動くゲームの基盤を作ってみたい」**
という目標で制作しています。

**#1**
エントリーポイントからシーンの構造

https://qiita.com/komugikoShimizu/items/5e66093a0a22e57e1e8c

**#2**
各種オブジェクトについて

https://qiita.com/komugikoShimizu/items/efd31f4fa683fce4e9c1

**#3**
当たり判定、アニメーションクラス

https://qiita.com/komugikoShimizu/items/6f0cc002f9f7fbd30760

に分けて記事にしていきます。

# 記事についての注意事項
* 分かりやすく伝えるために実際の記述とは違うことがあります。(意味はほぼ同じだけど)
* もっといい方法があるかもしれません
* いい実装方法を考えたぞ! というよりは　こういうのを実装したよ　のようなものに近いです。

# 製作物について
製作期間　合計2週間かかってないくらい?
間に大きな製作があったため1週間 - 1週間　くらいの期間で制作しています。

# 目標
* タイトルシーン→ゲームシーン→クリア or ゲームオーバーシーン をループする
* プレイヤーとエネミーが存在し、体力を削り切ったら勝ち
* 体力を削るのは弾で行う
* 当たり判定は円形を使用する
* 各種オブジェクトはアニメーションをする

# GitHub

https://github.com/komugikoShimizu/Shooting

※画像は抜いてあります。実行をしたい場合各自用意とコードの変更をお願いします。
GASを開こうとするとBad request Error 400の画面が出てくる

解決方法はシンプルだが、だいぶハマったのでメモしておく。
# エラーの内容
Google Apps Script(GAS)をGoogleフォームや、Googleスプレッドシートから開こうとすると、こんな画面が表示された後
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/295085/8fb94159-f8d3-94b2-80c9-501f8764b50b.png)

しばらくしてから、Bad request Errorが表示されることがある。Bad requestエラーは、めちゃくちゃ大雑把なエラーなので、初めてこの画面を見たときには、どうすればいいか結構困った。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/295085/d87cf92f-842f-a012-0f6d-802960bf0138.png)

# 原因
フォームやスプレッドシートを作成したアカウントと、ブラウザ自体にログインしているアカウントが違うため。
例えば、下の画像はGoogleフォームの画像だが、ブラウザでログインしているアカウントと、フォームにログインしているアカウントが違う。
Google Chromeは、複数のアカウントに同時ログインできるので、こんなこともできてしまう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/295085/bb36253b-8c5b-bc01-a57d-769320fc2239.png)

# 解決法
両者のアカウントを同じにすればよい。ここではブラウザ側でログインしているアカウントを変更する方法を紹介する。
Chromeのアイコンをクリックして、出てきたメニューの[ほかのプロファイル]->[追加]をクリックすればよい。後は、メールアドレスとパスワードを入れてログインすれば、OK。

# 参考動画

https://www.youtube.com/watch?v=GctW-oYLdPo
【SQL】WHERE IN で入力した順番でかつ、エスケープして取得する


CodeIgniter3とPHPとSQLの混合なのであまり参考にはならないかもです

# コード1
```SQL
    $ids = array_map('intval',explode(',',$ids));
    $hold_id = array_merge([$ids],$ids);

    global $wpdb;
    $sql="
    SELECT
        id,name
    FROM table 
    WHERE id IN ? 
    ORDER BY FIELD(id,".(implode(',',array_fill(0,count($ids),'?'))).") 
    ";
                                   ↓ここ[]を外す
    $data = $wpdb->db->query($sql,$hold_id)->result_array();
                                   ↑ここ[]を外す

    return $data;
}
```

# コード2
```SQL
    $master_ids = array_map('intval',explode(',',$ids));

    global $wpdb;
    $sql ="
    SELECT 
    id,name
    FROM table 
    WHERE id IN ? 
    ";
    $data = $wpdb->db->query($sql,[$master_ids])->result_array();

    if(empty($data)) [];

    $data = array_column($data, null, 'id');

    $res = [];
    foreach($master_ids as $key => $id){
        if(!empty($data[$id])){
            $res[] = [
                'id' => $data[$id]['id'],
                'name' => $data[$id]['name'],
            ];
        }
    }
```
Laravel Storageに動的にディレクトリ作成

# はじめに
LaravelのStorageにて動的に作成するディレクトリに関してつまずいた箇所があったので
備忘録も含めて投稿します．
ご意見などありましたらコメントいただければ嬉しいです．

# バージョン
Laravel: 9.8.1

# 要件
APIにてリクエストを受け取った際，DBデータをSqliteで出力し，ユーザーIDに応じてStorageないにディレクトリを作成，
zipファイル化し返す．
以下予想ディレクトリ

```
Storage/
    ┣ Sqlite
        └ユーザーID
            └hogehoge.db
```

# 失敗例1
Storageに追加するんだから　`Storage::makeDirectory` でいけるだろうという安直な考え

~~~
if(!Storage::exists('Sqlite/' . $ユーザーID)){
    Storage::makeDirectory('Sqlite/' . $ユーザーID);
}
~~~


テーブル作成時にエラー
~~~
Error: unable to open database xxx:unable to open database file
~~~

はぇ?
VScodeのエクスプローラーには確かにディレクトリがない
でもStorageファサードでディレクトリ取得すると新規作成されている......


権限が正しく設定できてないか?

# 失敗例2
とりあえず777で指定してみるか
権限が原因かまだわからないし

~~~
if(!Storage::exists('Sqlite/' . $ユーザーID)){
    Storage::makeDirectory('Sqlite/' . $ユーザーID, 0777);
}
~~~


変わらずテーブル作成時にエラー
~~~
Error: unable to open database xxx:unable to open database file
~~~

変わらんやんけ!!
そもそもStorageファサード使うのがダメなんか?
Fileファサード使うか

# 成功例
`File::makeDirectory` 使って `storage_path` 渡してあげればいいかな

~~~
if(!File::exists('storage_path(Sqlite/' . $ユーザーID)){
    File::makeDirectory(storage_path('Sqlite/' . $ユーザーID));
}
~~~

よっしゃ!!
エラー出てない!
ちゃんとディレクトリもファイルも生成されている!
勝利!!

# まとめ
以上,Storageファサードが無理だったらFileファサードを使ってみたらうまく行ったってお話でした．
とても初歩的な話な気もしますが......

ちなみに原因などはLaravel・PHPに関して理解が浅いので追及しきれていません．
コメントなどでご教授いただければ嬉しいです．

最後までご覧いただきありがとうございました!
GoogleサイトとGoogleスプレッドシートを連携する



# 1. はじめに
　Googleスプレッドシートの値をGoogleサイトに表示する方法を紹介します。
　方法は、Google Apps Script（GAS）を使用し、スプシの値をJOSN APIで取得できるようにし、そのAPIをGoogleサイトで使用し、スプシの値をGoogleサイトから参照できるようにします。
　

# 目次
[1.はじめに](#1-はじめに)
[2.Google Apps Scriptを編集する](#2-google-apps-scriptを編集する)
[3.GoogleサイトにHTMLを埋め込む](#3-googleサイトにhtmlを埋め込む)
[4.参考記事](#4-参考記事)

# 2. Google Apps Scriptを編集する
　スプシのタブから「拡張機能」を開き、Google Apps Scriptを選択します。
　開かれたGASのプロジェクトに以下を追記してください。またそこで使用するシートIDはスプレッドシートのリンク「https\://docs.google.com/spreadsheets/d/（シートID）」で確認できます。

```javascript
//デプロイする時は「全員」に設定する

const id = 'シートのID'
const name = 'シートの名前'

function getData() {
  var sheet = SpreadsheetApp.openById(id).getSheetByName(name);
  var rows = sheet.getDataRange().getValues();
  var keys = rows.splice(0, 1)[0];
  return rows.map(function(row) {
    var obj = {}
    row.map(function(item, index) {
      obj[keys[index]] = item;
    });
    return obj;
  });
}

function doGet(request) {
  var data = getData();
  return ContentService.createTextOutput(JSON.stringify(data, null, 2))
  .setMimeType(ContentService.MimeType.JAVASCRIPT);
}
```
　追記ができたら、右側のデプロイから「新しいデプロイ」を選択します。プロジェクトを始めてデプロイする時は承認が必要になります。
　承認完了後、開かれたウィンドウの右上の設定（歯車）から「ウェブアプリ」を選択し、生成されたURLからJSONでデータを取得できるかを確認します。問題なければ、アクセスできるユーザーを「全員」に設定し、右下のデプロイを押下します。
　これで、そのURLからスプシの値を参照することができます。

JSONデータの例
```
[ {
  "sum" : 3
} ]
```

# 3. GoogleサイトにHTMLを埋め込む
　Googleサイトの挿入タブから「埋め込む」を選択し、開かれたウィンドウで「埋め込みコード」タブを開き、以下を追記してください。
```
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<body>
<div id="data">取得中</div>
<script>
window.onload = function(){ getJsonp_GAS() }

function getJsonp_GAS() {
	$.ajax({
		type: 'GET',
		url: 'デプロイ時生成されたURL',
		success: function(json){
            var dataJS = JSON.parse(json)
            //JSONデータの0番目のsumキー（スプシのカラム名）を参照する場合
			var html =  json[0].sum ;    //3
			
			document.getElementById('data').innerHTML = html;
		}
	});
}
</script>
</body>
```

以上で、Googleサイトにスプシの値を表示することができました！
不明な点は、[参考記事](#4-参考記事)を読むと解決するかもしれません。

# 4. 参考記事
- [Googleスプレッドシートの内容をjsonデータとして利用する](https://www.koreyome.com/make-spreadsheet-to-json-at-google-apps-script/#outline__3_2)
- [JSONPの脆弱性](https://gihyo.jp/dev/serial/01/web20sec/0003)
【javascript】【jquery】のオートコンプリート(autocomplete)の一覧を最前面に表示

# なぜ必要か？

追従の検索ボックスでオートコンプリートを出す時に最前面が必要

# コード
```javascript
    'セレクタ'.find('セレクタ').autocomplete({
        source: function(request, response) {
            ///////////
            処理
            //////////
        },

       autoFocus:true,delay:200,minLength:2,
       ///////////↓これ
       open: function(){
            $(this).autocomplete('widget').css('z-index', 99999991);
            return false;
        },
        ///////////↑これ

    });

```
HTML bodyの基本構成

# Webコンテンツの区画の整理
WebページのコンテンツはHTMLのbody要素内にhタグやpタグをそのまま利用して記述することができる．しかし，コンテンツが複雑になり，要素の数が増えるとCSSやJSの実装が難しくなる場合がある．このような場合は，Webページの区画整理を行うことによって，文書構造をわかりやすくできる．

# モダンなWebサイトの基本構造
モダンなWebサイトの基本構造として以下の３つのエリアで構成されることが多く，HTMLで記述する際も同様に分ける必要がある．
* ヘッダーエリア
* メインコンテンツエリア
* フッターエリア

# 区画整理に利用されるタグ
HTML5(HTMLの改訂第5番)から登場した新しいタグを利用することによってコンテンツ内の整理がしやすくなった．コンテンツの整理に使用するため，全てbody要素内で使われる．

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3133563/00fe1937-f79f-4364-0f53-e3c256fdbf81.png)

**１．header**
Webサイトにおけるヘッダーエリアを明示するタグ．
**２．nav**
NavはWebサイト内でナビゲーションリンクであることを明示するタグ．
ナビゲーションリンクとはページ内の指定部分や他ページへの誘導を行うもの
**３．main**
HTML文書内の主要コンテンツであることを明示し，メインコンテンツエリアを担当するタグ．同一タグ内に１つしか使用することができない．
**４．section**
Webサイト内で文章のまとまりを明示するタグ．基本的に小要素に見出し(`<h1>〜<h6>`タグ)を持つことが推奨されている．
**５．article**
ブログ記事のように自己完結するコンテンツを明示する．`<article>`タグは子要素に見出しを含む形で記述する．`<article>`タグに`<article>`タグを配置することも可能
**６．aside**
サイドバーのように補助的なコンテンツを明示するタグ．広告，人気記事，新着記事，SNSシェアボタンなどのコンテンツを含んだ形で使用する．
**７．footer**
Webサイト内のフッターエリアを明示するタグ．コピーライトなどを書くのが一般的．
[解決方法]Environment Lightで背景画像を設定するもレンダリングで反映されない時

長いことハマってしまって、苦しんだのでメモしておきます。

## 問題
Environment LightのEnvironment Mapに背景画像を設定するも、
レンダリングしてみると背景部分が真っ暗で反映されない

## 解決方法
Render Light Geometryにチェックを入れる
※チェックボックスの場所：Environment Light＞Lightタブ＞Light Contributions項目の下あたり

![1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2797498/93e59a32-e9cb-3902-5b4e-bbea7db95b10.jpeg)
プリザンターでAPIから拡張SQLを実行しテーブル上のレコードを更新する

# はじめに
APIを投げた際に拡張SQLによって、プリザンターのテーブルのデータを更新する処理を走らせます。今回の機能はプリザンターにサーバスクリプトがない時からあるレガシー機能になります。そして、あまり使わない機能でもあるため、なかなか慣れない作業でした。

# 拡張SQL
まずは拡張SQLについて知らなくてはいけません。拡張SQLを使用するためには「Parameters¥ExtendedSqls」フォルダの配下にjsonファイルを下記入力内容を参考に作成します。APIから拡張SQLを実行する前段階として、新規作成したjsonファイルには、Name項目にAPIと紐づけるための名前を、Api項目に「true」を設定します。ここで注意が必要なのですが、可能であればjsonファイル名とName項目の値は一致させたほうが良いかと思います。こちらの理由に関しては後述します。
詳しくは下記ユーザーマニュアルを参照してください。
```json
{
    "Name": "Sample",
    "Api": true
}
```

#### 開発者向け機能：拡張機能：拡張SQL

https://pleasanter.org/manual/extended-sql

# APIから拡張SQLを実行
詳しくは下記ユーザーマニュアルを参照してください。
## API
下記、サンプルAPIのようにリクエストを作成しましょう。リクエストに記述されているName項目は上記で作成したjsonファイルのName項目と紐づく一意の値を設定しましょう。また、Params項目は後述するSQL文に渡す値を設定することができます。そして、キーは後述のSQL文と紐づくものとなるためスペルミスは厳禁です。
```text
{
    "ApiVersion": 1.1,
    "ApiKey": "XXXXXXXXXX...",
    "Name": "Sample",
     "Params": {
        "SiteId": 1
    }
}
```
また、APIのURLは下記になります。仮想ディレクトリを追加していなければ「{仮想ディレクトリ名}」は不要です。
```text
http://{servername}/{仮想ディレクトリ名}/api/extended/sql
```

## SQL文
下記、サンプルSQLのようにSQL文を作成しましょう。SQL文の置き場所は上記のjsonファイルと同じ「Parameters¥ExtendedSqls」フォルダの配下になります。また、WHERE句にある「@SiteId」は上記リクエストで指定した「1」が入ります。
```sql
UPDATE [Implem.Pleasanter].[dbo].[Results]
SET [ClassA] = '過去のレコード'
WHERE [SiteId] = @SiteId
AND [DateA] <= CAST(CURRENT_TIMESTAMP as date)
```

### 開発者向け機能：拡張機能：拡張SQL：APIから拡張SQLを実行する

https://pleasanter.org/manual/extended-sql-api

# まとめると・・・
上記APIリクエストを投げると拡張SQLが実行され、テーブルの[SiteId]が「1」のサイト、かつ[DateA]が今日以前が設定されているレコードの[ClassA]に「過去のレコード」というテキストを設定し、更新するという処理になります。

# 問題発生
しかし、今回お問い合わせしてくださったお客様の環境では、仮想ディレクトリを追加した環境(localhost/pleasanter)と仮想ディレクトリを追加していない環境(localhost)で挙動がおかしいというご連絡をいただきました。具体的にいは仮想ディレクトリを追加した環境では、レコードの更新がされなかったというご連絡です。私も色々調べてみましたが、わかりませんでした。正直、お手上げです・・・。そのため、実際にオンラインで画面共有をしていただきながら操作していただきました。私や先輩が見てもおかしいところはありませんでしたが、やはり動きません。そこで、弊社社長も加わり調査しましたら、なんとjsonファイルのName項目が重複していることが判明いたしました。そのため、注意事項として「jsonファイル名とName項目の値は一致させたほうが良い」と記載させていただきました。そうすることでName項目の重複を防ぐこともでき、気づくことも可能です。皆さん、気を付けましょう。

# おわりに
今回は「APIから拡張SQLを実行する」方法を学びました。これによって、APIを投げて直接DBを更新することが可能となりました。今回の件でVM環境から1つのWEBサーバーに複数のアプリケーションを入れる方法も学びました。まだまだ、改善の余地ありですが、次の記事は「1つのWEBサーバーに複数のアプリケーションを入れる方法」でも書こうかなと思っています。
Nitro Enclave + gRPC


## 試したかったこと

エンクレーブの中から、外界のgRPC サーバにアクセスできるかやってみたかった。


## 結論

下ができず、
![Screen Shot 2023-05-16 at 17.48.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/519792/0c9c2883-b933-265c-bbe3-224f0e4b885b.png)

```
<_InactiveRpcError of RPC that terminated with:
	status = StatusCode.UNAVAILABLE
	details = "failed to connect to all addresses; last error: UNKNOWN: ipv4:43.206.93.133:5555: Network is unreachable"
	debug_error_string = "UNKNOWN:failed to connect to all addresses; last error: UNKNOWN: ipv4:43.206.93.133:5555: Network is unreachable {created_time:"2023-05-16T08:42:47.988354476+00:00", grpc_status:14}"
>
```

となり、envoy 経由で試した（HTTP1.1 と２の違いとバンさんから少し聞いたため）

![Screen Shot 2023-05-16 at 17.48.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/519792/c3f9f536-a8ac-0d71-920b-fdf635ec53da.png)

が、同じエラーでできなかった。


## 再現方法

#### Docker Image  のビルド

```bash
sudo docker build -t test . --no-cache
```

#### eif ファイルのビルド

```bash
nitro-cli build-enclave --docker-uri test:latest --output-file secure-test.eif
```


### vsock-proxy の立ち上げ


```vsock-proxy.yml
allowlist:
- {address: your_grpc_host, port: your_grpc_port}
```

```
vsock-proxy 8001 your_grpc_host your_grpc_port --config vsock-proxy.yaml
```

待機状態になります。


### エンクレーブの立ち上げ 

```
nitro-cli run-enclave --cpu-count 2 --memory 3072 --eif-path secure-test.eif --debug-mode
```

以下のログが出力されます。

```
{
  "EnclaveName": "secure-channel-example",
  "EnclaveID": "i-07bdc1796ceb8d416-enc18823416037a232",
  "ProcessID": 4680,
  "EnclaveCID": 16,
  "NumberOfCPUs": 2,
  "CPUIDs": [
    1,
    3
  ],
  "MemoryMiB": 3072
}
```


#### debug モードでenclave の標準出力表示

```
nitro-cli console --enclave-id i-07bdc1796ceb8d416-enc18823416037a232
```



#### client

```
python3 client.py client 17 5005 "test"
```

```
<_InactiveRpcError of RPC that terminated with:
	status = StatusCode.UNAVAILABLE
	details = "failed to connect to all addresses; last error: UNKNOWN: ipv4:35.78.69.89:5555: Network is unreachable"
	debug_error_string = "UNKNOWN:failed to connect to all addresses; last error: UNKNOWN: ipv4:35.78.69.89:5555: Network is unreachable {created_time:"2023-05-16T06:48:20.697558034+00:00", grpc_status:14}"
>
```
RubyでAtCoder ABC301(A, B, C)を解いてみた

# はじめに
Webエンジニアを目指して、RubyやRailsをいじってます。
今回は、RubyでAtCoder ABC301のA, B, Cを解きました。備忘録として解き方をまとめていきたいと思います。

# A - Overall Winner
https://atcoder.jp/contests/abc301/tasks/abc301_a
```ruby:a-301.rb
n = gets.to_i
s = gets.chomp
t = s.count("T")
a = s.count("A")

if t > a
  puts "T"
elsif t < a
  puts "A"
else
  puts s.rindex("T") < s.rindex("A") ? "T" : "A"
end
```
## 解説
sに含まれるT, Aの数で場合分けをします。TとAの数が同じである場合には、rindexメソッドを使ってどちらがその数に先に達しているかを判定しています。

# B - Fill the Gaps
https://atcoder.jp/contests/abc301/tasks/abc301_b
```ruby:b-301.rb
n = gets.to_i
a = gets.split.map(&:to_i)

ans = []
a.each_cons(2) do |pre, nxt|
ans << pre
  if pre < nxt
    (pre + 1).upto(nxt - 1) { |i| ans << i }
  else
    (pre - 1).downto(nxt + 1) { |i| ans << i }
  end
  # if文の部分は以下のようにも書ける
  # ans.concat pre.step(nxt, by: nxt <=> pre).to_a[1..-2]
end
ans << a[-1]
puts ans.join(" ")
```
## 解説
each_consメソッドを使って、隣り合う要素について差が1となるように要素を追加することで実装することができます。

# C - AtCoder Cards
https://atcoder.jp/contests/abc301/tasks/abc301_c
```ruby:c-301.rb
s = gets.chomp.chars.tally
s.default = 0
t = gets.chomp.chars.tally
t.default = 0
array = %w(a t c o d e r)

("a".."z").each do |c|
  next if s[c] == t[c]
  if s[c] > t[c]
    if s[c] <= t[c] + t["@"] && array.include?(c)
      t["@"] -= s[c] - t[c]
    else
       puts "No"
       exit
    end
  else
    if t[c] <= s[c] + s["@"] && array.include?(c)
      s["@"] -= t[c] - s[c]
    else
      puts "No"
      exit
    end
  end
end
puts "Yes"
```
## 解説
（他の方の提出結果を参考にしました）

tallyメソッドを使って、与えられた文字列それぞれに含まれる英小文字の数を保持する連想配列を生成します。そして、@を使うことでsとtに含まれる`atcoder`の文字いずれかの数を合わせることができるかを調べることで実装することができます。

https://qiita.com/shoya15/items/50fd17e37d7c64f2b166
イラレ初心者がパパッとパターン背景作り

こんな感じの背景がほしいのに、見つからない。
そんなときにパパッと作れれば！

そこで、これならすぐできそう！
という時間勝負でイラレ初心者の私のための背景作りです☺

# 1.Illustratorを起動
起動しないと作業ができないので起動していきます！

# 2.なんでも良いので素材を用意
これについてはその時の使いたいものの雰囲気によるのですが、本当になんでもいいです！
今回は星で作っていきます🌟
（見た目がちょっとほ○た○よみたいですが気にせず作っていきます👌）
![アセット 1@2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3343818/0998ae22-fd70-2233-4b34-b8c03b30f3fd.png)

# 2.スウォッチに素材を入れる
パネル内のスウォッチに素材を「ドラックアンドドロップ」します！
![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3343818/25dbf7f7-a05f-16c2-abb8-e597398d369e.png)

# 3.編集する
星がスウォッチに入っていることを確認したら「ダブルクリック」します。
すると「パターンオブジェクト」のタブが出てきますのでこちらを編集していきます！
![アートボード 1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3343818/165801ae-c58a-397f-82fa-ad1cb662b049.png)
まずはパターンの並び方を選んでいきます。
「タイルの種類」の「グリット」の部分をクリックするとタイルの種類が選択できます。
![アートボード 2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3343818/6f503db1-9546-b5f0-dfb1-2de95e5b7583.png)

並び方は全部で5種類です！
![アートボード 3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3343818/54f3022b-0534-f79e-7cff-7de80364dc76.png)

並び方の違いで随分と変わって見えますね🤔
今回は無難にグリットでやっていきます！

「パターンオブジェクト」に戻り、今度は赤枠のところをクリックすると
青い選択範囲が出てくるので、そこを「shift」を押しながらドラックしていきます。

![スクリーンショット 2023-05-12 141102.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3343818/12f28847-e814-b556-badd-3061e6632541.png)
そうすると下記のように均等に間隔を広げることができます！
![感覚広げる.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3343818/b209f8c0-5ff5-4dab-54e7-563078e0768c.png)
そしてもう一度赤枠のところをクリックし元に戻すと赤い選択範囲が現れて
それを回すと星の回転をかけられます！
青い選択範囲の中に別のものや色違いのものを入れると
それも一緒にパターンになってくれます👏
（青い枠にかかっていればOKです！素敵機能ですね☺）

![スクリーンショット 2023-05-12 191220.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3343818/1861f7d5-eb3e-2c02-265d-c9a6c8224fec.png)
これで後は上にある「完了」をクリックし「長方形」に持ち替えてドラックするだけです！！
![スクリーンショット 2023-05-12 142811.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3343818/478f9082-3ae4-907c-a92e-a57aadab0813.png)
簡単で可愛いです☺

下記は同じ方法で作成したパターンです！
図形でも文字でもアイコンでも簡単に可愛いパターン背景になります☺
![アセット 9@2x.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3343818/4cbc229c-950f-8d28-71e6-6cdc0307b7c7.png)

この作り方でいろんなパターンを作成できるので
ぜひやったことない方はチャレンジしてください🌟
What's New from CircleCI - 2023/05/16

CircleCIに関する最近2週間(2023/05/09~16)くらいの情報をまとめてみました。

気のつく限りの情報は拾ってみたつもりですが、見落としなどありましたら、Twitterで [@CircleCIJapan](https://twitter.com/CircleCIJapan) をメンションしてタレ込んでいただけると幸いです。
少しでもお役に立てていましたら、いいね(❤️)クリックいただけると嬉しいです！

[＜ What's New from CircleCI - 2023/05/09](https://qiita.com/CircleCIJapan/items/91126e52da2e719a51a0) | 今週 | [What's New from CircleCI - 2023/05/23＞](https://qiita.com/CircleCIJapan/items/70ac63c78605f30eba62)

# ユーザーブログ・スライド
- CircleCI活用ブログ
    - [医療系スタートアップのバックエンドをモノレポ化した話 〜技術編〜](https://dev.henry.jp/entry/backend-monorepo-part-2) by 株式会社ヘンリー [@Kengo_TODA](https://twitter.com/Kengo_TODA) さん 岩永さん
    - [CircleCIのphp8.1.19だとphpstanが失敗する](https://zenn.dev/third_tech/articles/158a33d2755b95) by 株式会社THIRD sakamotchi8 さん

# Changelog (更新履歴)
- クラウド版の更新履歴: https://circleci.com/ja/changelog/
    - 05/04 [2023 May Android images update](https://circleci.com/changelog/#2023-may-android-images-update)
- サーバー版の更新履歴: https://circleci.com/ja/server/changelog/
- セルフホストランナーの更新履歴: https://circleci.com/ja/changelog/self-hosted-runner/
- プロダクト ロードマップ(今後の提供予定機能): https://circleci.com/ja/product-roadmap/

# CircleCI Discuss (CircleCIからのお知らせ＆コミュニティ掲示板)
- 日本語アナウンス: https://discuss.circleci.com/tag/ja-%E6%97%A5%E6%9C%AC%E8%AA%9E
    - 05/16 [[プロダクトリリース] “Only build pull requests ” 設定のオーバーライド機能](https://bit.ly/457xrq3)

# CircleCI 公式ブログ
- [日本語ブログ](https://circleci.com/ja/blog/)
- [英語ブログ](https://circleci.com/blog/)
    - [Role-based credential management with OIDC](https://circleci.com/blog/role-based-credential-management-with-oidc/)
    
# YouTube (動画コンテンツ)
- [CircleCI チャンネル (日本語)](https://youtube.com/playlist?list=PL9GgS3TcDh8x71MTG2uvZ-ljhIkuveSHZ)
- [CircleCI Channel (English)](https://www.youtube.com/c/CircleCI-Videos)
    - [A CTO & CFO Approved Guide to Managing Your Tech Spend in Tough Economic Times](https://youtu.be/jvPACZTKtfc)

# CircleCI Knowledge Base
- [CircleCI ナレッジベース (日本語)](https://support.circleci.com/hc/ja)
- [CircleCI Knowledge Base (English)](https://support.circleci.com/hc/en-us/)
    - [How to delete a Self-Hosted Runner Resource Class](https://support.circleci.com/hc/en-us/articles/15418352844187-How-to-delete-a-Self-Hosted-Runner-Resource-Class-)
    - [How to add additional Default Branches](https://support.circleci.com/hc/en-us/articles/15222074173723-How-to-add-additional-Default-Branches)
    - [How can I dynamically select branches for a scheduled pipeline?](https://support.circleci.com/hc/en-us/articles/15334848949403-How-can-I-dynamically-select-branches-for-a-scheduled-pipeline-)
---
それでは今週も Happy Building⌨!
cloud-initが動いていると/etc/hostsの変更が再起動で元に戻ってしまう

# 事象
`/etc/hosts`ファイルに新たにエントリを追加していたものが、再起動したら消えてしまった。

# 原因
cloud-initが起動時にテンプレートファイルで上書きしていた。

# 対応
cloud-initが`/etc/hosts`を更新しないように設定する方法がよく出てきますが、今回はテンプレートファイルを修正することで対応しました。

1. `/etc/hosts`の中身を見て、テンプレートファイルを特定します。
    下記の例では`/etc/cloud/templates/hosts.debian.tmpl`となります。
    ```
    $ cat /etc/hosts
    # Your system has configured 'manage_etc_hosts' as True.
    # As a result, if you wish for changes to this file to persist
    # then you will need to either
    # a.) make changes to the master file in /etc/cloud/templates/hosts.debian.tmpl
    # b.) change or remove the value of 'manage_etc_hosts' in
    #     /etc/cloud/cloud.cfg or cloud-config from user-data
    #
    127.0.1.1 microk8s-dmz-0.maas microk8s-dmz-0
    127.0.0.1 localhost
    
    # The following lines are desirable for IPv6 capable hosts
    ::1 localhost ip6-localhost ip6-loopback
    ff02::1 ip6-allnodes
    ff02::2 ip6-allrouters
    ```
1. テンプレートファイルを修正します。
    あとは普通に編集するだけ。
    ```
    $ sudo vim /etc/cloud/templates/hosts.debian.tmpl
    ```

これで再起動しても設定が消えなくなりました！
Mac M2でhdf5のインストール

#### 問題
使いたいpythonモジュールでhdf5が必要だった。
```
brew install hdf5@1.8
export HDF5_DIR=/opt/homebrew/Cellar/hdf5@1.8/1.8.22_2
```
で指定してpipするも、以下のようにhdf5の場所がわからないと言われる。
```
Collecting tables>=3.0 (from ******)
  Using cached tables-3.8.0.tar.gz (8.0 MB)
  Installing build dependencies ... done
  Getting requirements to build wheel ... error
  error: subprocess-exited-with-error

  × Getting requirements to build wheel did not run successfully.
  │ exit code: 1
  ╰─> [10 lines of output]
      ld: library not found for -lhdf5
      clang: error: linker command failed with exit code 1 (use -v to see invocation)
      cpuinfo failed, assuming no CPU features: 'flags'
      * Using Python 3.9.5 (default, Jan 24 2022, 16:49:12)
      * Found cython 0.29.34
      * USE_PKGCONFIG: True
      .. ERROR:: Could not find a local HDF5 installation.
         You may need to explicitly state where your local HDF5 headers and
         library can be found by setting the ``HDF5_DIR`` environment
         variable or by using the ``--hdf5`` command-line option.
      [end of output]

  note: This error originates from a subprocess, and is likely not a problem with pip.
error: subprocess-exited-with-error

× Getting requirements to build wheel did not run successfully.
│ exit code: 1
╰─> See above for output.

note: This error originates from a subprocess, and is likely not a problem with pip.
```
#### 解決
これが正解だった。
```
brew install hdf5
export HDF5_DIR="$(brew --prefix hdf5)"
```

##### 参考リンク
https://github.com/mit-han-lab/torchquantum/issues/17