OmniAICreator/Qiita-1.07M huggingface dataset:
Rubyの継承と定数を組み合わせるとどうなるか？

みなさん、継承についてはよくご存知のはずです。

サブクラスで基底クラスと同じ名前のメソッドを定義すると、サブクラスのメソッドが呼び出されます。

では、サブクラスで規定クラスと同じ名前の**定数**を定義するとどうなるでしょうか？

# 結果

`FOO` のように定数を直接参照するとメソッドが定義されたクラスの定数が参照されます。

`self.class::FOO`のように参照すると、selfのクラス（子クラス）の定数が参照されます。

```ruby
class Parent
  FOO = "parent"

  def refer_foo
    FOO # 常に Parent::FOO に解決される
  end

  def refer_self_class_foo
    self.class::FOO # 常に Parent::FOO または Sub::FOO に解決される
  end
end

class Sub < Parent
  FOO = "sub"
end

puts "refer_foo"
p Parent.new.refer_foo # => "parent"
p Sub.new.refer_foo # => "parent"

puts "refer_self_class_foo"
p Parent.new.refer_self_class_foo # => "parent"
p Sub.new.refer_self_class_foo # => "sub"
```
Accompanist無しで無限スクロールを実装する



:::note warn

本記事の内容はすべて以下のバージョンを前提とします

- Kotlin 1.9.22
- Compose Compiler 1.5.11
- Acoompanist 0.34.0
- Compose BOM 2024.03.00  
  (androidx.compose.foundation:foundation-android:1.6.4)
:::

`LazyLayout`を利用した擬似的では無い実装方法も投稿しました

https://qiita.com/Seo-4d696b75/items/3d597ba1b677e657cc94

--------

# 前置き

Composeで無限スクロールを実装しようと調べると、Accompanist + ページサイズ`Int.MAX_VALUE`の実装例が多く見つかります。しかし[accompanist-pager](https://github.com/google/accompanist/tree/main/pager)は現時点で非推奨になっており、Compose本家の`androidx.compose.foundation.pager.HorizontalPager`を使って同様に実装してみるとANRが発生します....

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/440643/746ce2fc-5fdf-a1da-da1d-6d4a79861644.png" width="300">

Profilerで見てみると何だかヤバそうな雰囲気です。`Int.MAX_VALUE`の代わりに小さめな数字を指定すると動く場合も確認しましたが、端末によってANRを起こす閾値がまちまちで安全な数字は分からずじまいでした。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/440643/cf0229dd-b63e-4141-5474-56b8d852b457.png" width="500">



# 完成品

Accompanistの代わりに`androidx.compose.foundation.*`を使いちゃんと動作する水平方向の無限スクロールを実装します

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/440643/a15474e9-1d40-0d76-69d2-22e9e2467d52.gif" width="300">


# 実装の説明

https://github.com/Seo-4d696b75/compose-loop-pager/tree/horizontalpager


## 基本実装

実際のリストサイズより多くページ数を指定する点は従来と同じですが、サイズの３倍程度に抑えておきます。

```kotlin
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun HorizontalLoopPager(
    count: Int,
    modifier: Modifier = Modifier,
    content: @Composable PagerScope.(page: Int) -> Unit,
) {
    val state = rememberPagerState(
        initialPage = count,
        pageCount = { count * 3 },
    )

    HorizontalPager(
        state = state,
    ) {
        // ダミーインデックスを正規化する
        val index = it.mod(count)
        content(index)
    }
}
```

## 擬似的な無限スクロール実装

そのままではページ末端に到達するとそれ以上スクロールできません。そこでページ位置が両端に近づいたら、真ん中の位置にアニメーション無しでジャンプさせる方針を採用します。

:::note info
ユーザー操作・アニメーション中の考慮

`PagerState.currentPage`は途中でも変化するため`settledPage`を使っています。加えてスクロール中はジャンプをスキップするよう制御して自然なユーザー体験に配慮しています。
:::

```kotlin

@OptIn(ExperimentalFoundationApi::class)
@Composable
fun HorizontalLoopPager(
    count: Int,
    modifier: Modifier = Modifier,
    content: @Composable PagerScope.(page: Int) -> Unit,
) {
    val state = rememberPagerState(
        initialPage = count,
        pageCount = { count * 3 },
    )

    LaunchedEffect(state) {
        // 現在のページ位置を監視
        snapshotFlow { state.settledPage to state.isScrollInProgress }
            .filter { !it.second }
            .map { it.first }
            .collectLatest {
                // 必要ならアニメーション無しでページ位置をジャンプ
                when {
                    it < count -> it + count
                    it >= count * 2 -> it - count
                    else -> null
                }?.let { idx ->
                    launch {
                        state.scrollToPage(idx)
                    }
                }
            }
    }

    // ..中略..
}
```

## TODO
この実装は完璧ではありません。例えば、素早いスクロール操作を繰り返すとジャンプで戻る前にページ末端に到達していまいます。
[Golang] VSCodeのGoプラグインを0.40.3 -> 0.41.xに更新したらブレークポイントデバッグができなくなった

# 環境

* golangで開発したAPIサーバーをDockerコンテナとして稼働させており、その中にデバッガdelveをインストールしてある。

* delveとのデバッグ用通信ポート2345をローカル(Mac)側に外出ししてある。

* Dockerコンテナ内のアプリケーションディレクトリは `/app`。

* VSCodeでブレークポイントデバッグするときは、Devcontainerでコンテナの中に入ったりはせず、ローカルから直接2345ポートに接続してデバッグしている。

```docker-compose.yml
services:
    app:
        container_name: app
...
        ports:
            - 2345:2345
            - 8080:8080
...
```

↓ VSCodeでブレークポイントデバッグを実行するための設定。
```launch.json
{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "debug app",
      "type": "go",
      "request": "attach",
      "mode": "remote",
      "remotePath": "/app",
      "cwd": "${workspaceRoot}/app",
      "port": 2345,
      "host": "localhost"
    }
  ]
}
```

# 問題
VSCodeのGoプラグイン 0.40.3 までは↑のlaunch.jsonでブレークポイントデバッグが出来ていたが、0.41.xに上げたら動作しなくなった。

どうやら
```
"remotePath": "/app",
```
が無視されて、ローカルの `${workspaceRoot}/app` (例：`/Users/yagrush/workspace/app`) と同じパス以下を探しに行って「そんなパス無いよ！」と怒られている様子。

# 解決方法
launch.jsonを見直しした。
```
      "remotePath": "/app",
```
↓
```
      "substitutePath": [{ "from": "${workspaceRoot}/app", "to": "/app" }],
```
無事動くようになりましたとさ。
C#でMarkdownをWord文書およびPDFファイルに変換する方法

Markdownは、シンプルで読みやすく、文法が明確な特徴から、多くのプログラマーや技術文書作成者に好まれています。しかし、特定のシナリオでは、Markdown形式のドキュメントを、豊富なレイアウト機能とフォーマット制御能力を持つWord文書に変換したり、印刷や閲覧に適したPDFファイルを生成したりする必要があります。.NETプラットフォームを使用して、C#コードを使用して**MarkdownコンテンツをWord文書またはPDFファイルに簡単に変換**することができます。

- **[C#を使用してMarkdownファイルをWord文書に変換する方法](#cを使用してmarkdownファイルをword文書に変換する方法)**
- **[C#を使用してMarkdownファイルをPDFファイルに変換する方法](#cを使用してmarkdownファイルをpdfファイルに変換する方法)**

この記事では、[Spire.Doc for .NET](https://jp.e-iceblue.com/download/spire-doc-for-net.html)を使用する方法を紹介します。Spire.Doc for .NETは、公式ウェブサイトから入手するか、NuGetを通じてインストールすることができます（`pip install Spire.Doc`）。

[無料ライセンスの申請](https://www.e-iceblue.com/TemLicense.html)

## C#を使用してMarkdownファイルをWord文書に変換する方法
Markdownファイルには、画像がリンク形式で保存されているため、画像を含まないMarkdownファイルを直接Word文書に変換することが適しています。画像を含む場合は、変換後に画像をさらに処理する必要があります。
以下は、MarkdownファイルをWord文書（.docx）に変換する手順です：
1. 必要なモジュールをインポートします。
2. **Document** オブジェクトを作成します。
3. **Document.LoadFromFile(string fileName, FileFormat.Markdown)** メソッドを使用してMarkdownファイルを読み込みます。
4. **Document.SaveToFile(string fileName, FileFormat.Docx)** メソッドを使用してMarkdownファイルをWord文書に変換して保存します。
5. リソースを解放します。

**コードの例**
```c#
using Spire.Doc;

namespace MdToDocx
{
    class Program
    {
        static void Main(string[] args)
        {
            // Documentオブジェクトの作成
            Document doc = new Document();

            // Markdownファイルの読み込み
            doc.LoadFromFile("サンプル.md", FileFormat.Markdown);

            // MarkdownファイルをWord文書に変換
            doc.SaveToFile("MarkdownからWordへの変換.docx", FileFormat.Docx);
            doc.Close();
        }
    }
}
```

**変換結果**
![C#を使用してMarkdownファイルをWord文書に変換する方法](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/278784/b46a3d0c-2dcc-631c-32da-6f9dc3fd776a.png)

## C#を使用してMarkdownファイルをPDFファイルに変換する方法
Word文書への変換だけでなく、Markdownファイルを直接PDFファイルに変換することもできます。以下は、MarkdownファイルをPDFファイルに変換する手順です：
1. 必要なモジュールをインポートします。
2. **Document** オブジェクトを作成します。
3. **Document.LoadFromFile(string fileName, FileFormat.Markdown)** メソッドを使用してMarkdownファイルを読み込みます。
4. **Document.SaveToFile(string fileName, FileFormat.PDF)** メソッドを使用してMarkdownファイルをPDFファイルに変換して保存します。
5. リソースを解放します。

**コードの例**
```c#
using Spire.Doc;

namespace MdToDocx
{
    class Program
    {
        static void Main(string[] args)
        {
            // Documentオブジェクトの作成
            Document doc = new Document();

            // Markdownファイルの読み込み
            doc.LoadFromFile("サンプル.md", FileFormat.Markdown);

            // MarkdownファイルをPDFファイルに変換
            doc.SaveToFile("MarkdownからPDFへの変換.pdf", FileFormat.PDF);
            doc.Close();
        }
    }
}
```

**変換結果**
![C#を使用してMarkdownファイルをPDFファイルに変換する方法](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/278784/b287a0ee-1c37-2153-0053-bbd01d457137.png)

この記事では、.NETプラットフォームを使用してC#コードでMarkdownファイルをWord文書およびPDFファイルに変換する方法を説明しました。手順とコードの例を提供しています。

より詳しいWord文書の操作方法については、[Spire.Doc for .NETのチュートリアル](https://jp.e-iceblue.com/tutorials/net/spire-doc-net/doc-net-program-guide-content.html)をご覧ください。
セグメントツリー(C言語貼り付け用)

すぐに見つからなかったので自分用

# 以下をmainの上あたりに貼り付け

```c
typedef struct str_segtree
{
    int (*op)(int, int);
    void (*change)(int, int);
    int (*query)(int, int);
    void (*scan)(void);
    void (*check)(void);
    int *data;
} segtree_t;

void segtree_scan(void);
void segtree_change(int a, int b);
int segtree_query(int a, int b);
int segtree_query_internal(int a, int b, int k, int l, int r);
void segtree_check(void);
static int seg_n;
static int *seg_dat;
static int seg_e;
segtree_t *segtree_;

segtree_t *seg_init(int n, void *func, int e)
{
    int i;
    seg_n = 1;
    seg_e = e;

    while (seg_n < n)
        seg_n *= 2;
    segtree_ = malloc(sizeof(segtree_t));
    seg_dat = malloc(sizeof(int) * (2 * seg_n - 1));
    for (i = seg_n - 2 + n; i < (2 * seg_n - 1); i++)
    {
        seg_dat[i] = e;
    }
    segtree_->change = segtree_change;
    segtree_->op = (int (*)(int, int))func;
    segtree_->scan = segtree_scan;
    segtree_->check = segtree_check;
    segtree_->query = segtree_query;
    segtree_->data = &seg_dat[seg_n - 1];
    return segtree_;
}

void segtree_scan(void)
{
    int i;
    for (i = seg_n - 2; i >= 0; i--)
    {
        seg_dat[i] = segtree_->op(seg_dat[2 * i + 1], seg_dat[2 * i + 2]);
        // printf("%d -> %d ^ %d\n", i, seg_dat[2 * i + 1], seg_dat[2 * i + 2]);
    }
}

void segtree_change(int a, int b)
{
    a += (seg_n - 1);
    seg_dat[a] = b;
    while (a > 0)
    {
        a = (a - 1) / 2;
        seg_dat[a] = segtree_->op(seg_dat[2 * a + 1], seg_dat[2 * a + 2]);
    }
}

int segtree_query(int a, int b)
{
    return segtree_query_internal(a, b, 0, 0, seg_n);
}

int segtree_query_internal(int a, int b, int k, int l, int r)
{
    int vl, vr;
    if (r <= a || b <= l)
        return seg_e;
    else if (a <= l && r <= b)
        return seg_dat[k];
    else
    {
        vl = segtree_query_internal(a, b, 2 * k + 1, l, (l + r) / 2);
        vr = segtree_query_internal(a, b, 2 * k + 2, (l + r) / 2, r);
        return segtree_->op(vl, vr);
    }
}
void segtree_check(void)
{
    int i;
    printf("check:\t");
    for (i = 0; i < seg_n * 2 - 1; i++)
    {
        printf("%d ", seg_dat[i]);
    }
    printf("\n");
}
```

# 使い方
https://atcoder.jp/contests/abc185/tasks/abc185_f
の場合

```c
int func(int a, int b)
{
    return a ^ b;
}

int main(void)
{
    int n, q;
    int a;
    int *t, *x, *y;
    segtree_t *seg;
    int i;

    scanf("%d %d", &n, &q);
    seg = seg_init(n, func, 0);
    for (i = 0; i < n; i++)
    {
        scanf("%d", &a);
        seg->data[i] = a;
    }
    // seg->check();

    seg->scan();

    // seg->check();

    t = malloc(sizeof(int) * q);
    x = malloc(sizeof(int) * q);
    y = malloc(sizeof(int) * q);
    for (i = 0; i < q; i++)
    {
        scanf("%d %d %d", &t[i], &x[i], &y[i]);
    }

    for (i = 0; i < q; i++)
    {
        if (t[i] == 1)
        {
            seg->change(x[i] - 1, seg->data[x[i] - 1] ^ y[i]);
        }
        else
        {
            printf("%d\n", seg->query(x[i] - 1, y[i]));
        }
    }

    return 0;
}

```

右端は含まないindexなので -1しない（t[i] != 1のとき）。

----
# 参考
https://tsutaj.hatenablog.com/entry/2017/03/29/204841
スナップショットとは？

## 初めに
今回はEBSのバックアップで出てくるスナップショットについて書いていきます。

## スナップショットとは？
EBSで利用されているバックアップ機能の事です。
※前回、[EBS](https://qiita.com/h3iak7i/items/003c4acc053caaa2fa39)に関してまとめてみました。ぜひご参考に！

##### (図解)
![スクリーンショット 2024-03-29 16.34.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3744656/ce2e50b8-34d7-8d34-911d-794ceb7f17fd.png)

バックアップされたデータはS3に保存されます。この領域に関してはAWS管理なのでユーザーから見る事ができないです。

## 仕組み
次にどういった仕組みでバックアップを取得しているのか、解説させて頂きます。
簡潔に言いますと、差分(変更点)をバックアップとして保存していくイメージです。

##### (図解)
![スクリーンショット 2024-03-29 16.39.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3744656/4f0eb54b-21aa-6f28-e741-f278074d115d.png)
通常のコピーですと、全てのデータをコピー・保存をしていきます。(ファイルコピーなど)

ただ、スナップショットは、上記の図解のように、2回目以降は1回目との差分のみを保存しています！
(変更されていない部分は、過去データを参照する)

## メリットとデメリット
【メリット】
1.容量が小さくて済む。
2.短時間で取得が可能

【デメリット】
1.過去のスナップショットが必要
→変更されていないデータは元データや過去のスナップショットからになる。

2.ディスクの故障でデータが失う可能性有り

※過去の操作時のデータには戻れるが、データ喪失に対する対策にはならない。

## 活用例
・データ分析
・画像
・暗号資産... など

一時的なものを保管する際に活用されています。

## 最後に
今回はスナップショットに関して解説させて頂きました。
データを丸ごとコピーをしないので、少ない容量で済むのと、短時間で取得出来ます。
ただ、差分のみの保存のため、元データが紛失してしまうとデータの復元が出来ないので注意しましょう！
【Next.js】 markdownで書かれた記事の目次を実装する。

# 概要
**Next.js** プロジェクトにおいて **markdown** で書かれた記事の目次を実装する方法を残しておきます。
記述の細かい説明等には触れておりません。

# 動作確認端末
- MacBook Air 2021(8GB)

# 環境
- Next.js 13.4
- React 18
- TypeScript 5.0

# 前提
下記の状態であることを前提としています。
- markdownで書かれた記事のデータが取得できる
- その上でmarkdown->htmlへの変換もできている

# 実装
この章では実装について説明していきます。

## Cheerioの導入
**cheerio** を使うのでプロジェクトに入っていない方はinstallしましょう
**cheerio** は、サーバーサイドで **HTML** を操作するための **jQuery** ライクなライブラリです。**Markdown** からHTMLへの変換後、 **HTML** から目次情報を抽出するために使用します。インストールは以下のコマンドで行います。

```bash:Terminal
npm install cheerio
```

## 目次のレイアウト等々
実際にcheerioを使う記述やレイアウトのついて言及していきます。

```tsx:page.tsx
~~~~~ 略 ~~~~~~~

//目次機能
const $ = cheerio.load(html);
const headings = $('h1, h2').toArray();
const toc = headings.map((element) => ({
    text: $(element).text(),
    id: (element as any).attribs.id,
    tag: (element as any).tagName,
}));

~~~~~ 略 ~~~~~~~~

<div className='p-5 rounded-lg table-contents'>
    <h1 className='text-2xl mb-5 font-bold'>目次</h1>
    <ul className='pl-2 scroll_bar'>
      {toc.map((data) => (
        <a href={`#${data.id}`}>
          <li
            key={data.id}
            className={`${
              data.tag == 'h2' ? 'ml-5' : data.tag == 'h3' ? 'ml-10' : 'ml-1'
            } mb-2 hover:bg-gray-500 rounded p-0.5`}
          >
            {data.tag == 'h1' ? data.text : '-' + data.text}
          </li>
        </a>
      ))}
    </ul>
</div>
```

# 完成
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2524017/a4f04ac8-19fc-84b9-bc00-ca878fd13c5b.png)

# おわりに
備忘録的な記事なので若干適当ですが、参考になれば幸いです。
実際に目次が実装されているブログは下記になります。

https://kt-tech.blog
Mecabの付き合い方（Python)

mecab-python3のカナ変換しか使っていない、個人の感想です。

![Default_Two_friends_walk_handinhand_Their_backs_are_to_us_as_t_0_5e83ac54-534a-4fd8-a3d7-0b6963608238_0.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/54688/40edc954-9e18-5b0c-1a63-9fb28d98e78b.jpeg)

## unidicは入れない
unidicを先に見ます。でも私はunidic_liteの方をよく使います。
```
def try_import_unidic():
    """Import unidic or unidic-lite if available. Return dicdir.

    This is specifically for dictionaries installed via pip.
    """
    try:
        import unidic
        return unidic.DICDIR
    except ImportError:
        try:
            import unidic_lite
            return unidic_lite.DICDIR
        except ImportError:
            # This is OK, just give up.
            return
```

それに、Mecabが、attributeエラーをcatchしてくれないので、unidicの削除忘れで、ある日悩むことになる。
## 両方対応するには？
try_import_unidic　で辞書の場所が帰ってきます。
それで、ユーザーがargで辞書を指定していない限り、どちらを使っているかわかります。

CSVの区切り文字は、unidicはカンマ、unidic-liteはタブです。
読みカナは、unidicなら９番目、unidic-liteは１番目です。０番目が、変換前の単語ですが、unidicはタブで区切って品詞情報を取り除きます。（ちなみに、この情報があるので、unidic-lite用のコードに、unidicを流し込んでも、出力(テキスト・音声)は壊れますが、エラーは出ません。
CloudFrontでS3静的サイトwithベーシック認証

## 概要
- 静的なサイトをS3に置いて手軽にアクセス
- S3 のパブリックアクセスは許可しない
- Basic認証で認証をかける

## ポイント
- CloudFrontのディストリビューションにデフォルトのルートオブジェクトを設定する
- テスト中はキャッシュOFFにしておくと良いかも

## Basic認証用関数
```javascript
function handler(event) {
  var request = event.request;
  var headers = request.headers;

  // echo -n user:pass | base64
  var authString = "Basic <base64エンコード済文字列>";

  if (
    typeof headers.authorization === "undefined" ||
    headers.authorization.value !== authString
  ) {
    return {
      statusCode: 401,
      statusDescription: "Unauthorized",
      headers: { "www-authenticate": { value: "Basic" } }
    };
  }

  return request;
}
```

## 設定
- S3バケット
    - オブジェクト
        - ファイルをアップロードする
    - プロパティ
        - 静的ウェブサイトホスティング
            - 無効
    - アクセス許可
        - ブロックパブリックアクセス
            - 全てブロック
    - バケットポリシー
        - 後ほど設定
- CloudFront
    - 関数
        - Basic認証用関数を作成
        - 作成した関数を発行
    - ディストリビューション
        - 一般
            - デフォルトルートオブジェクト：サイトによる (index.html等)
                - index.html
        - オリジン
            - オリジンドメイン
                - S3バケットのドメイン
            - オリジンアクセス
                - Orgin access control settings
                    - Create New OAC
                        - デフォルト設定で生成
                        - このメニューが出ない場合、S3の静的ウェブサイトホスティングが有効になっている可能性
                    - S3 のポリシーをコピーすることができるのでコピー
                    - S3 のバケットアクセス許可へ移動
                        - S3 のバケットポリシーを編集
                            - コピーしたポリシーで置き換える
                        - S3 バケットポリシー設定を反映
        - ビヘイビア
            - オリジンとオリジングループ
                - 作成したオリジン
            - ビューワー
                - デフォルトでも可
            - キャッシュキーとオリジンリクエスト
                - デフォルトでも可
            - 関数の関連付け
                - ビューワーリクエスト
                    - 作成した関数をセット
            - 変更を保存


以上、お疲れさまでした!
議事録の書き方

OJTで後輩の子に議事録の書き方について教えたのでその内容を
メモ程度ですがまとめます。
習うより慣れよ。。。とはいいますが、新卒の人たちに向けて。

# 記載事項
以下の項目は議事録の必須事項です。
1. 会議名
1. 開催場所
1. 実施日付
1. 会議時間
1. 参加者
1. 資料
1. 議事内容および、決定事項
1. 作成者・承認者

## 議事内容の記載方法

議事内容を記載する上で意識することは以下の通りです。

1.議事の進行に沿って章分けをする
2.各パートでの確認項目、決定事項を記載する
3.誰が発言したか、了解・承認したかを明記する
4.なぜその決定事項になったか、それまでの会話の流れも記載する
5.承認されたという事実を必ず記載する

2~5が大事です。1は読みやすければ（議事の流れが追えれば）なんでもいいと思います。

構成としては以下のようにするといいと思います。
| # | 議事内容 | 発言者 | 発言区分 |
|:-:|:-:|:-:|:-:|
|  1 |  XXの説明を行った。 | Aさん  |  その他 |
| 2  | 〇〇は◇◇でいいか。→問題ない。| Bさん→Aさん  | 確認  |
|  3 |  週末までに☆☆してください。→承知した。 |  Bさん→Aさん | 決定  |
|4|◆◆はどうか。→m月dd日までに検討する。|Cさん→Aさん|課題|

表形式だとうまく書けませんがこんな感じで良いと思います。
区分は、
1. 課題事項
1. 確認事項
1. 決定事項
1. その他

のように区分けすると読みやすくなります。

誰が誰とどう話したから、誰が発案して誰が承認した。
こういう流れで議事は基本進行するので、しっかり要点を抑えて書きましょう。

## よく指摘される項目
私が議事録を書いた際によく指摘された項目を列挙します。
もしよければ参考にしてください。

1. 議事録のファイル名に付加する日付は議事の実施日であること
1. 甲→乙の場合は了解した。乙→甲の場合は承知した。で、受け答え方を分けること
1. 議事の終わりに　以　上　と書きましょう（議事がそこで終わったことを示すため）
1. 議事のレビューで修正があった場合は、オリジナルの文書を上書きせずに枝番を上げて編集しましょう。（修正後って一目でわかるからだそう）
1. 議事で生じた課題・決定事項は課題管理表などで管理すること
1. 文章の平仄は合わせましょう（ですます。～だ。を統一するなど）
1. 口語ではなく、文語で書きましょう

## おわりに
議事録は決定したこと、なぜ決定したのか、誰が承認したのか、その証跡となるものです。
当然、作成された議事録が相手側で承認された場合は、法的根拠にもなりえます。
変に気負う必要はありませんが、しっかりと記載することで自分達の身を守ることに繋がりますので、自戒を込めてメモ程度ではありますが残させていただきます。

## おまけ
文章を記載するときは、以下にリンクしている文化庁のドキュメントが参考になるので
お時間があれば一読することを推奨します。

[公用文作成の考え方](https://www.bunka.go.jp/seisaku/bunkashingikai/kokugo/hokoku/93657201.html)

ある程度の国語力は上がると思います。


以　　上
FlutterアプリでMarkdownを表示する

## 概要
FlutterアプリでMarkdownを表示する場合の備忘録です。

## 結論
https://pub.dev/packages/flutter_markdown

を使うだけで十分...

### 気をつけるところ

一部分のテキストだけを変更したい場合は、`MarkdownBody`を使う。

サンプル（リンクタップ時に`url_launcher`を使って、アプリ内ブラウザ遷移をする）

```dart
        MarkdownBody(
          data: text,
          onTapLink: (String text, String? href, String title) async {
            if (href == null) {
              return;
            }
            if (await canLaunchUrlString(href)) {
              await launchUrlString(href);
            }
          },
        ),
```

### Tips

リンク色の変更 + リンク下線をつける

```dart
          styleSheet: MarkdownStyleSheet(
            a: const TextStyle(
              color: Colors.red,
              decoration: TextDecoration.underline,
              decorationColor: Colors.black,
            ),
          ),
```
Google拡張機能で毎日同じ時間に起動するアラームをセットしたい！

## はじめに
[Google拡張機能](https://chromewebstore.google.com/?hl=ja)を作っている際に、backgroundで毎日同じ時間に起こる使用を作ろうとしました。
調べてみると[chrome.alarms](https://developer.chrome.com/docs/extensions/reference/api/alarms?hl=ja)という物があり、その中の`delayInMinutes`や`periodInMinutes`で作ってみましたが、アラームの起動時間から`delayInMinutes`後に起動する設定になってしまいました。この場合Chromeを開いていない時間にアラームが起動することはないため次にChromeを開いた時にアラームが起動します。その時刻から`periodInMinutes`後の時刻に次のアラームがセットされ、毎日同じ時間に起動するアラームにはできませんでした。
そのため今回はアラームが起動した時に次のアラームをセットする実装を行いました。

## 実装
```ts
const STORAGE_KEY = "daily-alarm"

chrome.alarms.onAlarm.addListener(async (alarm) => {
  if (alarm.name === STORAGE_KEY) {
    //ここに毎日行いたい実装を加える
    chrome.alarms.clear(STORAGE_KEY)
    setResetAlarm()
  }
})

const setDailyAlarm = () => {
  const now = new Date()
  //今回は深夜０時に設定
  const nextMidnight = new Date(
    now.getFullYear(),
    now.getMonth(),
    now.getDate() + 1,
    0,
    0,
    0
  )
  const when = nextMidnight.getTime()
  chrome.alarms.create(STORAGE_KEY, { when })
}
```
以上のコードを既存のコードに加え、一度`setDailyAlarm`を呼び出すことで、特定の時間に起動するアラームを再起的にセットすることができます。

自分は以下の部分で実装を行いました。

https://github.com/s1f102100793/gpt-counter/blob/master/src/background/index.ts

## 終わりに
今回はGoogle拡張機能で毎日同じ時間に起動するアラームを再帰関数を用いて、
「アラームセット」→「アラーム起動＋アラームリセット＋アラームセット」を繰り返し行うように実装しました。
違うやり方があったり、もっといい方法あるよーとかあったらじゃんじゃんコメント待っています。
最後まで読んでいただきありがとうございました。
ODCとは何か？インド人件費とベトナム人件費の比較

最近、多くの企業がODCを利用していますが、一部の人々にとってはODCが何を意味するのか理解していないかもしれません。また、インドとベトナムの人件費についても、企業のグローバルな展開において重要な要素です。ここでは、ODCの意味とインド人件費、ベトナム人件費との比較について見ていきましょう。
![software-developement.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3031688/35b52c15-7d7d-69e5-3a41-824205a04c64.png)

1. ODCとは
ODCは「Offshore Development Center」の略称で、海外開発センターを指します。これは、外国の企業が自社の開発プロジェクトを効率的に管理および実施するために設立される施設です。[ODCは](https://newwave-solutions.co.jp/what-is-odc/)、優れた技術人材を提供する国や地域に設置されることが一般的であり、インドやベトナムなどのアジア諸国が人気のある場所として挙げられます。
2. インド人件費
[インド人件費](https://newwave-solutions.co.jp/india-offshore-cost/)は、インドでの労働者の賃金や人件費を指します。インドは、世界的に見て労働力が豊富であり、多くの企業が労働コストを抑えるためにインドを選択しています。一方で、近年ではインフレ率の上昇や人材の需要増加により、インド人件費も徐々に上昇しています。
3. ベトナム人件費
[ベトナム人件費](https://newwave-solutions.co.jp/offshore-development-cost/)は、ベトナムでの労働者の賃金や人件費を指します。ベトナムは、人件費が比較的低いことで知られており、多くの企業がベトナムをアウトソーシング先として選択しています。しかし、近年では経済成長が加速し、人材の需要が増加しているため、ベトナム人件費も上昇しています。
4. インド人件費とベトナム人件費の比較
インド人件費とベトナム人件費は、両国の経済状況や労働市場の需給バランスによって異なります。一般的に、インド人件費はベトナムよりも高い傾向がありますが、それでも他の先進国に比べれば比較的低い水準にあります。企業がODCを選択する際には、これらの要素を考慮して、最適な選択肢を見極めることが重要です。
5. SE単価
[SE単価](https://newwave-solutions.co.jp/brse-cost/)は、ソフトウェアエンジニアの労働単価を指します。これは、ソフトウェアエンジニアが1時間あたりに受け取る給与や報酬の金額を示します。SE単価は、ソフトウェア開発プロジェクトのコスト計算や予算管理において重要な要素です。一般的に、SE単価はソフトウェアエンジニアの経験やスキルレベル、プロジェクトの規模や複雑さなどによって異なります。企業がプロジェクトを進める際には、適切なSE単価を設定し、優秀なエンジニアを確保することが重要です。また、SE単価は市場の需要と供給にも影響されるため、時には変動することもあります。
6. まとめ
ODC、インド人件費、ベトナム人件費は、グローバルな企業の展開において重要な要素です。ODCを活用することで、企業は世界中の優れた技術人材を利用し、効率的な開発プロセスを実現することができます。インドとベトナムの人件費の比較を行いながら、企業は自社のニーズに合った最適な選択肢を見極めることが重要です。
モダンCSSまとめ②

![m-css.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3756285/961efbc9-e442-2e94-170a-73b295f08d84.png)

学習用に最新のモダンCSSをまとめてみます。
今回は新セレクタ:has() / :is() / :where() をまとめたいと思いますがまず:has()から。

# 疑似クラス :has() まとめ
### :has()セレクタ
:has()セレクタは、子要素に特定の要素が存在するかどうかで親要素にスタイルを適用できます。
これにより、特定要素を内包する要素をグループ分けするなど、細かいデザインがシンプルなCSSだけで実現可能です。

親要素のcard内に画像がある場合のみレイアウトを横並びに変更
<p class="codepen" data-height="300" data-theme-id="light" data-default-tab="html,result" data-slug-hash="vYMeydw" data-user="moco2-design" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/moco2-design/pen/vYMeydw">
  Untitled</a> by 山﨑恵 (<a href="https://codepen.io/moco2-design">@moco2-design</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

親子関係だけではなく　「直後にp要素があるようなh1要素に対して」といった隣の要素にスタイルを適用する事も可能です。

<p class="codepen" data-height="300" data-theme-id="light" data-default-tab="html,result" data-slug-hash="eYoGgdP" data-user="moco2-design" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/moco2-design/pen/eYoGgdP">
  Untitled</a> by 山﨑恵 (<a href="https://codepen.io/moco2-design">@moco2-design</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>


このようにJavascriptなしでスタイルの条件分岐指定ができるようになり、非常にシンプルで早くコーディングができるようになったと言えます。
プログラミング言語の普及：Go、COBOL、Dart、およびバックエンド言語

コンピュータ言語の世界は常に進化しています。古くから使われてきたCOBOLから、最新の技術であるGO言語とDart言語まで、バックエンド言語の進化は目覚ましいものがあります。今回は、これらの言語について詳しく見ていきましょう。
![software-developement-3.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3031688/a18f1187-4beb-0739-e9e9-b76728da0ba7.jpeg)

1. COBOL とは
[COBOL](https://newwave-solutions.co.jp/what-is-cobol/)（Common Business Oriented Language）は、1959年に開発されたプログラミング言語です。主にビジネスアプリケーションの開発に使用され、金融業界や行政機関などで広く採用されてきました。COBOLは、データ処理やバッチ処理などの領域で強力な性能を発揮し、長年にわたり重要な役割を果たしてきました。
2. GO 言語
[GO言語](https://newwave-solutions.co.jp/go-language/)は、Googleが2009年に開発したプログラミング言語です。GO言語は、高い並行性と効率性を持ち、大規模なシステムやネットワークアプリケーションの開発に適しています。また、シンプルな構文と豊富な機能を備えており、開発者にとって使いやすい言語です。
3. Dart 言語
[Dart言語](https://newwave-solutions.co.jp/what-is-dart/)は、Googleが2011年に公開したプログラミング言語であり、特にモバイルアプリケーションの開発に使用されています。Dart言語は、高いパフォーマンスと使いやすさを両立させており、特にFlutterフレームワークを使用したクロスプラットフォームアプリケーションの開発に適しています。
4. バックエンド 言語の進化
[バックエンド言語](https://newwave-solutions.co.jp/?p=21436&preview=true)は、ウェブアプリケーションやモバイルアプリケーションのサーバーサイドで使用される言語です。古くから使われてきたCOBOLから、GO言語とDart言語への移行は、その進化を示しています。古典的なCOBOLは、安定性と信頼性を提供してきましたが、近年では新たな技術の台頭により、効率性や柔軟性が求められるようになりました。GO言語やDart言語は、これらの要求に応えるために開発されたものであり、特に大規模なシステムやモバイルアプリケーションの開発に適しています。
5. まとめ: 新しい時代のバックエンド開発
COBOLからGO言語とDart言語への移行は、新しい時代のバックエンド開発の到来を示しています。これらの言語は、より効率的で柔軟な開発を可能にし、現代のデジタル時代において重要な役割を果たしています。ニューウェーブソリューションズジャパンでは、最新の技術を駆使したバックエンド開発サービスを提供し、お客様のビジネスの成長を支援します。お問い合わせや相談はいつでも歓迎です。
【個人開発】Bun + Vite + Remixを使い、１ヶ月でWebアプリ作ってみた　~感想編

# 初めに
この企画のきっかけとして、卒業前年時の春休みで何もしてないと、コーティングや技術力がなまってしまうと思い、最新のWebに関する技術についてキャッチアップしたいから

# なぜこの開発環境になったのか
- Bun
高速JSのランタイム
- Vite
Remixとの対応されたので相性がよい
- Remix
NextJsの対抗馬でよく名を聞くから

# 企画内容
ダイエットサポートサービス「トレノート」
自分自身がカレンダーに体重や行ったトレーニングを手書きで記入しているとこからの起案です。詳しくは[こちら](https://hackmd.io/HHVTafpvSzOcm7NNvPELTQ)

# 今回得たこと
- Bunの開発体験の向上
- Remixのフルスタック開発
- 個人開発のスケジュール

## Bunの開発体験の向上
以前npmを使ってパッケージ管理を行っていたのですが、ライブラリのインストールやローカルサーバーの起動の待ち時間が発生し、気になっていたところ、Bunを使用することで解決することができました。

## Remixのフルスタック開発
RemixではNextと同様にフロントとバックを言語を変えずに開発することができ、さらにNextよりわかりやすく書くことができます。loader,actionという関数を使い、PHPのような感覚でサーバーサイドを開発を行います。詳しくはまたQiitqに記事として書こうと思います。

## 個人開発のスケジュール
今回この企画は個人開発で3月末までという期限を設け、行いました。
企画段階でどのような工程が必要でどのくらいかかるか目安程度でもいいので考えておくと後で楽になるかなと思いました。

# まとめ
自分的には新しい技術を学んで、欲しいサービスを作れたので満足しています。ですがもっと効率よく開発することができたのではないかと反省しています。また一ヶ月で割とほぼ完成形までもっていく事ができたので、違う言語やフレームワークを使って開発していきたいと思いました。具体的な開発の内容は今後Qiitaにて投稿していく所存です。

ここまでお読みいただいた方ありがとうございます。
DApp(Decentralized Application)について

今回は「DApp」についてです。

「DApp」とは「<font color="red">**Decentralized Application**</font>」の略で
日本語にすると「<font color="orange">**分散型アプリケーション**</font>」となります。

既存の中央集権型アプリに対する新しい形のアプリケーションとなり、
下図の様なイメージの違いがあります。
![01_DApp_01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2633696/6c6856bc-6604-7165-ed40-bda4e45e415e.png)


では、
まず初めに「DApp」とはどういう物なのかを纏めていきたいと思います。

## ■「DApp」の特徴
「DApp」の特徴を３つ挙げるとすると、以下が挙げられるでしょう。
![01_DApp_02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2633696/43db75c9-22f7-9a38-28e2-62c7cdf2b077.png)
 
 　**①改竄耐性が高い**
　　ブロックチェーン上で運用されるアプリケーションの為、
　　データは分散管理され<font color="red">改竄されにくくなります</font>。

　**②障害に強い（高可用性）**
　　ネットワーク上で分散管理するものなので、ハードウェア関連のトラブルや
　　環境トラブルに対する<font color="red">耐性が高くなります</font>

　**③信頼性が高い（高信頼性）**
　　取引履歴が全て公開されていているので<font color="red">信頼性は高い</font>です。
  　（後追い検証も可能）
<br>
<br>
次にどの様に使われているのかについて纏めていきたいと思います。
## ■「DApp」の活用
「DApp」は色々な方面で活用されています。
　
 　**①仮想通貨**
　　BitcoinやEthereum等の代表的な仮想通貨で「DApp」は活用されていて、
　　<font color="orange">改竄耐性</font>・<font color="orange">高可用性</font>・<font color="orange">高信頼性</font>を実現しています。

　**②DeFi(分散型金融)**
　　DeFiは仮想通貨の仕組みを活用し、
　　「DApp」の概念をベースにして構築された新しい金融サービスです。

　**③NFT(代替不可能なトークン)**
　　NFTはブロックチェーン上で発行される<font color="orange">信頼性</font>の高いデジタル資産で、
　　「DApp」の概念をベースにして構築された取引サービスです。

　**④SNS**
　　マストドン等の分散型のSNSはP2Pネットワーク上に構築されていて
　　データが分散管理されている為、投稿の<font color="orange">改竄</font>がされにくくなっています。

　**⑤クラウドストレージ**
　　ネットワーク上にデータを分散して保存することで
　　<font color="orange">信頼性</font>が担保されています。
<br><br>

次は「DApp」の発展性について纏めていきます。

## ■「DApp」の発展性
「DApp」は発展性が高いと言われています。
その理由としては以下のようなものになると思われます。

　**①「DApp」の開発環境**
　　仮想通貨の「Ethereum」や「Polkadot」に使用されている為、
　　既に開発のナレッジが蓄積されており、ツールも存在している状況ですので
　　「DApp」の<font color="blue">開発を始めるハードルは低い</font>です。

　**②データ分散化**
　　社会全体として<font color="blue">改竄耐性・高可用性・高信頼性</font>が求められています。
　　分散化を行う事で上記の様な要件をクリアできる事が見込まれています。

　**③スマートコントラクト**
  　スマートコントラクトは日本語にするとすれば「賢い契約」となります。
  　事前に指定されたルールに従い自動的に動作するプログラムとなります。
  　具体的には、人間の介入なしに自動的に設計され、実行される、
  　より高度で安全なコントラクタです。
  　スマートコントラクトを使う事で<font color="blue">信頼性</font>を高めることができます。

　**④新しいビジネスモデル**
　　<font color="blue">中央集権型に置き換わる新しいビジネスモデル</font>として期待されています。
　　・DeFi：分散型ファイナンス
　　・NFT：デジタルアセット（資産として価値のあるデジタルデータ）
　　・DAO：分散型自立組織
<br><br>

最後に少しだけ「DApp」についての課題にも触れておきたいと思います。

## ■「DApp」の課題
「DApp」はまだ発展途上の技術である為、いくつかの課題が存在しています。

　**①相互運用**
　　<font color="red">異なる「DApp」間では情報の流動性を確保できない</font>事がほとんどです。
　　例えば、仮想通貨の「Ethereum」や「Polkadot」は「DApp」を使用していますが
　　相互に情報をやり取りできません。

　**②脆弱性やバグ**
　　発展途上の技術である為、プログラムコードには<font color="red">脆弱性やバグ</font>が残っている可能性があります。
　　致命的な脆弱性やバグが発見された場合、ユーザを危険に晒す可能性もあります。

  　　・2023年12月にレジャーコネクターを利用する複数のDAppでセキュリティ侵害が発生
  　　・2024年3月にDAppのStaking v3 で「推定報酬」が多めに表示された


　**③処理速度**
　　ネットワーク上で分散処理をする性質上、処理速度は<font color="red">環境や設定に大きく依存</font>します。
　　そのため、ユーザ数が多くなり、取引が集中するとトラブルが発生する事があります。
  　（ブロックチェーンのスケーラビリティの問題からくるトランザクション遅延や
   　　取引に対するマイナーの不足などが原因となる事があります）
<br><br>
## ■最後に
「DApp」をテーマにしてみましたが、
纏めていて改めて将来有望な技術だと再認識させられました。

「DApp」を直接目にすることはないと思いますが
　<font color="blue">**有用な特徴を持つ**</font>
　<font color="green">**多岐に渡る活用方法がある**</font>
　<font color="orange">**発展性が高い**</font>
という事がご理解いただけたと思います。

そんな縁の下の力持ちな「DApp」を知っていただき
今後の参考にしていただければ幸いです。
【生成AIアプリ100チャレンジ】（5）朗読アプリ

# 公開中サイト

https://ai.nuevo.jp/whispar/

# 開発環境

|              |              |
|:------------:|:------------:|
| Server       | lightSail    |
| Language     | Python3.11   |
| Framework    | Django       |
| DB           | sqlite3      |

ローカル環境ではPythonのvenvを使用。エディタはvs codeです。


# 目的
　WEB画面上でChatGPTと会話ができるアプリを作りたいと思っています。TTS(テキストを自然な音声にするAPI)とwhispar(音声をテキストにするAPI)を使えば、会話ができるはず。その第2弾として、whisparで音声を文字起こしするアプリを作りました。
これで、文字起こし機能と合わせて、同期処理すれば、ChatGPTと音声で会話ができる、はず。


# コード
```python:qiita.rb
def index(request):

    # Access the API key from the environment
    OPENAI_API_KEY = os.environ['OPENAI_API_IMAGE_KEY']
    chat_results = ""
    if request.method == "POST":
        form = TtsForm(request.POST)
        if form.is_valid():
            sentence = form.cleaned_data['sentence']
            client = OpenAI(
                api_key = OPENAI_API_KEY,
            )
            speech_file_path = settings.BASE_DIR / "uploads/tts/speech.mp3"
            response = client.audio.speech.create(
                model="tts-1",
                voice="alloy",
                input=sentence
            )
            response.stream_to_file(speech_file_path)
            chat_results = speech_file_path
    else:
        form = TtsForm()

```

正直思ったのが、音声を録音したファイルを保存するのが大変なんじゃないかって思っていた。
実際はこのワンラインだけで保存できる。とても楽。

```
 response.stream_to_file(speech_file_path)
```

逆に大変だったのが。保存された音声データをアプリ終了時に削除させること。JSでページ離脱時に削除するロジックをかいたのだが。文字データを送る時にもファイルが削除されてしまう。そこはPOST時の判定でクリアさせることができた。

あと、前回のミスを繰り返さないように生成されるファイルはgitignoreに追記しておくこと。これで、変なことにはならない。


# アプリ画面
![スクリーンショット 2024-03-29 15.31.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/398694/ecdfc71a-f9db-00ca-ed96-7892a7f194de.png)

# 感想
日本語と英語、どちらも発音が綺麗ですね。音声会話チャットができれば、擬似英会話もできそうですね。夢が膨らみます。
はやく作りたいです。
NextAuthでTypescriptの型エラーの修正

NextAuthとTypescriptを同時に扱う際、エラーがなかなか解決できなかったのでその対応メモです。

# 解決案 型定義
エラーでVScodeに怒られる原因は型の定義がないことだと思うんで、単純に定義してあげます。

(手のかかる子ですね、Typescriptちゃんは( ´∀｀ ))

```next-auth.d.ts
import NextAuth from "next-auth"

declare module "next-auth" {
  /**
   * Returned by `useSession`, `getSession` and received as a prop on the `SessionProvider` React Context
   */
  interface Session {
    user: {
      /** The user's postal address. */
      name: string,
      email: string,
      image: string | any,
    }
    id: string
  }
}
```

これはNextAuthのProviderにGitHubを設定している場合です。

他のパターンだとまた違うのであくまで参考ということで。

ではよきNextAuth＆Typescript ライフを
二次元配列に対するsort_byの挙動の覚書き

初学者向けの記事です。
二次元配列（配列の中に配列が入れ子になっている）のsort_byの挙動について、大分混乱したのでメモ的に残しておきます。

*その前にsort_byメソッドはこちらの解説がわかりやすいです。
>
https://zenn.dev/ganmo3/articles/fdda007796a7cb

---
```ruby
array = [[1, 6], [3, 4], [2, 5], [1, 7]]
sorted_array = array.sort_by { |a| a}
puts sorted_array.inspect
# 出力:[[1, 6], [1, 7], [2, 5], [3, 4]]
```
二次元配列のソートでは、サブ配列（配列の中の配列）の最初の要素を比較してソートが行われる。最初の要素が同じ場合は、次の要素を比較して順序を決定する。つまり、サブ配列は最初の要素で主に昇順に並べられ、同じ最初の要素を持つサブ配列がある場合は、次の要素で昇順にソートされる。
配列`[[1, 6], [3, 4], [2, 5], [1, 7]]`では、最初の要素1, 3, 2, 1を基にソートされる。まず1と1が最小で、次に2、最後に3が続く。1を最初の要素に持つサブ配列`[1, 6]`と`[1, 7]`は、次の要素である6と7を比較してさらにソートされる。その結果、ソート後の配列は`[[1, 6], [1, 7], [2, 5], [3, 4]]`となる。

---
```ruby
array = [[1, 6], [3, 4], [2, 5], [1, 7]]
sorted_array = array.sort_by { |a,b| [-a, b] }
puts sorted_array.inspect
# 出力:[[3, 4], [2, 5], [1, 6], [1, 7]]
```
{ |a, b| [-a, b] }でサブ配列の最初の要素aを降順で、次の要素bを昇順でソートする。まず最初の要素に基づいて`[3, 4]`が最も先に来る（最初の要素が最大のため）。次に`[2, 5]`、そして最初の要素が1である`[1, 6]`と`[1, 7]`が続く。これらは最初の要素が同じであるため、次の要素に基づいて昇順に並べられる。その結果、最終的なソート結果は`[[3, 4], [2, 5], [1, 6], [1, 7]]`となる。

---
```ruby
array = [[1, 6], [3, 4], [2, 5], [1, 7]]
sorted_array = array.sort_by { |a,b| [-a, -b] }
puts sorted_array.inspect
# 出力:[[3, 4], [2, 5], [1, 7], [1, 6]]
```
{ |a, b| [-a, -b] }でサブ配列の最初の要素aを降順で、次の要素bも降順でソートする。-aと-bにより、それぞれの要素が負の値として評価され、ソート時にはその絶対値が大きいものから小さいものへと順序が決定される。つまり、最初の要素aに関しては、最大の値から順に配列が並べられ、最初の要素が同じ場合は次の要素bが大きいものから順に並べられる。
最初の要素に基づき`[3, 4]`が最先に配置される。続いて`[2, 5]`が来る。最初の要素が1である`[1, 6]`と`[1, 7]`の場合、次の要素に基づいて降順にソートされるため、`[1, 7]`が`[1, 6]`より先にくる。
結果として得られる配列は`[[3, 4], [2, 5], [1, 7], [1, 6]]`になる。

---
値を`[[1, 6], [3, 4], [8, 6], [1, 7]]`に変えて
```ruby
array = [[1, 6], [3, 4], [8, 6], [1, 7]]
sorted_array = array.sort_by { |a,b| [-b, -a] }
puts sorted_array.inspect
# 出力:[[1, 7], [8, 6], [1, 6], [3, 4]]

```
{ |a, b| [-b, -a] }で各サブ配列の第二要素bを主な基準として降順、第一要素aをその次の基準として降順でソートする。
このソート基準では、`-b`と`-a`を使ってそれぞれの要素を負の数として扱い、結果として大きい数から小さい数へと降順にソートする。この場合、サブ配列の第二要素がソートの最初の基準で、第一要素は第二要素が同じサブ配列間でのソート基準になる。
配列`[[1, 6], [3, 4], [8, 6], [1, 7]]`をこの条件でソートすると、まず第二要素に基づいて降順にソートされる。最大の第二要素は`7`で、これに対応するサブ配列は`[1, 7]`となる。次に第二要素が`6`であるサブ配列があり、その中で第一要素が大きい`[8, 6]`が先に来る。同じく第二要素が`6`の`[1, 6]`が次に続く。最後に第二要素が最小の`4`である`[3, 4]`が来る。
したがって、ソート後の配列は`[[1, 7], [8, 6], [1, 6], [3, 4]]`となる。
開発ツールで確認されるようなソースコードが、Pythonのrequests.get()で取得できないときの対処法

# はじめに
ブラウザの開発ツールから確認するかぎりでは、ソースコードが確認できるwebページ。
しかし、いざ`python`の`requests.get()`でページのソースコードを取得しようとしてもソースコードを得られない場合があります。
本記事では、このような場合の対処法のひとつを示します。

# 解決策
**`requests.get()`の引数`headers`にUser-Agentを指定する**
```python
import requests
url = "" # webページのurl
response = requests.get(url, headers={"User-Agent": ""})
```
User-Agentの指定の仕方の一例は[こちら](https://yuki.world/python-requests-useragent/)。
自分の場合は、User-Agentに空文字列`""`を指定しても解決しました。

# User-Agentとは
webサイトを閲覧するためのブラウザ情報やOS情報。
これを指定することで、ブラウザの開発ツールで確認されるようなソースコードを返します。

# まとめ
本記事では、ブラウザの開発ツールで確認できるソースコードが、`requests.get()`で取得できない場合の対処法について書きました。

他の対処法でseleniumを使う方法がありますが、また別の記事で書きたいと思います。
gNMIとは？

# はじめに
　私が業務で経験した技術やハマった事象と解決方法について紹介していきます。
　監視業務でテレメトリを扱う際に、Openconfigに準拠したものを扱う技術が多くなってきているのでそれらを紹介できればと考えています。
　今回はその中でもgNMIについて紹介します。
 
# gNMIとは
　gNMI（gRPC Network Management Interface）とは、ネットワーク機器の設定とテレメトリ情報の取得を行うためのプロトコルです。gRPCを使用することで、効率的かつセキュアな通信を実現することができます。

## gNMIの機能
　gNMIの主な機能として下記が挙げられます。

1.ネットワーク機器の設定および設定情報の取得
2.テレメトリ情報の取得
　2.1 リクエストとレスポンスによる取得
　2.2 ストリーミングによる取得

　gNMIを扱う際にはクライアントツールを用いることが多いため、クライアントツールの使い方も合わせて紹介します。

## クライアントツール（gNMIc）
　gNMIc（gNMI client）は、gNMI（gRPC Network Management Interface）プロトコルを使用してネットワーク機器と通信し、設定変更やテレメトリ情報の取得などを行うためのクライアントツールです。

### gNMIcのインストール
　gNMIcのインストールは非常に簡単で、下記のコマンドを実行することでインストールが可能です。
```
    bash -c "$(curl -sL https://get-gnmic.openconfig.net)"
```

インストール後は後述のコマンドを使って機器からテレメトリを取得することが可能です。

## gNMIcのコマンド
　gNMIcで利用できるコマンドの一部と概要について下記に記載します。単純にデータを取得する場合、getを使い、データを定期的に取得する場合や値の更新を契機にデータ取得したい場合などにはsubscribeを使って取得します。
 また、対象の機器がサポートしているgNMIのバージョンやデータモデルをcapabilitiesで確認することも可能です。

| コマンド| 概要 | オプション | 備考 |
|:-:|:-:|:-:|:-:|
| get |  データを取得する |   |   |
| set |  設定を変更する |   |   |
| subscribe |  ストリーミングでデータを取得する | once:一度のみ取得、POLL:指定されたpolling期間で取得、STREAM:連続して取得（定期的 or 値の更新に合わせて）|   |
| capabilities | サポートしている機能の一覧を取得する  |   |   |

## gNMIcでのデータ取得
### 情報の取得(get)
　機器に設定されている設定情報や単発でテレメトリを取得する際は下記のコマンドを実行します。
```
    gnmic -a <IPアドレス>:<ポート番号> -u <ユーザ> -p <パスワード> --insecure get --path <取得対象のコンフィグのパス> --timeout 30s
```
 取得対象のコンフィグのパスは、「Yangファイル名：項目のパス」の形式で記載します。

### 情報の取得(subscribe)
　データを定期的に取得する場合や値の更新を契機にデータ取得したい場合などには下記のコマンドを実行します。
```
    gnmic -a <IPアドレス>:<ポート番号> -u <ユーザ> -p <パスワード> --insecure sub --path <取得対象のコンフィグのパス> timeout 1m -e proto --stream-mode ON_CHANGE
```

### 情報の取得(yaml,get)
　設定ファイルを読み込み、情報を取得する場合は下記のコマンドを実行します。
```
    gnmic --config ./<configファイル名> [取得方法(get/sub)]
```

　今回は簡単な実行方法までを紹介しました。次回以降で使っていて困ったことや監視システムとの連携方法とかを紹介していければと思います。

## おわりに
　テレメトリの収集は従来SNMPとか使っていましたが、Openconfig等のオープン化の流れもあり、netconfやgRPC、gNMIなども選択肢として挙がってくるため、使えるようになると監視の幅が出てくるとかと思います。

興味を持っていただけましたら、試してみてはいかがでしょうか。

## 参考URL
[gNMI](https://github.com/openconfig/gnmi "openconfig/gnmi")  (https://github.com/openconfig/gnmi)
[gNMIc](https://github.com/openconfig/gnmic "openconfig/gnmic") (https://github.com/openconfig/gnmic)
[gNMIc](https://gnmic.openconfig.net/ "gNMIc") (https://gnmic.openconfig.net/)
[JANOG52　gNMIcを活用したマルチベンダー環境でのテレメトリ技術の実績](https://www.janog.gr.jp/meeting/janog52/wp-content/uploads/2023/06/janog52-gnmic-ogawa.pdf "janog52-gnmic-ogawa.pdf") (https://www.janog.gr.jp/meeting/janog52/wp-content/uploads/2023/06/janog52-gnmic-ogawa.pdf)
【iOS】pb_check_proto3_default_value のエラー対応

# 概要・問題
アプリをリリースしたら iOS17.4 でアプリ起動時にクラッシュが発生するという報告を受けました。

Firebase の Crashlytics を確認すると、以下の問題が発生していました。

```shell
pb_check_proto3_default_value
```

```
Crashed: com.apple.main-thread
EXC_BAD_ACCESS KERN_INVALID_ADDRESS 0x00000001736eb51c
```

メインスレッドで nanopb がクラッシュしているようです。

また厄介なのが、 TestFlight や AppStore で再現可能で、ローカルビルドでは再現しないという点です。

## 開発環境
Xcode 15.3
Swift 5.10
iOS 17.4

使用技術：Firebase、nanopb
(詳細は大人の事情で割愛）

# 解決策

この問題は Firebase のバグによるものだそうです。

そのため、バージョンを `v10.22.0` にアップデートすれば解決するそうです。

原因の原因については調査中...(2024年3月29日現在)

# 参考文献
https://stackoverflow.com/questions/78122789/crash-in-nanopb-ios-17-4-0

https://github.com/firebase/firebase-ios-sdk/issues/11403
AWSで、Terraformを利用する方法をまとめてみた(10/10)

Terraformを使用してオートスケーリングを実現する
--

目次
1.構成ファイルを作成します。
2.構成ファイルを適用します。

1.構成ファイルを作成します。
--
このファイルでは、AWSのAuto Scalingグループを作成し、EC2インスタンスを自動的に起動および削除するための設定を定義しています。
~~~
provider "aws" {
  region = "us-west-2"
}

resource "aws_launch_configuration" "example" {
  image_id        = "ami-0c55b159cbfafe1f0"
  instance_type  = "t2.micro"

  lifecycle {
    create_before_destroy = true
  }
}

resource "aws_autoscaling_group" "example" {
  name                 = "example-asg"
  launch_configuration = aws_launch_configuration.example.id
  min_size             = 1
  max_size             = 3
  desired_capacity     = 2

  lifecycle {
    create_before_destroy = true
  }

  tag {
    key                 = "Name"
    value               = "example-instance"
    propagate_at_launch = true
  }
}
~~~
この例では、aws_launch_configurationリソースを使用してEC2インスタンスの起動設定を作成し、aws_autoscaling_groupリソースを使用してオートスケーリンググループを定義します。aws_autoscaling_groupリソースで指定されたmin_size、max_size、およびdesired_capacityは、それぞれ、グループで維持されるインスタンスの最小、最大、および望ましい数を指定します。

2.構成ファイルを適用します。
--
terraform applyコマンドを使用して、AWSリソースを作成します。
~~~
terraform init
terraform apply
~~~
これで、Auto Scalingグループが作成され、EC2インスタンスが自動的に起動されます。EC2インスタンスの負荷が増加するにつれて、Auto Scalingグループは追加のインスタンスを起動し、負荷が減少するとインスタンスを削除します。


AWSで、Terraformを利用する方法をまとめてみた(1/10)
https://qiita.com/kimuni-i/items/ffde2f8df96fe5d9513f
AWSで、Terraformを利用する方法をまとめてみた(2/10)
https://qiita.com/kimuni-i/items/6298ed10adc6ad450488
AWSで、Terraformを利用する方法をまとめてみた(3/10)
https://qiita.com/kimuni-i/items/4d2f90506eb2dd4ee702
AWSで、Terraformを利用する方法をまとめてみた(4/10)
https://qiita.com/kimuni-i/items/b97089a820cc06ed2d9c
AWSで、Terraformを利用する方法をまとめてみた(5/10)
https://qiita.com/kimuni-i/items/11efb60b9a731d212211
AWSで、Terraformを利用する方法をまとめてみた(6/10)
https://qiita.com/kimuni-i/items/943daf8f4e80af12b70e
AWSで、Terraformを利用する方法をまとめてみた(7/10)
https://qiita.com/kimuni-i/items/b2cc6e1e4b5d5c1a92cb
AWSで、Terraformを利用する方法をまとめてみた(8/10)
https://qiita.com/kimuni-i/items/2934372c385ba0561425
AWSで、Terraformを利用する方法をまとめてみた(9/10)
https://qiita.com/kimuni-i/items/edbdc88f0bca9c0354d2
SwiftのUIImage(named:)とUIImage(systemName:)の違い

## 概要
SwiftのUIImage(named:)とUIImage(systemName:)の違いについてと、使用例をまとめた。

## UIImage(named:)
画像ファイルを表示するために使用する。
＊下記のsampleなど、使用する画像はAssetsに格納する。
```
backgroundImageView.image = UIImage(named: "sample")
```
## UIImage(systemName:)
SF Symbolsのアイコンを表示する場合に使用する。
＊SF Symbolsは、Appleが提供しているシンボルフォント。(別名アイコンフォントとも呼ばれる。)
```
backgroundImageView.image = UIImage(systemName: "cloud.bolt")
```
## 終わりに
これからSwiftのことについてまとめて行く予定です。
SAP Business Accelerator HubのAPIを使ってSAP BTPユーザー情報取得検証を行う

お疲れ様です。

今回はSAP APIからSAP BTPユーザー追加方法について調査しました。
その内容紹介します。

## やりたい動き
1)API実行でSAP BTP cockpitユーザー情報を取得

## 事前準備
・認証情報の取得
1)サービス＞インスタンスおよびサブスクリプションへ移動
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2500270/1a7cec24-c31f-4fd0-32a5-d0551652649e.png" width=80%>

2)サービス名＝Authorization and Trust Management Serviceを選択
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2500270/699c980a-12ab-d190-d38b-fdcd26b00ac6.png" width=80%>

3)サービスキーを選択
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2500270/02b8732d-af66-1ef9-ae14-132383b760d7.png" width=80%>

4)clientidとclientsecretを取得します。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2500270/4b4bebb3-d90e-45e4-0888-e7369b6263a6.png" width=80%>

※コピーする際は「JSON」ではなく「フォーム」でコピーすることをお勧めします。

## 検証開始
1)User Management (System for Cross-domain Identity Management (SCIM))へ移動

https://api.sap.com/api/PlatformAPI/overview

2)Try Outへ移動

3)SAP BTPの情報を設定
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2500270/576e86f4-a77a-9529-37cf-6b4dc02756dc.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2500270/9fbdd496-df9b-5493-78f2-c9a32b655db5.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2500270/b9efc694-d2d1-b536-cb04-3feb62bc7f26.png)
上記設定を埋めて頂き、Configureを押下します。

4)User情報の取得検証
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2500270/60c66564-724f-dcce-af48-4bdbfc17a156.png)
左側メニューの赤線部分を選択します。

5)Runボタンを押下
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2500270/839ffd7e-d230-5a15-908a-d6b2cb9d51ab.png)

6)200で返ってきた場合は成功です
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2500270/0bec097e-7d7a-e441-bac6-fa8584e9ebe2.png)
Response Bodyの中身を確認します。
※実行結果はサンプルです。

## 最後に
ユーザー情報取得だけでなくユーザー追加・削除も可能なAPIです。
あとは、PGを組み込めばユーザー制御をcockpitから実装せずに試すことが出来そうです。

##リンク集
[User Management (System for Cross-domain Identity Management (SCIM))](https://api.sap.com/api/PlatformAPI/overview)
Array.newで異なる値を持つ配列を作成する方法

初学者向けの記事です。簡単なコーディング問題で学習したのでメモ的に。
※当初「Array.newで異なる値を持つ配列は作成出来ない」という記事でしたが、訂正しました。

-----
`Array.new(n)` は、指定された要素数 `n` で新しい配列を作成するRubyのメソッドです。このメソッドは、`n` を引数に取り、その数だけnilで初期化された配列を生成します。
```rb
arr1 = Array.new(3)
puts arr1.inspect
# 出力:[nil, nil, nil]
```
-----
ブロックを与えることで、ブロックの評価結果を各要素に代入した配列を作成することもできます。
```rb
arr2 = Array.new(3) { 'a' }
puts arr2.inspect
# 出力:["a", "a", "a"]
```
-----
続いてArray.newで異なる値を持つ配列を作成する方法です。
まず直接的に異なる値を持つ要素を生成することはできません。例えば、["a", "b", "c"]みたいな配列を作成しようと、`Array.new(3) { 'a', 'b', 'C' }`のように書くと文法エラーになります。
```rb
arr3 = Array.new(3) { 'a', 'b', 'c' }
puts arr3.inspect
# 文法エラー：
# Main.rb:5: syntax error, unexpected ',', expecting '}'
# arr3 = Array.new(3) { 'a', 'b', 'c' }
#                          ^
# 以下略
```
---
しかし、下記のような書き方であれば、Array.newで異なる値を持つ配列を作成出来ます。

数値を2倍した値を持つ配列
最初の例は、数値を2倍にした値を持つ配列を作成する方法です。

```rb
p Array.new(3) { |i| i * 2 } # => [0, 2, 4]
```
このコードは、3つの要素を持つ新しい配列を作成し、それぞれの要素に0 * 2、1 * 2、2 * 2の計算結果を代入します。結果として[0, 2, 4]が得られます。

---
ランダムな数値を持つ配列
次の例では、1から10の間でランダムな数値を持つ配列を作成します。
```ruby
p Array.new(3) { rand(1..10) } # => 例えば [3, 7, 1] など
```
各要素が1から10までの範囲でランダムに選ばれた数値になります。これにより、[3, 7, 1]のような配列が得られます

---
異なる文字列の配列
["a", "b", "c"]は例えば下記のようにすると作成出来ます。
```ruby
p Array.new(3) { |i| (97 + i).chr } # => ["a", "b", "c"]
```
ASCIIコードで'a'が97なので、それにインデックスi（0から始まる）を足していき、.chrメソッドで対応する文字に変換します。
「絶対にミスをしない人の仕事のスゴ技BEST100」を読んでみた。（前編）

https://www.asuka-g.co.jp/book/business/012295.html

こちらの本は入社してから、現在のチームに入って1ヶ月くらいで思わず衝動買いした本です。
実際に書いてある内容をどこまで活かせているのかということが自分自身気になったので振り返りも兼ね、内容を紹介していこうと思います。
内容が多いので２回に分けて紹介します。

# 本の目次
プロローグも含めると大きく分けて８章から成っています。

Prologue　「ミスしない人」 になるために
Chapter 1　仕事を爆速化する 段取り
Chapter 2　ヌケ・モレ・遅れをなくす スケジュール管理
Chapter 3　仕事をなめらかに進める コミュニケーション
Chapter 4　手戻りをなくす 書類・メールの書き方
Chapter 5　気持ちよく仕事をする しくみ作り
Chapter 6　パフォーマンスを高める 生活習慣
Chapter 7　信頼を引き寄せる ミスのリカバリー

# Chapter 1　仕事を爆速化する 段取り
【内容】
<ul>
  <li>朝、今日やる仕事をデスクに出す</li>
  <li>準備に時間を割く</li>
  <li>優先順位を正しくつける</li>
  <li>作業は細分化して工程を書き出す</li>
  <li>気になっている仕事を先にやる</li>
  <li>ポジティブになれる仕事を用意する</li>
  <li>お客様ファーストにする</li>
  <li>締め切りに遅れそうなら早めに相談</li>
  <li>仕事が重なったら直接部門の依頼を優先</li>
  <li>プチ改善する</li>
  <li>「割り込み常習犯」と交渉する</li>
  <li>急ぎの仕事を減らす</li>
  <li>余裕のある時期に仕込む</li>
  <li>ムダ・ムラ・ムリをなくす</li>
  <li>自分専用！ 秘密のマニュアルを作る</li>
  <li>仕事は80点主義でいく</li>
</ul>


できていることは、準備に時間を割くことと自分専用のマニュアルを作ることです。
業務を進めるにあたって知らないことが多いのである程度下調べをして、
手順を明確にしてから進めることが多いです。
また、忘れっぽいのでマニュアルを意識的に作成するようにしています。
マニュアルはnotionを活用してメモしています。
こんな簡単なことまで？というところまでメモすることで全部忘れてしまってもその作業ができるようにしています。
それ以外はできていないですね。


# Chapter 2　ヌケ・モレ・遅れをなくす スケジュール管理
【内容】
<ul>
  <li>その場ですぐにメモする</li>
  <li>毎日ToDoリストを作る</li>
  <li>予定表の週始まりの曜日をそろえる</li>
  <li>自分の手を見て月の残り日数を知る</li>
  <li>My営業時間を決める</li>
  <li>相手の都合を考えて納期を設定する</li>
  <li>My締め切りを作る</li>
  <li>自分にアポをとる</li>
  <li>「すぐやります」と言わない</li>
  <li>日程調整も優先順位を考える</li>
  <li>他人の予定もおさえる</li>
  <li>始めと終わりの時間を相手に伝える</li>
  <li>「徒歩3分」の意味を知っておく</li>
  <li>外ランチは11時半にスタートする</li>
  <li>出張の手配は早い者勝ちと心得る</li>
  <li>オンライン会議のリマインドメールを送る</li>
</ul>

できていることはその場ですぐにメモすることです。
カレンダーに予定を書き込むようにしていますが、日常的なタスク管理ができていないです。
スケジュール管理や計画に沿って実行するというところは自分の苦手分野だと感じているので、改善の余地がありそうです。
長期的にタスク管理をして、それを細分化し日々のタスクを管理していくというのが徐々にできるようになりたいです。

# Chapter 3　仕事をなめらかに進める コミュニケーション
【内容】
<ul>
  <li>積極的に質問する</li>
  <li>指示を受けたらメモして復唱</li>
  <li>仕事の途中で報告する</li>
  <li>主観でなく客観で伝える</li>
  <li>６W３Hで指示やお願いをする</li>
  <li>ツールでイメージをすり合わせる</li>
  <li>約束したら証拠に残す</li>
  <li>変更や修正は「対比法」で伝える</li>
  <li>アイスブレイクで緊張をほぐす</li>
  <li>会議は事前に進行表を作る</li>
  <li>会議で敵を作らずに意見を言う</li>
  <li>オンライン会議中は内職禁止</li>
  <li>名前をど忘れしたら名刺交換</li>
  <li>よけいな一言で信用を失わない</li>
  <li>甘え上手になる</li>

  
イメージをすり合わせるというところは以前に比べてできるようになってきたのではないかと思います。
ただ、積極的に自分からコミュニケーションをとるというところは苦手なのでもう少し自分から関係構築ができるようになっていきたいと思います。

# 終わりに
本の前半部分を振り返ってみて、自分はまだまだ仕事の改善の余地があると思いました。
次の記事では本の後半部分を振り返りたいと思います。
Vision Pro を快適に装着する

## ヘッドバンドでサポートする
どこかで呼んだ記事を参考にVision Proにヘッドバンドを巻いてみたところ、とても快適。ヘッドバンドは頭頂部ではなくオデコに当てるようにするとしっかりサポートできた。  

![IMG_9758.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1839672/965a72f7-19c6-5b62-45cd-278f6d6f444a.jpeg)

オリジナルのニットバンドだけだと上下方向の負荷を顔の前側でサポートする形になるから、どうしても長時間使っていると目や頬のあたりが疲れてくるんだけど、ヘッドバンドを付けると、その負荷をオデコの上側で受け止めてくれるようで楽になる。

これ↓に近い機能。
![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1839672/76fab53f-fc74-eb8d-5b6e-e97f8a859351.png)


## Amazonで格安購入
購入はAmazonで。洗替えが3色付いて税込み799円でしたw
![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1839672/88fe08ec-d065-eeaa-689d-b61f1afc87f9.png)


https://amzn.asia/d/4TZZlyM