OmniAICreator/Qiita-1.07M huggingface dataset:
DBからの更新日時に基づく差分データ抽出

DWH などデータ分析基盤上のETL/ELT処理において、データソースからデータを抽出する際に、処理時間の短縮を目的としてデータソースから全量データではなく追加・更新されたデータ（以下、差分データ）のみを抽出する手法が良く使われます。

![03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/388675/64a9f5a0-7796-6cb4-9e5a-37ef4f0220a5.png)


このデータソースから差分データのみを抽出する処理の実装手段は複数あるのですが、比較的よく使われる手段の1つとして、データソース側でレコードが追加・更新された際に現在のタイムスタンプを格納する「更新日時」という列に基づいて差分抽出を行うというアプローチがあります。

例えば、まずDWH側で過去にどこまで追加・更新されたレコードを取り込んだかを以下のような SQL 文で確認します。

```sql:DWH側
select
    max("更新日時")
from
    "ステージングテーブル"
```

その後に、データソース側でその日時以降に追加・更新されたレコードのみを以下の SQL 文で抽出します。

```sql:データソース側
select
    "購入ID",
    "ユーザーID",
    "購入商品ID",
    "購入数",
    "更新日時"
from
    "購入履歴"
where
    "更新日時" > [DWH側のクエリで取得した日時]
```

（この方法は [dbt のincremental modelsに関するドキュメント](https://docs.getdbt.com/docs/building-a-dbt-project/building-models/configuring-incremental-models)の models/stg_events.sql を参考にしています）

この方法は差分データ抽出する際にデータソース側でデータ変更が発生していなければ概ね問題ないのですが（データソース側でレコードがDELETEされるケースを考慮しなければ）、データソース側でデータの追加・更新が発生している場合は、差分データを抽出し損ねる可能性があります。

例えば以下のようなケースです。

![06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/388675/4d204c36-2ed8-928c-a1b3-13c1d7570dc2.png)



1. トランザクション①が購入ID=101のレコードをINSERTするが、何かの原因（別の処理をする、ネットワークで少し待たされるなど）でコミットはすぐにはされない。
1. トランザクション②が購入ID=102のレコードをINSERTし、すぐにコミットされる。
1. 1回目の差分データ抽出の処理が動く。この時点で購入ID=102のレコードはコミット済みのため抽出されるが、購入ID=101のレコードは未コミットのため抽出されない。
1. トランザクション①がコミットする。
1. トランザクション③が購入ID=103のレコードをINSERTし、すぐにコミットされる。
1. 2回目の差分データ抽出の処理が動く。この際、更新日時の基準となる日時は抽出済みの購入ID=102の更新日時となるため、それより前にINSERTされた購入ID=101のレコードは今回も抽出されない（購入ID=103のレコードはもちろん抽出される）。

抽出漏れが発生する根本原因はレコードの更新日時とコミットのタイミングがずれていることにあります。

そのため、先に示した差分データ抽出の方法は、データソース側でデータ抽出中にデータの追加・更新が発生する可能性がある場合は使いづらいです。

対策としては幾つかあり、例えば以下のような方法があります。

- 案１：データ抽出の際にデータソース側のテーブルにロックを掛ける。
    - 最もラジカルな方法。ただ、これで困らないのであれば一番楽。
    - もちろん、データソース側のデータ追加・更新処理は待たせることになる。
- 案２：[抽出済みレコードの更新日時の MAX] < "更新日時" < (current_timestamp - α)の条件で抽出する。
    - 更新日時のの抽出条件に現在時刻より少し前の時刻を上限とすることで、この範囲に未コミットのレコードが含まれていないことを保証する。
    - αはトランザクションの最大時間を指定する。
    - 毎晩 1:00 に差分抽出のバッチ処理を動かし、その中で前日 0:00 - 24:00 までという条件を指定するのも本質的には同じ。
    - αより時間が掛かるロングトランザクションがあると危険。
    - ごく直近に追加・更新されたレコードの抽出は次回に回される。
- 案３："更新日時" > [抽出済みレコードの更新日時の MAX] - β の条件で差分データ抽出する。
    - 上の例の購入ID=101のレコードを2回目で抽出できるよう、条件を少し緩める。
    - 前回抽出した差分データと今回抽出する差分データに重複が発生する可能性があるが、それは DWH 側で何とかする。
- 案４：データの追加・更新処理が動いていないタイミングの過去断面からデータを抽出する。
    - 例えば、Oracle では v$transaction などを監視し、データ追加・削除が発生していない時刻を確認できたら、フラッシュバッククエリなどでその時刻の断面のデータから差分データ抽出を行う。
    - 案２と同様、ごく直近に追加・更新されたレコードの抽出は次回に回される。
- 案５：Change Data Capture（CDC）を使う。
    - 使えるなら使いたい。

どの方法が適しているかは要件次第ですが、データ抽出時にデータ追加・更新がないかはまず確認しましょう。
Swift 基本構文

XcodeのSwift Playgroundを使って基本文法の学習

## Swiftの変数

### よく使う変数の型の種類
| 型名 | 概要 |
|:-:|:-:|
| String | 文字列型 |
| Character | 文字型 |
| Int | 整数型 |
| Double | 浮動小数点型 |
| Bool | 真偽型 |

### コレクション型
配列や辞書型(連想配列)などはコレクション型と呼ばれる
![スクリーンショット 0004-08-13 21.06.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/420303/365cacd9-d1ac-22a7-ddb4-a95a1cbeb09c.png)



### オプショナル型(Optional)
オプショナル型は、通常の変数と異なり空の値（値が無い）状態を保持することができる変数。
オプショナル型を宣言するときは、変数の型の後ろに **「?」** をつける。
また、オプショナルは配列にも同様に宣言できる。
![スクリーンショット 0004-08-13 21.20.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/420303/a4276ec5-a4e3-5e7b-b090-ad5804999091.png)


```swift
//オプショナル型の宣言
var number: Int?

// 暗黙的なオプショナルの宣言
var text: String!
```

## Swiftの変数の宣言方法

```swift
let greeting:String = "Hello, playground"
(定数宣言) (定数名):(型) = "定義する値"

// カンマ区切りで1行の中に複数の定数や変数を定義することもできる
var colorA = red, colorB = black, colorC = blue
(変数宣言) (変数名) = "定義する値"...
```


playgroundでコードを実行
![スクリーンショット 0004-08-13 17.54.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/420303/7262b3ff-9739-a990-e9fe-7c1e4f197152.png)


Swiftでは型宣言をしていない変数は **初期値に格納された値の型が自動的に宣言(型推論)** される。
また、 **宣言された型とは異なる型の値を入れようとするとエラー** になる。
![スクリーンショット 0004-08-13 18.02.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/420303/9c746713-0ab4-0920-648a-5c3e02927ab8.png)


### 型変換(キャスト)
数値型から文字列型への変換(キャスト)するには`型名(変数)`で記述する
数値に変換できない文字列を渡されてしまうとエラーや予期せぬ挙動を起こす原因になってしまうため、関数の中で使える`guard 〜 else`構文を用いる。
![スクリーンショット 0004-08-14 0.14.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/420303/561fbee5-3332-6364-0c28-d48ecba819f6.png)


## Swiftの配列

![スクリーンショット 0004-08-14 1.22.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/420303/7b4e8c33-08e6-5edb-4302-bf16c6558b5d.png)

```swift
// 配列の宣言だけする場合は、[]を渡して初期化する
var arr: [Int] = []
```

## FizzBuzz問題
SwiftでFizzBuzz問題をやってみる
`FizzBuzz`の処理が実行される・されない → 上から条件式が実行された後`else`でその他の条件を返すため、14行目の論理演算子は実行されない
![スクリーンショット 0004-08-14 23.42.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/420303/cff9d242-f22c-0a84-0523-752f87586534.png)



## 参考
[The Swift Programming Language(日本語版)](https://www.swiftlangjp.com/)
[Swift の演算子](https://softmoco.com/swift-basics/swift-operator.php)
テックキャンプの感想

こんにちは、プログラミング初学者"fujitacoma"です！

今回は、テックキャンプでの学習や転職活動の感想をお伝えしたいと思います！
あくまで個人の感想ですので、ご参考までに。

### カリキュラムについて
カリキュラム終盤になってくると質問できない項目や質問制限が増え、
やり辛さも感じる反面、自己解決力の醸成にも繋がりました。
一部に学習内容が重複するカリキュラムも見られましたが、復習と思えば良いかと感じました。

### 講師陣の質
ライフコーチはじめ、メンター陣の質はとても高いと感じました。
口コミでは「肝心なところまで教えてくれない」といったものが散見されますが、
仮説・検証がしっかりできていればメンターからのヒントで十分かと思いました。

### 料金の満足度
決して安くはないですが、高すぎることもないと感じます。
「Udemyで20万課金したほうがいい」などの口コミもありますが、
得られるものは同じかもしれませんが過程が違うため比較になりません。
実際の現場を想定した学習環境が得られる時点で価値があると思います。

### 転職サポートの満足度
キャリアアドバイザーとの相性などもあるかもしれませんが、概ね満足です。
提示した条件に合わせてCAが募集企業を紹介してくれる形のため、
テックキャンプの保有求人全てを見ることはできないと思われます。
希望としては、テックキャンプの保有求人を転職サイトのように一覧できたり、
お気に入りに入れてCAと相談できるようなシステムがあれば良いと思いました。

### 入会前カウンセリングについて
質問はなんでも答えてもらえるので、事前に質問内容を考えておくのが吉かと思います。

### フォロー体制の満足度
カリキュラム中盤までは手厚いフォローがあります。
後半からは中盤まで学んだことの総合力や自己解決力が試されるため、
人によってはフォローが少ないと感じるかと思います。
オリジナルアプリの作成に関しては一切質問ができませんが、
機能によってはやり方次第でうまく質問できるので、そこも応用力かと。

### 受講環境について
#### 1日の学習時間
平日8時間以上、土日４時間程
#### 使用端末
ノートパソコン(MacOS)
OS指定あり
レンタルあり

### 受講料金について
#### 受講スタイル
オンライン受講
#### コース名
短期集中コース
#### 授業料
約60万円
給付金申請で約40万ほど還ってきます

### 最後に

項目ごとに簡単に纏めてみましたが、如何でしたでしょうか！
個人的にはテックキャンプでの学習はとても良い経験になりました。
ライフコーチやキャリアアドバイザー等は相性もあるかと思いますが、頼りになる方ばかりかと思います！

結果、私自身も3ヶ月の学習を経てエンジニア転職を果たすことができました！
ここからが本番ではありますが、テックキャンプでやり切ったという経験を軸に精進していきます。

もし、エンジニアへの道を考えていたり迷っている方がおられれば、
まずはテックキャンプの入会前カウンセリングを受けてみることをオススメします！

ここまでお読み頂き、有難うございました！
【typeorm】「オプションではない引数が 0 個では不足しています。少なくとも 1 個の引数が必要です」のエラー

## やろうとしていたこと
Expressとtypeorm使って、学習がてら個人開発してました。
entityを作ろうと思って、下記コマンドを叩いたら、

```
$  npx typeorm entity:create -n User

typeorm entity:create <path>

Generates a new entity.

オプション:
  -h, --help     ヘルプを表示                                             [真偽]
  -v, --version  バージョンを表示                                         [真偽]

オプションではない引数が 0 個では不足しています。少なくとも 1 個の引数が必要です:
```

タイトルのエラーが出ました。
引数が足りないって、どういうこっちゃ。公式に書いてある通りに叩いているのなんでエラーでるんだ？

## 原因
ヘルプオプションをつけて調べてみたところ、

```
$ npx typeorm entity:create -h

オプション:
  -h, --help        ヘルプを表示                                          [真偽]
  -c, --connection  Name of the connection on which to run a query
                                                         [デフォルト: "default"]
  -n, --name        Name of the entity class.                             [必須]
  -d, --dir         Directory where entity should be created.
  -f, --config      Name of the file with connection configuration.
                                                       [デフォルト: "ormconfig"]
  -v, --version     バージョンを表示                                      [真偽]
```
`-d, --dir         Directory where entity should be created.`というのが怪しいな
（エンティティーファイルを作るディレクトリを指定しないとだめか？）と思い、

```
$ typeorm entity:create -n User -d src/entity
```
を叩いてみたが、やはり結果は変わりませんでした。
公式ドキュメントを読んでも、詳しく書いていませんでした。

結局、原因はわかりませんでした。
もし、わかる方いましたら、教えてください。

## 対処法
下記バージョンに下げる。これで再度npm iすればとりあえずファイルは出来ました。

0.3.7
↓
0.2.37

## 参考にした記事
https://zenn.dev/chida/articles/bba2b5346414ed
huskyでGitでcommit前にeslintコマンドを自動実行したい


## 目次

- [目次](#目次)
- [概要](#概要)
- [準備](#準備)
  - [React雛形作成](#react雛形作成)
  - [起動確認](#起動確認)
  - [リンターの導入](#リンターの導入)
  - [リンターの動作確認](#リンターの動作確認)
- [huskyの導入](#huskyの導入)
  - [huskyのインストール](#huskyのインストール)
  - [コミット前のhooks作成](#コミット前のhooks作成)
- [検証](#検証)
  - [git addしてコミットしてみる](#git-addしてコミットしてみる)
- [参考](#参考)

## 概要

開発でリンターを入れている人はいるかと思いますが、eslintコマンドを実行し忘れてPRで指摘されるケースがたまにあります。

それをなくすために、commit前にeslintコマンドを自動実行する方法についてまとめてみました。

## 準備

### React雛形作成
```bash
npx create-react-app react-app
```

### 起動確認
```bash
npm start
```

![スクリーンショット 2022-08-14 20.08.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/882682/2cd40f38-80b5-1ba7-c0e4-af2b46074fbc.png)

### リンターの導入
以下対話形式で回答する
```bash
npm install eslint --save-dev
npx eslint --init

✔ How would you like to use ESLint? · style
✔ What type of modules does your project use? · esm
✔ Which framework does your project use? · react
✔ Does your project use TypeScript? · No / Yes
✔ Where does your code run? · browser
✔ How would you like to define a style for your project? · guide
✔ Which style guide do you want to follow? · airbnb
✔ What format do you want your config file to be in? · JavaScript
Checking peerDependencies of eslint-config-airbnb@latest
The config that you've selected requires the following dependencies:

eslint-plugin-react@^7.28.0 eslint-config-airbnb@latest eslint@^7.32.0 || ^8.2.0 eslint-plugin-import@^2.25.3 eslint-plugin-jsx-a11y@^6.5.1 eslint-plugin-react-hooks@^4.3.0
✔ Would you like to install them now? · No / Yes
✔ Which package manager do you want to use? · npm
```

package.jsonに以下を追加
```diff_json
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
+   "lint": "npx eslint . --ext .js",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
```

### リンターの動作確認
```
npm run lint
```

リンターが効いていることを確認。

## huskyの導入

### huskyのインストール
```
npx husky install
```

### コミット前のhooks作成
```
npx husky add .husky/pre-commit "npm run lint"
```

以下が作成される
```.husky/pre-commit
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

npm run lint
```

## 検証

### git addしてコミットしてみる
```bash
git add .
git commit -m "Introduce husky"

> react-app@0.1.0 lint /path/react-app
> npx eslint . --ext .js
```

git commit時に`npm run lint`コマンドを実行し、lintコマンドが自動実行されることを確認できた。

## 参考

https://dev.classmethod.jp/articles/pre-commit/
https://fwywd.com/tech/husky-setup
gitlabへのpush時に"you don't have permission"で失敗した問題の解決

# 概要
- 内輪用に立てたGitLabサービスでprivate設定のリポジトリにpushする際に失敗した
- リポジトリが見つからないとのことで、remoteの送り先にアカウント名を含むように修正した。

# 前提
- git: git version 2.37.1.windows.1
- gitリポジトリのProject visibility: Private
- 使用したプロトコル: https

# 背景
- 普段はPublicなリポジトリを操作する事が多かった
- しかし、家族にプログラム講座をやるためにプライベートな例を含めたリポジトリが欲しくなりPrivateなプロジェクトを作成した
- clone時は接続後に認証処理をしたが、push時はそもそもパーミッション的に見られないとのエラーが出た
- remoteのURLをアカウント名を含むように修正し、解決した。

# 現象
- 自分で立てたGitLabサービスに作ったPrivateなプロジェクトにpushしようとしたら以下のエラーが出た。
```
PS Microsoft.PowerShell.Core\FileSystem:<path名>> git push
remote: The project you were looking for could not be found or you don't have permission to view it.
fatal: repository 'https://<GitLabのドメイン>/<プロジェクト所有者名>/<プロジェクト名>.git/' not found
```

# 解決方法
- 元のremoteを消す  
```
git remote remove origin
```  

- 元のremoteのURLをアカウント名を含むように書き換える
```
git remote add origin https://<アカウント名>@<GitLabのドメイン>/<プロジェクト所有者名>/<プロジェクト名>.git  
```

# おわりに
- もしかすると、 `git push'のエラー発生条件は"Project visibility: Private"ではないかもしれないが、絞り込めなかった
- Enjoy git!
 
# 参考サイト
- [gitlabに新プロジェクトをpushできなくて困った話(https編)](https://bitto.jp/posts/%E6%8A%80%E8%A1%93/git/gitlab-new-push-failed/)
Colabでの綺麗な実験

# 0. はじめに
- この記事は,google colaboratoryを使用して実験をする際の効率を上げることを目的とした記事です.
- 結論としては,`%run main.py`,`subprocess.make()`をうまく使用してコードの綺麗さ,実験の効率をあげることを提案します.

# 1. なぜGoogleColaboratoryを使うの?

- Googleのアカウントを持つ誰もが,[GoogleColaboratory](https://colab.research.google.com/?hl=ja#scrollTo=OwuxHmxllTwN)を使うことで,GPUを使えるため,行列計算が早く行えます(無償の場合,時間制約あり).
- また,GoogleColabotatoryで実行することで,計算結果をその場で確認することができます.

# 2. Colabの利点と欠点
- 利点
    - 結果が見やすい.
    - 可視化をしやすい.(可視化によるデータの確認等)
- 欠点
    - セルごとに実行するため,ただ単にGPUを回すことが目的である場合はエンターボタンを押すのがうんざりする.
    - コードが見づらい.
    - 外部ライブラリを使用する際に高頻度で`! pip install sth -qqq`をする必要がある.
    - 制限のあるGPUの時間を気にしながら実行しなければならない.

# 3. 提案

- 提案
    - `%run main.py args`,`subprocess.make()`を駆使してセルを綺麗にする.
- 提案理由
    - 利点
        - `%run main.py` という短いコードで命令を実行することができ,かつ,編集するファイルはmain.pyとそれが使用するmoduleのみとなり,ノートブックではなく,Pythonファイルのみを編集して開発することができます.
        - `subprocess.make()` を駆使することで,linuxのpkillというコマンドを実行できるため,自動でセッションを切ることができるため,GPUの実行時間を軽減できる点.
    - 不便な点
        - 可視化や新しい情報の発見をするためのものではないため注意が必要な点.

# 4. サンプルプログラム

今回はtorch.tensorの値をprintするだけの関数を,moduleとpythonファイルを用いてサンプルを用意しました.

- Google DriveのURLです: 
    - https://drive.google.com/drive/folders/177Zns9qxF3ec9Ps6QnEgSomivp62hDbR?usp=sharing
- GitHubのURLです:
    - https://github.com/makinzm/Qiita-Blog-2022-08-14

概要としては
- `%run` で pyファイルを実行
  - これをすることで,cellを綺麗に保てます.
- subprocess で pip install, pkill をする
  - subprocessをすることで,`!`による実行を回避できます.
  - これのおかげで,`.py`ファイルで実行できる処理が増え,`pkill`によりprocessを強制的に終了できるため,GPUを無駄に使うことを回避できます.


実際のnotebook上でのコードは以下のようになります([githubのリンク](https://github.com/makinzm/Qiita-Blog-2022-08-14/blob/main/main.ipynb)):

```py
from google.colab import drive
drive.mount('/content/drive')

### *** ###
# 通常(2022/08/14時点)では
# 1.12.1+cu113がデフォルトでinstallされているが, 
# 次の指定で 1.0.0をimportできる.
from subprocess import run
run("pip install torch==1.0.0",shell=True)
import torch
print("torch.__version__: ",torch.__version__)
### *** ### 

%run /content/drive/MyDrive/Qiita_env_01/main.py 20220814
```
```console
Mounted at /content/drive
torch.__version__:  1.0.0
torch.__version__ in qiita.func.func:  1.0.0
Printed Number Is 20220814 😄
```

# 5. 現状私が理解していないこと
### 5.1. [no-good.py(githubのリンク)](https://github.com/makinzm/Qiita-Blog-2022-08-14/blob/main/no_good.ipynb),
- `pip install`の内容を pythonファイルに含めて`run`をしてしまうと,正しく実行することができませんでした.
    - おそらく非同期処理の処理が実行されてしまったかと推測されていますが,改善する方法が現状追えてはいません.
- このことから,`.py`ファイル内で`pip install`は難しいのではないかと考えています.

```python
from google.colab import drive
drive.mount('/content/drive')

%run /content/drive/MyDrive/Qiita_env_01/main.py 20220814
```
```console
Mounted at /content/drive
torch.__version__ in qiita.func.func:  1.12.1+cu113
Printed Number Is 20220814 😄
```

# 6. 最後に

この記事に立ち寄ってくださり,ありがとうございました.
なにかご指摘やご質問あれば,遠慮なくお尋ねください.
【Laravel】リレーション先のデータを条件指定して取得する

## やりたいこと

例えば usersテーブルとblogsテーブルが1対多の関係にあり、全てのユーザー情報と、ユーザーが書いたブログのうちcategoryがPHPのブログ情報をまとめて取得したい、というような場面。

Laravelでは `User::with('blogs')` のように記述することでリレーション先のテーブルのデータもまとめて取得することが出来るが、その際に取得するリレーション先のデータの条件指定の方法について、備忘録としてまとめておく。

## 記述方法

結論、以下の記述でやりたいことが実現できる。

```php
$items = User::with(['blogs' => function ($query) {
	$query->where('category', 'PHP');
}])->get();
```

実行しているSQLを出力して確認してみる。

```php
DB::enableQueryLog();

$items = User::with(['blogs' => function ($query) {
	$query->where('category', 'PHP');
}])->get();

Log::debug(DB::getQueryLog());
```

SQL出力結果

```php
array (
  0 => 
  array (
    'query' => 'select * from `users`,
    'bindings' => 
    array (
    ),
    'time' => 9.9,
  ),
  1 => 
  array (
	    'query' => 'select * from `blogs` where `blogs`.`user_id` in (1, 2, 3, ...省略
```

上記の通り、ユーザー情報は全て取得し、ブログについてはcategoryがPHPのもののみ取得できていることが分かる。

## 失敗例

ちなみに以下の書き方だと、whereHasの条件に合うユーザー情報を取得する。

よって今回のように、ユーザー情報は全て取得したうえで、リレーション先のデータは指定した条件に合うものだけ取得したい、、という場合は以下の書き方ではいけないので注意。

```php
$items = User::with(['blogs'])->whereHas('blogs', function ($query) {
	$query->where('category', 'PHP');
}])->get();
```

こちらも実行しているSQLを出力して確認してみる。

```php
DB::enableQueryLog();

$items = User::with(['blogs'])->whereHas('blogs', function ($query) {
	$query->where('category', 'PHP');
}])->get();

Log::debug(DB::getQueryLog());
```

SQL出力結果

```php
array (
  0 => 
  array (
    'query' => 'select * from `users` where exists (select * from `blogs` where `users`.`id` = `blogs`.`user_id` and `category` = ?),
    'bindings' => 
    array (
      0 => 'PHP',
    ),
    'time' => 20.21,
  ),
  1 => 
  array (
    'query' => 'select * from `blogs` where `blogs`.`user_id` in (1, 4, 5, ...省略
```

上記の通り、existsでサブクエリを使って、リレーション先で指定した条件に合致するユーザー情報とそのリレーション先のデータを取得していることが分かる。

## さいごに
実行されているSQLを確認すると、何が起きているのか分かりやすかった。

普段からEloquentを使うときにはどのようなSQLが実行されるのか把握し、SQLの実行計画も確認して重くなっていないか意識するようにしたい。

## 参考記事
https://qiita.com/daisei-yoshino/items/c64bce0c1c8b39a8e04b
WillPopScope を使った際にiOSで 'swipe back gesture' が使えなくなる問題の対処法



## 概要
WillPopScope を使った際にiOSで 'swipe back gesture' が使えなくなる問題の対処が地味に難しかったので紹介します。

## 解決策について

この問題については、

https://github.com/flutter/flutter/issues/14203

で議論されていましたがまともな解決策が出ていないようでした。

そこで、いろいろ調べてみたところ 

https://pub.dev/packages/cupertino_will_pop_scope

というパッケージにたどり着き、これを使うことで思い通りの実装ができました。


## 実装

インストールやthemeの設定は本家の方を参照していただくとして、工夫した点だけ紹介します。

サンプルだと、

```dart
@override
Widget build(BuildContext context) {
  return WillPopScope(
    child: _MyScreenContent(),
    onWillPop: _hasChanges ? _onWillPop : null,
  );
}
```

という実装になっています。しかし、`_hasChanges`は戻るボタンを押した際の値ではなく、レンダリング時の値になってしまうので戻るボタンを押したタイミングで変更があったかどうかというのは`_onWillPop`の中で実装する必要があります。

しかし、次のように実装すると

```dart
Future<bool> _onWillPop() async {
  if(!_hasChanges){
    Navigator.of(context).popUntil(ModalRoute.withName('/'));
    return true;
  }
  return await showDialog(
      context: context,
      builder: (_) => AlertDialog(
        title: const Text("保存しますか？"),
        actions: <Widget>[
          TextButton(
            onPressed: () {
              ...
            },
            child: Text(
              "はい",
            ),
          ),
          TextButton(
            onPressed: () {
              Navigator.of(context).popUntil(ModalRoute.withName('/'));
            },
            child: Text(
              "いいえ",
            ),
          ),
        ],
      ),
    ) ??
    false;
}
```

変更があった場合は想定通りの動きをしますが、変更がなかった場合、ページが思い通り遷移してくれません。

この問題については、次のように変更することで解決しました。

```dart
  if(!_hasChanges){
    await Future.delayed(const Duration(microseconds: 1)); // <-追加
    if (!mounted) return true;                             // <-追加
    Navigator.of(context).popUntil(ModalRoute.withName('/'));
    return true;
  }
```

原因はよくわかっていないですが、何かしらの処理を挟まないと、`Navigator.of(context).popUntil(ModalRoute.withName('/'));
`がうまく実装されないようだったので、ごくわずかな時間だけ待たせることで思い通りの動作をさせることができました。

## 参考文献
https://pub.dev/packages/ios_willpop_transition_theme/example


https://github.com/flutter/flutter/issues/14203

https://github.com/flutter/flutter/issues/58907


## 余談

別の方法として、

https://qiita.com/sanbasan/items/47b472e03c287c005a6b

のようにスワイプ処理を通してページ遷移させようと試みたのですが、なぜかうまくいきませんでした。。。
[Go]ジェネリクス

# 概要
**同じコードで様々な型のデータを処理できるようにする** 機能。
動的型付け言語っぽく処理させることができる。
goではバージョン1.18から追加された。

関数の引数、返り値ともにその型を「型パラメータ」 + 「型パラメータリスト」で定義できる

**参考**

https://zenn.dev/nobishii/articles/type_param_intro

# 引数の型を「型パラメータ + 型パラメータリスト」で定義

**一般化したコード**
```generaralized_argument_generics_sample.go

// [<型パラメータ名> <型パラメータの制約>]の部分が「型パラメータリスト」と呼ばれる部分
func <関数名>[<型パラメータ名> <型パラメータの制約>](<引数名> <型パラメータ名>) {
   <処理>
}
```

**具体的にジェネリクスを適用したサンプルコード**

```argument_generics_sample.go
package main

import "fmt"

/* 
 * [概要] 引数の型と値を出力する
 * [引数] 
 *   prefix: 前に挿入する接頭辞(string)
 *   printTarget:  型と値を出力する対象([T any])
 * [詳細]
 *   以下のような文字列がターミナルに出力される
 *   [<prefix>] 型: <printTargetの型>, 値: <printTargetの値>
 */
func PrintTypeAndValue[T any](prefix string, printTarget T) {
  fmt.Printf("[%s] 型: %T, 値: %+v\n", prefix, printTarget, printTarget)
}

func main() {
  intVar := 1
  stringVar := "1"
  float64Var := 1.1
  PrintTypeAndValue("intVar", intVar)
  PrintTypeAndValue("stringVar", stringVar)
  PrintTypeAndValue("float64Var", float64Var)

  // 上記コードを実行すると以下のように出力される
  // [intVar] 型: int, 値: 1
  // [stringVar] 型: string, 値: 1
  // [float64Var] 型: float64, 値: 1.1
} 
```

# 型パラメータの制約指定方法
以下が使える

- ``any``: ガチでどんな型でもOK
- ``comparable``: ``==`` と ``!=``が使えるすべての型
- ``<型名> | <型名>...``: 自分で型指定(インターフェースを定義することでスッキリすることが可能)

**参考**

https://re-engines.com/2022/02/07/go-generics/


# 型パラメータ名の慣習
**「型パラメータ名が1つだけ登場するならT、型パラメータ名が複数登場するなら、大文字一文字でそれぞれいい感じにつける」**

**参考** 

https://future-architect.github.io/articles/20220209a/


# 参考:
ゆめみさんのフロントエンドコーディング試験のお題を SolidJS で作った

## はじめに

[ここ](https://notion.yumemi.co.jp/0e9ef27b55704d7882aab55cc86c999d)で試験内容が公開されてたので、お題のサイトを SolidJS で作ってみた。

実際に試験を受けるわけじゃないので、制約は以下のように改ざんした。

> React/Vue.jsのいずれかを用いてSPAを構築すること

→ SolidJS を使う

> グラフは Highcharts や Rechart.js などのサードパーティ製のグラフライブラリを用いて描画すること

→ グラフライブラリは使わず、無理やり描画する（後述）

> PC/スマートフォン表示に対応すること

→ スマホ対応はやらない（PC 版を作って満足してしまった）


## 作ったもの

https://population-spa.ahyaemon.com/

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244354/c443f735-377d-b31a-7b85-e4e5f01ecd4e.png)

我ながらダサい。


## 技術スタック

- TypeScript
- SolidJS
- Jest
- ESLint
- Prettier


## SolidJS

tsx で書けるので、React と書き味は似ている。
ただコンポーネントにリアクティビティを持たせるのにちょっと癖がある感じがした。
例えば親から受け取った Props を加工して表示する場合、一旦シグナル（React で言うステート）を作って Effect でセットしないといけない。
（試行錯誤しながら実装したからもっといいやり方があるかも）


ダメな例
```typescript
const Counter: Component = () => {
  const [count, setCount] = createSignal<number>(0)

  return (
    <div>
      <p>count: {count}</p>
      <p>twice: <Twice n={count()}/></p>
      <button onClick={() => { setCount(c => c + 1) }}>add</button>
    </div>
  )
}

const Twice: Component<{n: number}> = (props) => {

  const twice = props.n * 2

  // 最初に渡された n = 0 で計算された値が更新されない
  return <>{twice}</>
}
```

add ボタンを押しても twice が更新されない。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244354/5b9c891d-b97e-4021-ba3b-4b0ac4bfd028.png)

createEffect で、n の更新のたびに Effect をかけると動く。

```typescript
const Twice: Component<{n: number}> = (props) => {
  const [twice, setTwice] = createSignal<number>(0)

  createEffect(() => {
    setTwice(props.n * 2)
  })

  return <>{twice}</>
}
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244354/6239370a-14f5-ed72-44e9-bdb95bd5ad93.png)

return の中で `props.n * 2` を計算しても動くけど、props から複雑な値だったり、複数の値を導出してから使いたい時は向いてないと思う。

```typescript
const Twice: Component<{n: number}> = (props) => {
  return <>{props.n * 2}</>
}
```

{} の中に式を入れても動く。正直あんまり良くわかってない。

```typescript
const Twice: Component<{n: number}> = (props) => {
  const twice = () => props.n * 2

  return <>{twice}</>
}
```


## グラフ

簡単な描画ならライブラリ要らないかなーと思ってやってみたけど結構大変だった。
やり方としては div で箱を作って、そこにグラデーションを掛けて斜線を模すようにした。

```typescript
const Bar: Component = () => {
    const style = `
        width: 100px;
        height: 200px;
        background-image: linear-gradient(to right bottom, transparent 48%, black 48%, black 52%, transparent 52%);
    `

    return <div style={style}></div>
}
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244354/8d727282-8c34-7468-6b04-fcd3eaf24611.png)

この height とかをコンポーネント内で計算して、それを並べてグラフにした。
（大変な見た目になったけど、実装していたらなんとなく味があるようなこれはこれでアリなような気がしてきた）


## API キー

RESAS(地域経済分析システム) の API を使って人口データを取得すると言う指定があった。
RESAS は無料で利用登録することで API キーを発行してくれて、それを使って API を叩くことができる。
API キーをリポジトリに置いておくと全世界に公開されてしまうので、今回は人口データは事前に全て手元で取得しておき、それをリポジトリに置くことにした。
取得した人口データはダイナミックインポートするようにして、非同期 API 呼び出しを模すようにした。

興味本位で他にもこのお題を実装してる人がいないかググってみたけど、API キーを公開してる人もいたりして、それはやめた方が良いんじゃないかと思った。
あと「API キーをいかに見えないようにするか。隠すか。」みたいな話も見かけたけど、そもそも自分が取得した API キーを使って他人が API を叩けること自体微妙な気がする。


## 終わりに

自分の css 力の無さを実感しました。
【Tableau Data Saber挑戦記】あるユーザー層とあるユーザー層を重ね合わせて分析したいとき〜結合セット〜

# はじめに
あるユーザー層とあるユーザー層を重ね合わせて分析したいときもあると思います。
例えば、「購入金額〇〇円以上の顧客」と「サイト流入〇〇回以上の顧客」で条件を設定して売上などの指標がどうなるかを見たいとか。そういうグループ化やその掛け合わせだったりもtableauなら簡単にできちゃいます。

# そもそも「セット」とは？
セットとはグループのことです。「売上TOP10の顧客」とか「利益率WORST5の顧客」とかいった集団です。それらを掛け合わすことができるのが「結合セット」です。
[上位 N とそれ以外のセット - Tableau](https://help.tableau.com/current/pro/desktop/ja-jp/sortgroup_sets_topn.htm)

[Tableau（タブロー）再現シリーズ（応用編）_３／２０選（結合セット（集合）の作成）](https://web-kaizen.co.jp/2017/08/24/tableau_%E7%B5%90%E5%90%88%E3%82%BB%E3%83%83%E3%83%88/)
こちら↑の記事では売上上位100の製品名のセットと利益下位100の製品名のセットの結合セットを作成しています。
セットを2つ選択したまま右クリックで「結合セットの作成」で作れちゃいます。


# 和集合・積集合（「または」・「かつ」）
結合セットの作り方は先の通りですが、結合のさせかたについてです。
◯と◯を重ね合わせたベン図が出てきます。高校数学の「場合の数・集合」や「確率」で出てきたやつです。

集合についてはこちらが参考になるかと思います。
[数学入門：「和集合」「積集合」の概念をシミュレーターを用いて解説！](https://retu27.com/math_unionset.html)

AかつB（積集合）

AまたはB（和集合）


# 結果
https://youtu.be/R3ESTiBevUA?t=95

この動画では「売上top100」かつ「利益worst100」でくくっています。（つまり、売上高くても利益出てないっていうもの）

動画のように該当のものが青く表示されています。これを見たら「売上高くても利益が出てないなら縮小する」とか次のアクションを明確にすることができます。


このセットについては業務の場面で色々活用の仕方があるんじゃないかなって思っています。
例えば、「APPログインユーザー」かつ「ショッピング利用ユーザー」とか
「あるキャンペーンに応募している人」と「別のキャンペーンには応募しているけどあるキャンペーンには応募していない人」の比較だったりも新たな情報や施策を得るのに重要だと思います。

【Tableau Data Saber挑戦記】の最初の記事はこちら
こちらもぜひ

[【Tableau Data Saber挑戦記】1. はじめます](https://qiita.com/hetare/items/d55a7dda34f3b0e9073b)

https://qiita.com/hetare/items/d55a7dda34f3b0e9073b

以上、お粗末さまでした。
STM32G031とI2Cを使い0x80に「hello...」と出力する。(Keil)



目的
I2C-USB変換を用いたライブラリーもどき

![o_con673.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/840352/3f6c5e77-0f06-18b7-2dbe-ef394f8214ce.jpeg)


![o_con674.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/840352/b82b3ce6-908e-f53c-6891-29050183d2d7.jpeg)


![o_con675.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/840352/a6d5a2f7-0263-0efe-2882-b26adc3eaa0a.jpeg)


![o_con676.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/840352/02ef416c-8cfb-8df8-23fd-044ebcc896cf.jpeg)

![o_con677.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/840352/6cf9287f-ac84-8a7a-b652-c392e4bb0d85.jpeg)



出力先

https://qiita.com/caa45040/items/5032e6905c9425f41495


main.cpp


```c++


//I2C_TEST_STRING_R1_031_1

#include "mbed.h"

#include "OUT80.h"

DigitalOut myled(PB_7);

//        sda   scl
I2C i2c(PA_12, PA_11); //031


//メインプログラム
int main()
{

    OUT80 out80(&i2c);

    //無限ループ
    while(1) {

        myled = 1;//LEDをオン

        //I2Cに文字列を出力
        out80.ns_puts( (char *)"hello world\r\n");

        //I2Cに文字列を出力
        out80.ns_printf( (char *)"%d",1234);

        //I2Cに文字を出力
        out80.ns_putc('\r');
        out80.ns_putc('\n');

        myled = 0;//LEDをオフ

        wait_us(1000*1000);//1秒待つ

    }//while

} // main





```


OUT80.h


```c++


//OUT80.h

#include "mbed.h"

struct OUT80 {

    OUT80( I2C *i2c );

    void initialize();

    void ns_putc(char ch);

    void ns_puts(char *str1);

    void ns_printf(char* fmt, ...);

    I2C *_i2c;

};



```

OUT80.cpp


```c++


//OUT80.cpp

#include "mbed.h"

#include "OUT80.h"

//最初に呼ばれるプログラム
OUT80::OUT80( I2C *i2c )
{
    _i2c = i2c;

    initialize();
}//OUT80


//イニシャライズ
void OUT80::initialize()
{
    //_i2c->write(0x80, "START", 3);
}//initialize


void OUT80::ns_putc(char ch)
{
    _i2c->write(0x80, &ch, 1);
    wait_us(1000);//約1ms秒待つ
}//ns_putc


//文字列の表示 nana_seg
void OUT80::ns_puts(char *str1)
{
    //文字の中身がゼロか
    while(*str1) {
        ns_putc( *str1 ++);
    } //while
}//ns_puts


void OUT80::ns_printf(char* fmt, ...)
{

    char buff[256];
    va_list args;
    va_start(args, fmt);
    vsnprintf(buff, sizeof(buff), fmt, args);
    va_end(args);

    char *s = buff;
    while (*s) {
        ns_putc(*s++);
    }//while

}//ns_printf




```
公開鍵暗号方式をかじった

(追記：かじった自分用メモです！正確な情報は他の方をご参照ください！)

GitHubの鍵作ったりしてて、秘密鍵ってなんで必要なの？なんで教えちゃいけないの？って思っていたので調べた　なるほど〜！

読んだサイト：「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典


https://wa3.i-3-i.info/word1830.html


![IMG_F467F113F6F3-1.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695931/e2d1d00f-da93-ac44-ec53-45ef6e11865b.jpeg)
初心者向けSQLメモ SELECT(GROUP BY)

# はじめに

SQL未経験の私が、勉強したことを整理、備忘のための記事を作成しました。
1つの記事だと長くなりすぎるので、分割した1つになります。

なお、この記事内のサンプルはすべて、以下のテーブルに対して実施したものです。
|id|name|goods|price|
|-|-|-|-|
|0001|里帆|時計|200000|
|0002|美桜|カバン|150000|
|0003|遥|アクセサリー|300000|
|0004|里帆|カバン|300000|
|0005|美桜|アクセサリー|200000|
|0006|遥|カバン|500000|
|0007|美桜|カバン|200000|

## グループ分け(GROUP BY)
- 指定したカラムの値をキーとしてグループ化できる
- GROUP BY句にないカラムをSELECT句で使用することはできない
- GROUP BY句で別名を使用できない
- 実行順序`FROM` ⇒ `WHERE` ⇒ `GROUP BY` ⇒ `SELECT`

```console:構文
SELECT <カラム1> ,<カラム2>...
  FROM <テーブル名>
 GROPU BY <カラム1>, <カラム2>...
```

##### サンプル

```SQL
SELECT name
  FROM hogehoge
 GROUP BY name;
```

|name|
|-|
|美桜|
|遥|
|里帆|


##### サンプル(集約関数と組み合わせ)

```SQL
SELECT name, COUNT(*)
  FROM hogehoge
 GROUP BY name;
```

|name|count|
|-|-|
|美桜|3|
|遥|2|
|里帆|2|

##### サンプル(複数カラムでグループ化)
```SQL
SELECT name, goods, COUNT(*)
  FROM hogehoge
 GROUP BY name, goods;
```
|name|goods|count|
|-|-|-|
|美桜|カバン|2|
|遥|アクセサリー|1|
|美桜|アクセサリー|1|
|遥|カバン|1|
|里帆|時計|1|
|里帆|カバン|1|

##### サンプル(NG)
- GROUP BY句にないカラムをSELECT句では使用できない
この例だと`name`でグループ化されており、グループ化されたレコードに`goods`は混在しているので、`goods`でSELECTできない
```SQL
SELECT name, goods, COUNT(*)
  FROM hogehoge
 GROUP BY name
```

##### サンプル(これはOK)

```SQL
SELECT name, COUNT(*)
  FROM hogehoge
 GROUP BY name, goods;
```
|name|count|
|-|-|
|美桜|2|
|遥|1|
|美桜|1|
|遥|1|
|里帆|1|
|里帆|1|

## グループ化したデータを絞り込む(HAVING)
- 実行順序`FROM` ⇒ `WHERE` ⇒ `GROUP BY` ⇒ `HAVING` ⇒ `SELECT`

##### サンプル
```SQL
SELECT name, COUNT(*)
  FROM hogehoge
 GROUP BY name
HAVING COUNT(*) = 2;
```
|name|count|
|-|-|
|遥|2|
|里帆|2|

## 検索結果の並び替え(ORDER BY)
- 実行順序`FROM` ⇒ `WHERE` ⇒ `GROUP BY` ⇒ `HAVING` ⇒ `SELECT` ⇒ `ORDER BY`
- 複数指定したときは、左側のキーから優先してソート

##### サンプル
```SQL
SELECT name, goods, price
  FROM hogehoge
 ORDER BY price;
```
|name|count|price|
|-|-|-|
|美桜|カバン|150000|
|美桜|アクセサリー|200000|
|美桜|カバン|200000|
|里帆|時計|200000|
|里帆|カバン|300000|
|遥|アクセサリー|300000|
|遥|カバン|500000|

##### サンプル(降順)
```SQL
SELECT name, goods, price
  FROM hogehoge
 ORDER BY price DESC;
```
|name|count|price|
|-|-|-|
|遥|カバン|500000|
|遥|アクセサリー|300000|
|里帆|カバン|300000|
|里帆|時計|200000|
|美桜|アクセサリー|200000|
|美桜|カバン|200000|
|美桜|カバン|150000|

##### サンプル(複数のソートキー)
```SQL
SELECT name, goods, price, id
  FROM hogehoge
 ORDER BY price, id;
```
|name|count|price|id|
|-|-|-|-|
|美桜|カバン|150000|0002
|里帆|時計|200000|0001
|美桜|アクセサリー|200000|0005
|美桜|カバン|200000|0007
|遥|アクセサリー|300000|0003
|里帆|カバン|300000|0004
|遥|カバン|500000|0006
NginxでMinecraft BE用のUDP Proxyをたてる

# 環境
さくらのVPS
-> Mem:1G
-> OS: Ubuntu20.04LTS
-> Nginx: 1.22.0

# NginxとUDPproxy

VPSを借りてMinecraftマルチプレイサーバー(Java版、統合版クロスプレイ)のためのプロキシサーバーを立てようと思いついた。
Java版の通信についてはVPSにHAproxyを置いて対応した。
しかし統合版の通信はUDPなのでTCPにしか対応していないHAproxyでは通信を捌くことが出来ない。
そこでUDPについてはNginxを用いることにした。
(Webサーバーなのにプロキシサーバーとしても使えるらしい。そしてTCP・UDPともに対応している)

# Nginxの導入
`sudo apt install nginx`で取得できるNginxは最新バージョンではないので使わない。
適当なディレクトリを作成してwgetでNginxを取得する。
Nginx公式(http://nginx.org/en/download.html) に飛んでStableの左から２つ目のリンクをコピーして`wget [コピーしたリンク]`で取得。

.tar.gz形式のファイルがゲットできるのでtarコマンドで解凍＆展開


(https://gakumon.tech/nginx/nginx_install_option.html) を参考にして
`./configure`の引数を書く
今回は`./configure --with-stream`を実行すれば必要なものは集まる
```
checking for C compiler ... not found

./configure: error: C compiler cc is not found
```
と言われてプロセスが停止したら、`sudo apt install gcc` を実行して C 言語のコンパイラをインストールしたのちに再実行してください。

./configureを実行したら`make`を実行

処理が終わったら`make install`を実行
(パーミッション関連のエラーが出たらsudoをつけて実行)

# .confを書く
minecraft-udp.confのように名前を付けて保存する。
デフォルトのコンフィグファイルと同じ階層に置く。
(ここでは`/etc/nginx/nginx.conf`と同じ階層)



※自宅のグローバルIPアドレス、使用するポートは自分の環境に合わせて変えてください

```config:minecraft-udp.conf
user www-data;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;

events {
    worker_connections 1024;
}

stream {
    upstream backend {
    server 自宅のグローバルIPアドレス:使用するポート;
    }

    server {
        listen 使用するポート udp;
        proxy_pass backend;
    }
}

```
4行目のinclude(以下略)を抜かしてしまうと必要なモジュールが読み込まれず、起動時にエラーを吐くので書き忘れてはいけない

# configファイルのテスト
`/usr/sbin/nginx -t -c /etc/nginx/minecraft-udp.conf`
を実行すると先ほど書いたコンフィグファイルの構文チェックを行うことができる。
ここでエラーが出た場合はその文に従って修正すること。


user関係のエラーがでるようであれば`user nobody nogroup;`にして試してみると良い。


:::note info
 `/usr/sbin/nginx` の部分はパスを通していればその通りに書く必要は無い。
これ以降の説明でも同様。
:::


# 動かす
`nginx`だけで動かそうとするとデフォルトコンフィグを使われてしまう。
先ほど書いた`minecraft-udp.conf`に書いた設定を適用してNginxを動かしたいのでコマンドを変える。
参考(https://www.server-memo.net/server-setting/nginx/nginx-command.html)
`/usr/sbin/nginx -c /etc/nginx/minecraft-udp.conf`
と打つと、Nginxが先ほど書いたminecraft-udp.confの設定を適用して動いてくれる。
(/usr/sbin/nginxの部分は環境により異なる可能性あり)

# 経過
稀に通信が不安定になることがあるが、よく動いてくれている。
【Android】時間を表示するView『TextClock』

# 初めに
今回はデジタル時計で時間を表示させるためのView 『TextClock』というものうを見つけたので、それをご紹介します

# TextClock
デジタル時計を表示させるには　**TextClockクラス**か　Layoutに**TextClockタグ**を追加して用います。
TextClockはTextViewの子ViewなのでTextViewのメソッドを使用することができます。

TextClockのデジタル時計の表示形式は２４時間表示か１２時間表示のどちらかを定義することができます。
**setFormat24Hour**メソッドを使用すると２４時間表示内容を定義することができます。
XML側で定義する場合は
```
　android:format24Hour="HH:mm:ss"
```
といった感じで定義することができます。

# タイムゾーンを指定
TextClockではデジタル時計のタイムゾーンを指定することができます。
setTimeZoneメソッドを使用するとタイムゾーンの指定が可能になります。
XML側で定義する場合は
```
android:timeZone="GMT+900"
```
とすると日本の時刻（GMT+900）を設定することができます。

または
```
android:timeZone="Asia/Tokyo"
```
という設定することも可能です。
ドメインプリミティブについて

# はじめに
最近[セキュア・バイ・デザイン 安全なソフトウェア設計](https://onl.sc/8UiGd49)（以下、セキュア・バイ・デザイン）を読んだので、学んだことをアウトプットしようと思います。
今回は特に印象に残ったドメインプリミティブについてです。

# ドメインプリミティブとは
セキュア・バイ・デザインで紹介されている実装パターンのことです。
バリューオブジェクトとよく似ていますが、似て非なるものになります。
ドメインプリミティブはバリューオブジェクトの特性を持ちつつ、さらに以下の特性があります。
 - プログラミング言語における基本データ型やStringなどのような汎用的な型を使用しない
 - Nullを許容しない
 - 不変条件をオブジェクトの生成時に確認する

# 不変条件とは
ドメインが持つ何が起きても変わることのない特性のことを不変条件といいます。
 - 年齢の場合
    - Nullでないこと
    - 整数であること
    - 0から150の間であること(150は人類最高齢が122なのでそこから余裕持った値)
 - 携帯電話番号の場合
   - Nullでないこと 
   - 070,080,090のいずれかから始まること
   - 11桁の整数であること

上記のような条件をドメインに持たせることにより、よりセキュアなオブジェクトを生成することができるようになります。
不変条件は、ドメインエキスパートなどとしっかり話し合ってドメインを深く理解することで見えてきます。
型や桁数、受け入れるべき文字の種類など細かいところまでしっかりと話すことが大事です。

# ドメインプリミティブとバリューオブジェクトの違い
実際にコードでドメインプリミティブとバリューオブジェクトを比較してみます。
今回は年齢をお題としてやってみます。

## バリューオブジェクト
```java
public record Age(Integer value) {}
```

## ドメインプリミティブ
```java
public record Age(Integer value) {
    public Age { //コンパクトコンストラクタで生成前にチェック
        if(Objects.isNull(value)) throw new IllegalArgumentException("値がNullです");
        if(value < 0 || value > 150) { // 0から150歳まで
            throw new IllegalArgumentException("値が不正です");
        }
    }
}
```
上記のように、ドメインプリミティブではNullのチェックや年齢として扱える数値の範囲を表現しています。
これにより、ドメインプリミティブでAgeというクラスが生成された際に、0から150の値が入っていることが保証されます。
そのため、呼び出し元でNullのチェックや、代入されている値の妥当性を確認する必要がなくなるのです。
セキュアなドメインを生成することで、コードの不正確さや曖昧さ、エラーの起こりやすさなどを防ぐことができます。

# さいごに
今回はドメインプリミティブについて簡単にですがまとめました。
システムを構築する上で、たくさんのドメインを取り扱うと思います。
その１つ１つをしっかり定義づけして、ドメインプリミティブに落としていくことでとてもセキュアでわかりやすいシステムになると思いました。
ドメインプリミティブはセキュア・バイ・デザインのプラクティスの中でも非常に取り入れやすいので、取っ掛かりとして良いと思います。
また、この本はこれ以外にも非常に勉強になることが書いてあるので、まだの方はぜひ読んでみてください。
【Spring】Entityから別のmodelに値をセットする

# はじめに

業務でJavaを使うことになったが、少しブランクがあるため、記事としてまとめる。


# 背景

Entityでセットされた値を、modelに格納したいと考えた。しかし、modelのsetterを呼び出し、setterの引数にEntityのgetterを呼び出すのは、フィールドの数が多い場合、可読性に欠けると考え、わざわざ上記のようなことをしなくてもmodelに格納することが出来ないかと思った経由から始まった。

# 実装

ちなみに、lombokを使用しているため、setterとgetterは記載していない。


### DBから取得・格納済みのEntityクラス

```java:Entity.java
@Getter
@Setter
@Entity
public class Entity {
  private String entityStringA;
  private String entityStringB;
  private String entityDateTime;
}
```


### Entityクラスからセットするmodelクラス

```java:Model.java
@Getter
@Setter
@Entity
public class Model {
  private String modelStringA;
  private String modelStringB;
  private DateTime modelDateTime;
}
```


```java:変換する処理
int id = 1;
Entity entity = xxx.fetchXxx(id);

// entityクラスに格納された値がmodelにセットされる
Model model = modelMapper.map(entity, Model.class);
// entityDateTimeからmodelDateTimeを格納しているが、型がStringからDateTime
// にしているが、String⇒DateTimeと型変換をしてくれる
```

EntityとModelのフィールド名が同じ場合は以下クラスは不要だが、今回のようにフィールド名を変更したい場合、以下設定クラスを作成する必要がある。


```java:app/comfig/ModelMapperConfig
@Configuration
public class ModelMapperConfig {

  @Bean
  public ModelMapper modelMapper() {
    ModelMapper modelMapper = new ModelMapper();
    modelMapper.getConfiguration().setAmbiguityIgnored(true);
    convert(modelMapper);
    return modelMapper;
  }

  private void convert(ModelMapper modelMapper) {
    TypeMap<RecruitmentDetail, JobPosting> mapperTypeMap = 
      modelMapper.createTypeMap(Entity.class, Model.class);

    // mapper.mapの第一引数に取得対象の値のgetterを指定
    // mapper.mapの第二引数にセット対象の値のsetterを指定
    mapperTypeMap.addMappings(mapper -> {
      mapper.map(Entity::getEntityStringA, Model::setModelStringA);
      mapper.map(Entity::getEntityStringB, Model::setModelStringB);
      mapper.map(Entity::getEntityDateTime, Model::setModelDateTime);
    });
  }
}
```
Amazon Primeで倍速再生を行えるようになる拡張機能[Video Speed Controller]



Amazon Primeでは倍速再生が初期から搭載されていません。
倍速機能を追加するために、Chrome拡張機能を追加することで倍速再生を可能にする方法を紹介します。

# Video Speed Controller

下記のURLから拡張機能をインストールします。

https://chrome.google.com/webstore/detail/video-speed-controller/nffaoalbilbmmfgbnbgppjihopabppdk/related?hl=en


適切にインストールが完了すると、Amazon Primeの動画を再生すると左上に下図のような数字が表示されるようになります。これは現在の再生速度になります。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/593429/b2921219-6360-816a-f28e-c1bab3f63881.png)

操作方法は設定より変更することができます。初期設定では下図のようになっております、この場合だと

[Shift + d]でスピードアップ
[Shift + S]でスピードダウン

になっております。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/593429/fdd310e7-3db2-6023-d082-6e9befe01b63.png)
【UiPath】エラー発生時のスクリーンショットをOrchestratorで管理したい

# 検討の背景　トラブルシューティング
RPAは必ずしも開発環境と実行環境が一致しないため、開発環境では動いたが実行環境では動かないというケースが発生し得る。その際にトラブルシューティングとしてエラーログやスクリーンショットは有効である。
ログはOrchestratorに集中管理されるが、スクリーンショットは端末に保存されてしまうことが多い。できればスクリーンショットもOrchestratorで管理したい。
※OrchestratorにUR向けのRecording機能はあるが、今回はARも想定したやり方を紹介

## スクリーンショットを取得するアクティビティ
スクリーンショットの取得は「スクリーンショットを取得」を利用する。全画面をスクリーンショットとりたいため、セレクターの設定は行わない。つまり、**デフォルトのままでOK**。今回はファイル名をデフォルトの `"screenshot.png"` のままとする。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2707463/ae008070-df8a-34e4-2e77-76b6a6eba98d.png)

## Orchestratorのストレージバケットを有効化
Orchestratorのストレージバケットに任意のストレージバケットを追加する。Assetと同様にフォルダ単位に設定が必要である。
今回は `sampleBuckets`　という名前にした。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2707463/7fa053f8-0d6b-3034-34b2-226797ea1a6a.png)

ただしく追加されると以下のように表示される。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2707463/711c5ce1-9998-2014-d074-5279b86ccf06.png)

## WFからストレージバケットをにアップロード
「ストレージ ファイルをアップロード」というアクティビティを使用する。
ストレージバケット名は`sampleBuckets`
ターゲットはアップロードするファイル名
パスはアップロード元のファイル名
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2707463/f48e2e1a-a1bf-9077-5ad3-a1c7a2c1f960.png)

## エラー処理の全体像
エラー処理として個別のWFにこれらを利用してもよいが、今回はGlobal Exception Handlerでエラーハンドリングを実施する。
ストレージバケットにさえファイルをアップロードできれば問題ないが、今回はJob情報を含めた実装とした。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2707463/356dcbc8-8d9b-7013-cda4-5f588dd270ac.png)

## 実行結果
エラーが発生するとこのようにプロセス名と発生日付のスクリーンショットがアップロードされている
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2707463/77ea91d4-41d6-f027-970b-974cda369f74.png)
WSLで作成したgitリポジトリをWin側で開くと生じる所有権問題の解決

# 概要
- Windows11でWSL2上で開発する際、VScodeはWin11側で起動している。
- このとき、WSL2側でgit initやgit cloneしたgitリポジトリをWin11側で起動したVScodeで開いてもVScodeのgit機能で管理できなかった。
- powershellでgit logすると「リポジトリの所有権が疑わしい」とのこと。
- メッセージに従い、git configでsafe.directoryに当該パスを追加し、解決した。

# 前提
- OS: Windows 11 Pro(バージョン:21H2/OSビルド22000.856)
- 仮想マシンOS: CentOS Stream release 8
- 仮想マシン動作環境: WSL2
- 前提ソフトウェア: VScode, Windows Terminal

# 背景
- WSL2のお陰でWindowsマシンのみでも手軽にLinux環境向けの開発もできるようになった。
- また、開発物はgitで管理したい。
- さらに、上記環境でVScodeで開発したい場合、WSL2上のファイル、リポジトリをWin側のVScodeで操作、編集することになる。
- WSL2上の所有権と、Win上での所有権の扱いの違いにより今回の問題が起きたと考えている。

# 現象
- WSL2上のCentOSで作成したgitリポジトリをWin側のVScodeで操作しようとした。
- Win側のVScodeは.bashrcなどでパスを通しておくことにより、WSL2のターミナルで`code .`とするだけで現在地を開ける。
    - 参考:[WSL2からのVScodeの開く際の設定](https://qiita.com/ezmscrap/items/6ccae69f1ab8795b4b4e)
![2022-08-14_0001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/208295/86fdbf4b-9fa9-9c1a-3dc1-427343fa523b.png)
- しかし、開いたVScodeでのソース管理をひらくと、WSL2側で作ったはずのgitリポジトリを認識していない。
![2022-08-14_0006.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/208295/a709fbda-b4a6-4b1d-8419-443552984d6f.png)
- これだと、Win側のVScodeでソース管理機能が使えないので困る。

# 解決方法
- VScodeのエクスプローラーでファイルを選択し、「エクスプローラーで表示する」でエクスプローラーをひらく。
- 開いたエクスプローラーで右クリックし、ターミナルで開く。
- 開いたターミナルで`git log`を実行すると以下のエラーが出る。
```
fatal: detected dubious ownership in repository at '<当該のパス>'
```
- 具体的には以下の通り。
![2022-08-14_0007.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/208295/a722605a-3b08-6ee2-7bcd-a49e6f524db1.png)
- 指示通り、git configでsafe.directoryに当該パスを追加。
```
 git config --global --add safe.directory '%(prefix)/<当該のパス>'
```
- VScodeを一度閉じ、WSL2側から開きなおすと無事に操作できるようになった
![2022-08-14_0005.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/208295/94d24eb4-ab4b-e91d-98d5-97055a798b2c.png)

# おまけ
- 少し、参考サイトも読みました。
    - 参考サイト:[git-config - Get and set repository or global options](https://git-scm.com/docs/git-config)
- safe.directoryを削除したい場合は空値を追加
- %(prefix)はホーム ディレクトリからの相対パスの意味

# おまけのおまけ
- 設定は設定されたディレクトリにのみ適用され、再帰的に配下のディレクトリに適用されることはない
- 楽できないかと、/tmpをsafe.directoryに設定したら、/tmp/sandbox1もtmp/sandbox2も許可されるかと思ったがそんなことは無かったぜ!

# おわり
- WSL2とVScodeとgitがあると幸せに開発が楽しめています。
- Enjoy WSL2!
HerokuのPlay製デモアプリを2022年度版にアップグレードする

Heroku は無料で実行コンテナとデータベースがついてくるクラウドサービスです。言語別のサンプルデモアプリもあり、Scala+PlayFrameworkもあるのですが、バージョンが古いのでアップグレードしてみましょう。

# アップデート手順

## 巨人の肩に乗る

[元のGitHub](https://github.com/heroku/scala-getting-started)の[フォーク一覧](https://github.com/heroku/scala-getting-started/network)を調べると、[play-2.8を試行している](https://github.com/fabjan/scala-getting-started/tree/play-2.8)人がいるので、これを足場として作業しましょう。

## 各種ライブラリをバージョンアップする

| ライブラリ | 変更前 | 変更後 | 備考 |
|:-:|:-:|:-:|:-|
| Java | 1.8 | 11.0.16 | `system.properties` に設定 |
| Scala | 2.11.7 | 2.13.8 | `build.sbt` に設定 |
| sbt | 0.13.11 | 1.7.1 | `project/build.properties` に設定 |
| Play | 2.4.3 | 2.8.16 | `project/plugin.sbt` に設定 |

## アプリケーションシークレットの追加

https://www.playframework.com/documentation/latest/ApplicationSecret

```hocon:application.conf
application.secret="change-me"
```

↓

```hocon:application.conf
play.http.secret.key=${?APPLICATION_SECRET}
```

ローカル開発では適当なシークレットを追加しておきましょう。

```shell:.env
APPLICATION_SECRET=QCY?tAnfk?aZ?iwrNwnxIlR6CTf:G3gf:90Latabg@5241AB`R5W:1uDFN];Ik@n
```

本番用には、まずシークレットを生成して

```console
$ sbt playGenerateSecret
...
Generated new secret: <<生成されたシークレット>>
...
```

構成変数に設定しておきましょう。

```console
$ heroku config:set 'APPLICATION_SECRET=<<生成されたシークレット>>'
```

## 許可ホストを設定する

https://qiita.com/s-katsumata/items/6e16dbadbf4d5516010f

```hocon:application.conf
play.filters.hosts.allowed = ["."]
```

## プロパティ名の変更に対応する

https://www.playframework.com/documentation/latest/Migration24#Configuration-changes

```hocon:application.conf
application.langs="en"
```

↓

```hocon:application.conf
play.i18n.langs=["en"]
```

## ローカルデータベースに postgresql を使う

足場に使ったコミットでは h2 を使うようになっていますが、本番と構成が同じ方が接続情報などを流用できるため、postgresql を使うようにしてみましょう。

```bash:.envrc
export POSTGRES_PASSWORD=password
export DATABASE_URL=postgres://postgres:${POSTGRES_PASSWORD}@localhost:5432/postgres
```

```docker-compose.yml
version: "3"

services:
  db:
    image: postgres:14
    environment:
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-password}
    ports:
      - "5432:5432"
    restart: always
```

```hocon:application.conf
db.default.driver=org.postgresql.Driver
db.default.url=${?DATABASE_URL}
```

```build.sbt
libraryDependencies ++= Seq(
  jdbc,
  caffeine,
  ws,
  guice,
  "org.postgresql" % "postgresql" % "42.3.6"
)
```

ローカル開発する際はdbを立ち上げておきましょう

```console
$ docker compose up
```

## resolvers の削除

https://www.scala-sbt.org/1.x/docs/Resolvers.html

resolvers に設定されている Typesafe repository はデフォルトで入っているので不要です。

## Procfile の修正

console が起動しなくなっているので、削除しておきます。

## heroku stack のアップグレード

https://devcenter.heroku.com/articles/upgrading-to-the-latest-stack

デフォルトだと 20 で作成されるので、 22 にアップグレードします。

```console
$ heroku stack:set heroku-22
```

# デプロイ

ここまでできたらデプロイしておきます。お疲れ様でした。

```console
$ git push heroku master
$ heroku open
```

# まとめ

heroku でも最新の play が動くことが確認できました。良いですね。
【Java】ifを使わずListの値からEnumで定義した値のみを表示する

# はじめに

業務でJavaを使うことになったが、少しブランクがあるため、記事としてまとめる。

# 環境
Java：11（8でも問題ない）

# 実装

仕様は以下の通り

* aaa、iii、uuu以外は表示させない
* 表示優先順位は aaa > iii > uuu
* 重複して表示させない

```java:呼び出し元
List<String> tests1 = new ArrayList<>();
tests1.add("aaa"); // 1番目に表示したい
tests1.add("aaa"); // 重複するので除外したい
tests1.add("uuu"); // 3番目に表示したい
tests1.add("ooo"); // 除外したい
tests1.add("eee"); // 除外したい
tests1.add("iii"); // 2番目に表示したい
tests1.add(null);  // 除外したい
tests1.add("");    // 除外したい

List<String> tests2 = TestType.getXXX(tests1);
tests2.stream().forEach(System.out::print);

// 出力結果
// aaaiiiuuu
```

```java:呼び出し先のEnumクラス
@Getter
public enum TestType {

    A(1, "aaa"),
    I(2, "iii"),
    U(3, "uuu"),

    private int priority;

    private String matchString;

    private TestType(int priority, String matchString) {
        this.priority = priority;
        this.matchString = matchString;
    }

    public static List<String> getXXX (List<String> tests) {
        // 対象以外を除外して、マッチしたEnumの値をList化する
        List<TestType> targetEnums = new ArrayList<>();
        for (String test : tests) {
            TestType target = Arrays.stream(TestType.values())
                .filter(x -> x.getMatchString().equals(test))            
                .findFirst()
                .orElse(null);
            targetEnums.add(target);
        }

        // 優先度順に並び替え＆重複を除外してList化
        return  targetEnums.stream()
          .filter(x -> !Objects.isNull(x)) // nullを除外
          .sorted(Comparator.comparingInt(TestType::getPriority))
          .map(x -> x.getMatchString())
          .distinct()
          .collect(Collectors.toList());
}
```
Cousera + DeepL（無料/有料） + VLCで英語教材での学習速度を爆速にする


### この記事を読んでできるようになること

Couseraで公開されている日本語字幕のないコースを日本語で履修できるようになります。

**※DeepLPROを利用する場合最低月額750円かかります。
スムーズな翻訳には有料のPROがおすすめです。**

![スクリーンショット 2022-08-14 18.42.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2636207/bf42969a-f36a-56b4-adea-c805bcc8445c.png)

画像はBuild a Modern Computer from First Principles: 
From Nand to Tetris (Project-Centered Course)より

#### Couseraとは？

""コーセラ（英名：Coursera）は、スタンフォード大学コンピュータサイエンス教授Andrew NgとDaphne Kollerによって創立された教育技術の営利団体である。世界中の多くの大学と協力し、それらの大学のコースのいくつかを無償でオンライン上に提供している。""

簡単に言えばめちゃくちゃクォリティの高い授業を無料で受けられる場所です。
お金を払えばスタンフォード大学の修了証が発行されたりとニヤニヤできる特典もあります。

#### Couseraでありがちなこと

![スクリーンショット 2022-08-14 18.44.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2636207/f17f3f8b-7232-aef9-8734-d2afc1ce24d6.png)

・翻訳してもミニプレーヤーになってしまって見にくい
・Google翻訳が微妙でよくわからない
・動画と一緒に字幕が表示された方が絶対にわかりやすいのに・・・
・しょうがないのでデュアルモニタで翻訳文と一緒に動画を再生している


#### VLC+DeepLPROの組み合わせで解決する

Couseraの字幕ファイルは「vtt」という形式で管理されています。

下記のサンプルを見たらわかるように、何分何秒まではこの字幕だよ！と書いているだけです。
時間の下が字幕本文というわけです。

サンプル

``` vtt
1
00:00:02.716 --> 00:00:04.516
This is a pen！

2
00:00:04.516 --> 00:00:10.660
English is difficult.

3
00:00:10.660 --> 00:00:15.090
I want to translate.

4
00:00:15.090 --> 00:00:18.430
Yeah!

5
00:00:18.430 --> 00:00:22.890
This is cool!
```

Couseraでは動画の他にこの「vtt」ファイルもダウンロードできるので、vttファイルをDeepLで翻訳してやろうというわけです。

また、VLCプレイヤーという動画再生プレイヤーはデフォルトでvttファイルに対応しています。
動画を再生しながらvttファイルをドラッグ&ドロップしてあげるだけで、日本語字幕付き動画として再生できるのです。

#### 翻訳クォリティについて

素の状態ではB-くらいの印象です。

これはCouseraで公開されている文章が、DeepLにとって読みにくい状態だからです。

例えば、This is a pen or cat. It's so cool.という文章があった時、字幕ではこうなっていることがあります。

``` vtt
00:00:15.090 --> 00:00:18.430
This is a pen

5
00:00:18.430 --> 00:00:22.890
or cat. It's

6
00:00:22.890 --> 00:00:22.999
so cool

```

こうなると「これはペンです」「もしくは猫。それはそうです」「かっこいい」のような字幕が表示されてしまうわけです。

とはいってもないよりはマシです。

#### 翻訳クォリティアップの手段2選

**①シェルスクリプトで「.」で区切ることを前提に字幕文章を組み立て直し、DeepLにかける。**
文章の始まりの時間と「.」があった時間を記録しておく。
該当する場所にスクリプトで字幕を挿入する。

こんな感じの実装が1番スムーズそう。

調べてみると実際に似たようなことを他の方も行っていました。
https://qiita.com/kitakitash/items/df808c010639ea1ff0f1

**②txt形式のファイルをダウンロードし、補助教材として使う。**

txt形式でも字幕文は公開されています。

こちらの方が「.」の位置が正しい位置にあることが多く、翻訳がより自然でした。

わかりにくい部分をtxt形式の翻訳で見返すというのが一番簡単ですが、少しめんどくささが残ります。


#### 感想

有名大学の授業を月額750円で学習可能になるDeepLつよい。
PHP 一定文字列以上は…に省略するコード

# 概要

- 一定文字数以上は`文字列…`のように文字列を変換するコードをメモ的に記載する。

# コード

- 下記のように記載することで`$limit`に入れた文字列をオーバーした場合`…`に省略する事ができる。

    ```php
    <?php
    
    $limit = 文字数制限;
    $str = '判定したい文字列';
    
    if (mb_strlen($str) >= $limit) {
        $str = mb_substr($str, 0, $limit) . '…';
    }
    
    echo $str;
    ```

- 例えば下記のように記載すると「ありが…」と出力される。

    ```php
    <?php
    
    $limit = 3;
    $str = 'ありがとう';
    
    if (mb_strlen($str) >= $limit) {
        $str = mb_substr($str, 0, $limit) . '…';
    }
    
    echo $str;
    ```

# 追記（2022/08/17）

- @rana_kualu 様よりコメントいただき、もっと簡単に省略する方法をお教えいただきました。。！本当にありがとうございます！
- 省略対象が確実に全角文字の場合非常にスマートに書けそうでした！

    ```php
    echo mb_strimwidth('ありがとう', 0, 8, '…');
    ```

- 記載例

    ```php
    mb_strimwidth(末尾を丸める文字, 開始位置, 丸める幅（半角文字なら1、全角文字なら2でカウント）, 丸めた後の文字列の最後に追加される文字列);
    ```


# 参考文献

- [https://www.php.net/manual/ja/function.mb-strlen.php](https://www.php.net/manual/ja/function.mb-strlen.php)
- [https://www.php.net/manual/ja/function.mb-substr.php](https://www.php.net/manual/ja/function.mb-substr.php)
- [https://www.php.net/manual/ja/function.mb-strimwidth.php](https://www.php.net/manual/ja/function.mb-strimwidth.php)