0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【PowerShell 業務自動化レシピ #6】2つのリストを突合して差分を出す(Compare-Objectで増えた人・減った人・変わった項目を検出)

0
Posted at

株式会社Good Labでエンジニアをしている コータロー です。
日々、Java・SQL・Gitなどの技術情報や、新人エンジニア向けの学習ノウハウ、
AI活用についての情報を発信しています。

Good Labについて気になった方は、コーポレートサイトもぜひご覧ください。
コーポレートサイト


本記事は 「PowerShell 業務自動化レシピ」 シリーズの第6回です。このシリーズは、文法解説ではなく 「1記事=1つの業務課題」を、コピペで動く検証済みスクリプトで解決することを目的とした実務レシピ集です(全12回想定)。SES・受託・社内運用の現場で、いまだに手作業でやっている地味な作業を片っ端から自動化していきます。

第1回 では「大量ファイルの一括リネーム」、第2回 では「古いログファイルの抽出・自動削除」、第3回 では「フォルダ構成・ファイル一覧をExcel台帳化」、第4回 では「複数CSVを1枚に結合して集計」、第5回 では「Excelを開かずにセル値を読み書き」を扱いました。第4回・第5回で データを集めて集計する流れを固めたので、今回はその次の頻出業務、データ同士を突き合わせて「差分」を出すに進みます。

第6回のテーマは 「2つのリストを突合して差分を出す」Compare-Object を使い、先月と今月の社員名簿・在庫マスタ・権限一覧などを比較して、**「増えた行・減った行・値が変わった行」**を自動で検出します。Excelで両方を並べて1行ずつ目視照合していた作業を、コマンド一本で・取りこぼしなく終わらせます。

本記事は PowerShell 7(pwsh、クロスプラットフォーム) を基準にしています。Windows標準の Windows PowerShell 5.1 での差異は、各所と最後の章で明記します。Compare-Object の中核機能はバージョン差がほとんどなく、5.1でもほぼそのまま動きます。

今回も 読み取り+CSV出力のみ で、元のCSVを書き換えたり消したりはしません(破壊的操作なし)。安心して試せます。出力先CSVの上書きにだけ軽く注意します(第4回と同じ整理です)。


Before:手作業のつらいシーン

シーン1:先月と今月の名簿を並べて1行ずつ目視照合

「先月の社員名簿と今月の名簿を比べて、入社した人・退社した人・部署が変わった人を出して」。手元には members_202605.csv(先月)と members_202606.csv(今月)の2枚。やることは決まっていて、Excelで両方を開いて並べ、IDでソートして、上から1行ずつ「この人いる?この人は?」と指でなぞって突合する。

  • 数百行あると 目が滑る。見落とすと「退社済みの人にアカウントが残る」「異動が反映されない」など実害に直結
  • 「増えた/減った」だけでなく「同じ人だけど部署や権限が変わった」を拾うのが特に難しい。両方のシートを横に並べてVLOOKUPで突き合わせて…と関数を組むうちに半日が溶ける
  • 翌月も翌々月も同じ作業。毎月発生する突合ほど自動化の費用対効果が高い

シーン2:「差分」を出したはずが、何が違うのか分からない

頑張って差分行を抽出しても、「どっちのリストにしかない行なのか」「どの列の値が変わったのか」が一目で分からない。結局「先月版」「今月版」をもう一度見比べることになり、二度手間になる。

これ、2枚のCSVを読み込んで Compare-Object でキー(社員ID)ごとに突き合わせ、「追加(入社)/削除(退社)/変更(異動・権限変更)」に分類し、変更については旧値→新値まで添えてレポートCSVに出す——ここまでスクリプト一本で終わります。


After:スクリプト一本で完了

進め方は ①2枚のCSVの単純な差分(どちらかにしか無い行)を出す → ②キー列で突合して「追加/削除」に分類する → ③同じキーで値が変わった行(変更)を旧値→新値つきで抽出する → ④追加・削除・変更を1枚のレポートCSVにまとめる の順です。

この記事では、社員名簿が C:\hr(macOS/Linuxなら ~/hr)に2枚ある前提で進めます。中身はこんな形です。

members_202605.csv(先月)

id,name,dept
1001,佐藤,営業
1002,鈴木,開発
1003,高橋,総務
1004,田中,開発

members_202606.csv(今月)

id,name,dept
1001,佐藤,営業
1002,鈴木,人事
1003,高橋,総務
1005,渡辺,開発

先月と今月で何が起きたかを先に言語化しておくと、ID 1004(田中)が消え(退社)ID 1005(渡辺)が増え(入社)ID 1002(鈴木)は残っているが dept が「開発」→「人事」に変わった(異動)、という3種類の変化が混在しています。これを取りこぼさず分類するのがゴールです。

レシピ1:2枚のCSVの「単純な差分」を出す(どちらかにしか無い行)

まずは一番シンプルな使い方です。Compare-Object に2つのリストを渡すと、片方にしか存在しない要素を教えてくれます。

# ===== 設定 =====
$oldCsv = "C:\hr\members_202605.csv"   # 先月(基準=Reference)
$newCsv = "C:\hr\members_202606.csv"   # 今月(比較対象=Difference)

# ===== 本体 =====
$old = Import-Csv -LiteralPath $oldCsv
$new = Import-Csv -LiteralPath $newCsv

# id 列を基準に差分を取る
Compare-Object -ReferenceObject $old -DifferenceObject $new -Property id |
    Sort-Object id |
    Format-Table -AutoSize

出力イメージ:

id   SideIndicator
--   -------------
1004 <=
1005 =>

SideIndicator 列がこの結果の読み方の核心です。

  • <=-ReferenceObject(先月)にしか無い → 今月消えた = 退社(ID 1004 田中)
  • =>-DifferenceObject(今月)にしか無い → 先月に無かった = 入社(ID 1005 渡辺)

つまり「<= が減った行・=> が増えた行」です。両方に存在する行(1001・1003、および id だけ見れば 1002 も)は、既定では結果に出てきません(一致した行は黙って除外される)。

⚠️ ここが最初の落とし穴です。-Property id付けないとCompare-Object は CSV の行オブジェクト同士を参照(オブジェクトの同一性)で比較しようとし、見た目が同じ行でも「全部違う」と判定されて差分が大量に出ます。何のキーで比較するのかを -Property で必ず明示するのが鉄則です(詳細は後述の要点解説)。

レシピ2:キー列で突合して「追加/削除」に分類する

レシピ1の SideIndicator を読み替えれば、そのまま「入社/退社リスト」になります。-PassThru を付けると、マッチした側の元の行オブジェクトSideIndicator 列が付いて返ってくるので、id だけでなく name・dept も一緒に出せます。

# ===== 設定 =====
$oldCsv = "C:\hr\members_202605.csv"
$newCsv = "C:\hr\members_202606.csv"

# ===== 本体 =====
$old = Import-Csv -LiteralPath $oldCsv
$new = Import-Csv -LiteralPath $newCsv

# id をキーに突合。-PassThru で元の行(name/dept 付き)を返す
$diff = Compare-Object -ReferenceObject $old -DifferenceObject $new -Property id -PassThru

# --- 退社(先月にしか無い = <=)---
Write-Host "===== 退社(先月のみ) ====="
$diff |
    Where-Object SideIndicator -eq '<=' |
    Select-Object id, name, dept |
    Format-Table -AutoSize

# --- 入社(今月にしか無い = =>)---
Write-Host "===== 入社(今月のみ) ====="
$diff |
    Where-Object SideIndicator -eq '=>' |
    Select-Object id, name, dept |
    Format-Table -AutoSize

出力イメージ:

===== 退社(先月のみ) =====
id   name dept
--   ---- ----
1004 田中 開発

===== 入社(今月のみ) =====
id   name dept
--   ---- ----
1005 渡辺 開発

退社(<=)の行は $old 由来なので「先月時点の所属(田中・開発)」が、入社(=>)の行は $new 由来なので「今月時点の所属(渡辺・開発)」が出ます。-PassThru は「マッチした方の元オブジェクトをそのまま返す」ので、namedept が欠けずに付いてくるわけです。

この段階では 「鈴木が開発→人事に異動した」ことは検出できません。id 1002 は先月・今月の両方に存在するため、Compare-Object -Property id から見れば「一致=差分なし」となり、結果に出てこないからです。「両方にいるけど中身が変わった人」を拾うのが、次のレシピ3です。

レシピ3:同じキーで「値が変わった行(変更)」を抽出する

異動・権限変更のような「キーは同じだが特定の列の値が変わった」変更を検出します。考え方はシンプルで、-Property に比較したい列をすべて渡すだけです。iddept の両方を比較対象にすると、「id は同じでも dept が違う」行が差分として浮かび上がります。

# ===== 設定 =====
$oldCsv = "C:\hr\members_202605.csv"
$newCsv = "C:\hr\members_202606.csv"

# ===== 本体 =====
$old = Import-Csv -LiteralPath $oldCsv
$new = Import-Csv -LiteralPath $newCsv

# id と dept の両方で比較し、両側に出るものだけ(=変更)を拾う
$changed =
    Compare-Object -ReferenceObject $old -DifferenceObject $new -Property id, dept -PassThru |
    Group-Object id |
    Where-Object Count -eq 2   # 同じ id が <= と => の2件 → dept が変わった人

# 旧値→新値の形に整える
$changed | ForEach-Object {
    $oldRow = $_.Group | Where-Object SideIndicator -eq '<='
    $newRow = $_.Group | Where-Object SideIndicator -eq '=>'
    [pscustomobject]@{
        id      = $_.Name
        name    = $newRow.name
        OldDept = $oldRow.dept
        NewDept = $newRow.dept
    }
} | Format-Table -AutoSize

出力イメージ:

id   name OldDept NewDept
--   ---- ------- -------
1002 鈴木 開発    人事

仕組みを順に追います。-Property id, dept で比較すると、ID 1002 だけが「id は同じだが dept が違う」ため、<=(先月:開発)と =>(今月:人事)の2行として返ってきます。一方、退社の 1004 と入社の 1005 も差分として出ますが、これらは <==>どちらか1行だけです。そこで Group-Object id でまとめ、1つの id が2件ある=両側に出た=変更だけを Where-Object Count -eq 2 で拾えば、純粋な「変更行」だけが残ります(追加・削除は1件なので除外される)。

あとは各 id の <= 行と => 行を取り出し、OldDeptNewDept として並べれば、旧値→新値が一目で分かるレポートになります。

検算:先月・今月で id, dept の組が一致しているのは 1001(佐藤・営業)と 1003(高橋・総務)の2人で、これらは差分に出ません。差分に出るのは 1002(dept が開発→人事で2件)・1004(退社で1件)・1005(入社で1件)。Count -eq 2 を満たすのは 1002 だけなので、変更検出は1件で正しく一致します。

レシピ4:追加・削除・変更を1枚のレポートCSVにまとめる

最後に、ここまでの「追加(入社)/削除(退社)/変更(異動)」を1枚のレポートCSVに統合します。各行に ChangeType(変化の種類)列を付けて、何が起きたか一覧で読めるようにします。

# ===== 設定 =====
$oldCsv  = "C:\hr\members_202605.csv"
$newCsv  = "C:\hr\members_202606.csv"
$outCsv  = "C:\hr\diff_report.csv"

# ===== 本体 =====
$old = Import-Csv -LiteralPath $oldCsv
$new = Import-Csv -LiteralPath $newCsv

$report = New-Object System.Collections.Generic.List[object]

# --- 追加/削除(id だけで突合)---
Compare-Object -ReferenceObject $old -DifferenceObject $new -Property id -PassThru |
    ForEach-Object {
        $type = if ($_.SideIndicator -eq '=>') { '追加(入社)' } else { '削除(退社)' }
        $report.Add([pscustomobject]@{
            ChangeType = $type
            id         = $_.id
            name       = $_.name
            OldDept    = if ($_.SideIndicator -eq '<=') { $_.dept } else { '' }
            NewDept    = if ($_.SideIndicator -eq '=>') { $_.dept } else { '' }
        })
    }

# --- 変更(id, dept で突合し、両側に出たものだけ)---
Compare-Object -ReferenceObject $old -DifferenceObject $new -Property id, dept -PassThru |
    Group-Object id |
    Where-Object Count -eq 2 |
    ForEach-Object {
        $oldRow = $_.Group | Where-Object SideIndicator -eq '<='
        $newRow = $_.Group | Where-Object SideIndicator -eq '=>'
        $report.Add([pscustomobject]@{
            ChangeType = '変更(異動)'
            id         = $_.Name
            name       = $newRow.name
            OldDept    = $oldRow.dept
            NewDept    = $newRow.dept
        })
    }

# レポートCSVに出力(id 順)
$report |
    Sort-Object id |
    Export-Csv -LiteralPath $outCsv -Encoding UTF8BOM -NoTypeInformation

Write-Host "差分レポート出力: $outCsv$($report.Count) 件)"
$report | Sort-Object id | Format-Table -AutoSize

出力された diff_report.csv の中身:

ChangeType,id,name,OldDept,NewDept
変更(異動),1002,鈴木,開発,人事
削除(退社),1004,田中,開発,
追加(入社),1005,渡辺,,開発

画面側のテーブル表示:

ChangeType id   name OldDept NewDept
---------- --   ---- ------- -------
変更(異動) 1002 鈴木 開発    人事
削除(退社) 1004 田中 開発
追加(入社) 1005 渡辺         開発
差分レポート出力: C:\hr\diff_report.csv(3 件)

検算:このレポートは「変更1件(1002)+削除1件(1004)+追加1件(1005)」の計3件。先月4人・今月4人で、共通して変化なしなのは 1001・1003 の2人。残る変化分が3件で、先述の整理(入社1・退社1・異動1)とぴったり一致します。OldDept が空なら入社(今月から)、NewDept が空なら退社(先月まで)、両方埋まっていれば異動、と列を見るだけで判別できます。

これで「毎月、名簿を目視照合」していた作業が、コマンド一本・数秒・取りこぼしなしで再現できます。比較対象を在庫マスタ(商品コード+在庫数)や権限一覧(ユーザーID+ロール)に差し替えれば、そのまま流用できます。


コードの要点解説(なぜこの書き方か)

文法そのものではなく、現場でハマらないための選択を中心に解説します。

1. 【最重要】-Property を付けないと「参照比較」になって全件差分が出る

これが Compare-Object 最大の落とし穴です。Import-Csv が返すのは 行ごとの PSCustomObject-Property指定しないと、Compare-Object はこのオブジェクト同士をそのまま(参照の同一性ベースで)比較しようとします。先月と今月で読み込んだオブジェクトは別インスタンスなので、中身が同じ行でも「全部違う」と判定され、差分が大量に噴き出します。

# ❌ 危険:-Property が無いと行オブジェクトを参照比較し、ほぼ全行が差分扱いになる
Compare-Object $old $new

# ✅ 正解:比較に使う列(キー/値)を必ず明示する
Compare-Object $old $new -Property id            # id だけで存在比較(追加・削除)
Compare-Object $old $new -Property id, dept      # id と dept で値比較(変更も拾う)

「何の列で比較するのか」を -Property で明示する——これさえ守れば、Compare-Object は期待どおりに動きます。「差分が異常に多い」ときは、まずここを疑ってください。

2. SideIndicator<= => == の意味

差分の読み方は SideIndicator 列に集約されます。

記号 意味 今回の文脈
<= -ReferenceObject(先月)にのみ存在 退社(消えた行)
=> -DifferenceObject(今月)にのみ存在 入社(増えた行)
== 両方に存在(-IncludeEqual 指定時のみ表示) 変化なし/一致

矢印は「どちら側に寄っているか」を表します。<= は左(Reference=先に書いた方)、=> は右(Difference=後に書いた方)です。どちらを Reference にするかで意味が逆転するので、「-ReferenceObject =基準=先月」と決めて固定するのが混乱しないコツです。

3. -IncludeEqual-ExcludeDifferent で「一致した行」も扱える

既定では Compare-Object差分(違う行)だけを返し、一致した行は捨てます。一致した行も欲しいときは -IncludeEqual を付けると == 付きで返ります。逆に「一致した行だけ」が欲しいなら -IncludeEqual -ExcludeDifferent を組み合わせます(差分を除外し、一致のみ残す)。

# 両方に存在する(=変化なしの)id だけを取り出す
Compare-Object $old $new -Property id -IncludeEqual -ExcludeDifferent |
    Select-Object id
# → 1001, 1003(id だけ見れば 1002 も両方にあるので含まれる)

補足:-Property id だけで -IncludeEqual した場合、1002 も「id は両方にある」ので == に含まれます。「値も含めて完全一致」を一致と呼びたいなら、レシピ3同様に比較列を増やしてください。

4. 「変更」は『両側に出た同一キー』として表現する(レシピ3の肝)

Compare-Object 自体には「変更(update)」という分類はありません。出てくるのは <=(消えた)と =>(増えた)だけです。そこで**「キーは同じだが値が違う行」は、<==> が同じ id で対になって出る**という性質を使います。Group-Object id でまとめて Count -eq 2(=両側に出た) を「変更」とみなすのがレシピ3のロジックです。Count -eq 1 は片側だけ=純粋な追加か削除、というふるい分けになります。

5. -PassThru で「元の行(全列)」を取り戻す

-Property id を付けると、-PassThru なしの既定では結果に idSideIndicator しか出ません(比較に使った列だけ)。name や dept も一緒に欲しいときは -PassThru を付けます。これは「比較はキー列でするが、返すのはマッチした側の元オブジェクトそのもの」という挙動で、<= なら $old の行、=> なら $new の行が、全列付きで返ります。レシピ2〜4でレポートに name・dept を載せられているのはこのためです。

6. 値はすべて文字列。比較は問題ないが、数値で大小を見るときは注意(第4回と整合)

第4回で触れたとおり、Import-Csv が返す各列の値は 見た目が数字でもすべて文字列です。Compare-Object等値比較(一致/不一致の判定)には文字列のままで支障ありません("開発" と "人事" の比較、"1002" と "1002" の比較は文字列で正しく機能します)。ただし、在庫数のように「差分の大小(増えた数・減った数)を計算したい」場合は、第4回と同じく [int] / [double] へのキャストが必要です。

# 在庫数の増減を数値で出す例(id をキーに突合し、両側の在庫数の差を計算)
Compare-Object $old $new -Property id, stock -PassThru |
    Group-Object id | Where-Object Count -eq 2 | ForEach-Object {
        $o = $_.Group | Where-Object SideIndicator -eq '<='
        $n = $_.Group | Where-Object SideIndicator -eq '=>'
        [pscustomobject]@{
            id    = $_.Name
            Delta = [int]$n.stock - [int]$o.stock   # ★文字列のままだと引き算にならない
        }
    }

応用・注意点

① どちらを Reference にするかで <==> が逆になる

-ReferenceObject-DifferenceObject を取り違えると、入社と退社がそっくり入れ替わって出ます。直感に合わせるなら -ReferenceObject =基準=古い方(先月)」「-DifferenceObject =比較対象=新しい方(今月)」 と固定し、「=>(右・新しい方にある)=増えた=入社」と覚えるのがおすすめです。スクリプトの先頭コメントに「old=Reference/new=Difference」と明記しておくと、後から読む人も迷いません。

② キー列に重複があると分類が崩れる

レシピ2〜4は キー(id)が各リスト内で一意であることが前提です。同じ id が同一CSV内に複数あると、レシピ3の Count -eq 2(変更判定)が誤作動します(変更でないのに2件・3件になり得る)。突合の前に、キーの重複を点検しておくと安全です。

# id の重複チェック(2件以上ある id を表示)
$new | Group-Object id | Where-Object Count -gt 1 |
    Select-Object @{Name='id';Expression={$_.Name}}, Count

重複が出たら、元データ側の名簿管理を見直すか、複数列の複合キー(例:idbranch)で一意になるように -Property を組み合わせます。

③ 列名の表記ゆれ・前後の空白で「変わっていないのに変更扱い」になる

Compare-Object の文字列比較は既定で大文字小文字を区別しません-CaseSensitive を付けると区別する)。一方で、前後の空白や全角/半角の違いは「別の値」として差分になります。「人事」と「人事 」(末尾に空白)は変更扱いです。データの揺れが疑わしいときは、比較前に Trim() などで正規化しておくと、ノイズの差分を防げます。

# 比較前に dept の前後空白を除去して正規化([pscustomobject] で組み直す)
$new = $new | ForEach-Object {
    [pscustomobject]@{ id = $_.id; name = $_.name; dept = $_.dept.Trim() }
}

④ 比較対象が大きいとき(数万行〜)

Compare-Object は内部でハッシュを使うため数万行程度なら実用的に動きますが、数十万行を超える巨大データ同士-Property 複数列で何度も突合するなら、キーを使ったハッシュテーブル(@{}Group-Object -AsHashTable)での突合に切り替えると速くなる場合があります。まずは Compare-Object で素直に書き、遅さを感じたら最適化を検討する——の順で十分です。

# 大量データ向け:id をキーにしたハッシュ表で O(1) 参照
$oldMap = @{}
$old | ForEach-Object { $oldMap[$_.id] = $_ }
$new | Where-Object { -not $oldMap.ContainsKey($_.id) }  # 今月だけにいる=入社

⑤ 出力先CSVの上書き(第4回と同じ注意)

Export-Csv は出力先に同名ファイルがあると 確認なしで上書きします。月次で差分履歴を残したいなら、ファイル名に年月や日時を入れます。

$stamp  = Get-Date -Format "yyyyMMdd"
$outCsv = "C:\hr\diff_report_$stamp.csv"   # 例: diff_report_20260630.csv

⑥ Windows PowerShell 5.1 との差異

Compare-Object中核(-Property / -PassThru / -IncludeEqual / -ExcludeDifferent / SideIndicator)は 5.1 と 7 で共通で、ロジックはそのまま動きます。差異は第4回までと同じく、主に Import-Csv / Export-Csv のエンコードまわりです。

項目 PowerShell 7(pwsh) Windows PowerShell 5.1
Compare-Object の挙動 -Property-PassThru-IncludeEqual など同じ ほぼ同じ(中核機能に差なし)
Import-Csv の既定エンコード UTF-8 環境の既定(多くはShift_JIS/ANSI)
Export-Csv の既定エンコード UTF-8(BOMなし) 環境の既定(多くはShift_JIS/ANSI)
-Encoding UTF8BOM の指定 利用可(BOM付きUTF-8) 指定不可(値が存在しない)
-NoTypeInformation 既定で型情報行は付かない(付けても無害) 付けないと #TYPE ... 行が入る → 付けるのが必須
値の型(Import-Csv すべて文字列 同じくすべて文字列(差分の大小計算時はキャスト必要)

5.1 でUTF-8(BOM付き)の差分レポートを出したいときは、第4回で紹介した [System.IO.File]::WriteAllLinesUTF8Encoding($true) を使うか、PowerShell 7 に統一して -Encoding UTF8BOM で揃えるのが手堅いです。

# 【5.1向け】UTF-8(BOM付き)で差分レポートを出したいときの例
$csv = $report | Sort-Object id | ConvertTo-Csv -NoTypeInformation
$utf8Bom = New-Object System.Text.UTF8Encoding($true)   # $true = BOM付き
[System.IO.File]::WriteAllLines("C:\hr\diff_report.csv", $csv, $utf8Bom)

まとめ

  • 2つのリストの差分は Compare-Object -ReferenceObject 古い -DifferenceObject 新しい -Property キー で取る
  • -Property を必ず指定する(無いと行オブジェクトの参照比較になり全件差分になる)——これが最大の落とし穴
  • SideIndicator<= =先月のみ(退社)/=> =今月のみ(入社)/== =両方(-IncludeEqual 時)
  • 「変更(異動・値変更)」は 比較列を増やし(-Property id, dept)、同じキーが <==> の2件で出る=Group-Object id | Where Count -eq 2 として表現する
  • -PassThru で name・dept など元の全列付きで結果を取り戻せる。レポートCSVは Export-Csv -Encoding UTF8BOM -NoTypeInformation
  • 値は全部文字列。等値比較は問題ないが、増減の大小を計算するなら [int]/[double] キャスト(第4回と同じ)。キー重複・前後空白・Reference/Difference の取り違えに注意
  • 5.1 でも Compare-Object の中核は同じ。違うのはエンコード指定だけ

これで、毎月半日かけていた「名簿・マスタ・権限一覧の目視突合」が、コマンド一本・数秒・取りこぼしなしで再現できるようになります。比較するキーと列を差し替えれば、在庫・権限・設定値など、あらゆる「2つの状態の差分」に応用できます。

次回 第7回は「大量ログからエラー行だけ抽出して日次レポート化」Select-String +正規表現で、肥大化したログファイル群から ERROR 行や特定パターンだけを抜き出し、件数集計つきの日次レポートにまとめるレシピを扱います。お楽しみに。


参考


@kotaro_ai_lab
AI活用や開発効率化について発信しています。フォローお気軽にどうぞ!

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?