文章AとBの類似率を数値化するJaccard係数をシェル・sqlite・mecabで実現してみた

はじめに このブログの記事Aと記事Bがどれだけ類似しているかを検出して、関連記事を自動化したいと思った。そこで簡単に思いつく方法は、それぞれの記事のキーワードを抽出し、お互いどれだけ共通のキーワードを持っているか調べれば良さそう。 そこでJaccard係数という手法を見つけた。 Jaccard係数とは Jaccard係数は、2つの集合 ( A ) と ( B ) の類似度を測る数学的なアプローチ。 $$ J(A,B)=\frac{|A\cap B|}{|A\cup B|} $$ 記事Aと記事Bのキーワードに当てはめて集合を考えると、 重複を取り除いた記事Aと記事Bのキーワードの合計が分母 重複する部分のキーワードだけを抽出したのが分子 となる。そしてこの割り算の結果をJaccard係数とし、 係数が大きいほど類似度が高いことを意味する。 Jaccard係数はsqliteで簡単に計算できる 次は、実際にこのブログサイトで関連記事を作成するために使っているSQLである。 全ての記事に対してJaccard係数を計算し、係数が0.1以上のものを関連記事として抽出する処理をさせている。 WITH target_words AS ( SELECT word FROM keywords WHERE filename = '__FILENAME__' ), intersections AS ( SELECT k.filename, COUNT(*) AS intersect_count FROM keywords k JOIN target_words t ON k.word = t.word WHERE k.filename != '__FILENAME__' GROUP BY k.filename ), unions AS ( SELECT k.filename, COUNT(DISTINCT k.word) + (SELECT COUNT(DISTINCT word) FROM target_words) - COUNT(DISTINCT CASE WHEN k.word IN (SELECT word FROM target_words) THEN k.word END) AS union_count FROM keywords k WHERE k.filename != '__FILENAME__' GROUP BY k.filename ) SELECT i.filename, CAST(i.intersect_count AS REAL) / u.union_count AS jaccard FROM intersections i JOIN unions u ON i.filename = u.filename WHERE CAST(i.intersect_count AS REAL) / u.union_count >= 0.1 ORDER BY jaccard DESC; 複雑に入り組んでいて読みにくいが、Jaccard係数を計算するために共通部分の要素を求めるintersectionsと、キーワードの和集合を求めるunionsがポイントである。 ...

公開: 2025年4月3日 · 更新: 2026年3月25日 · Toshihiko Arai

iOSアプリ開発でSQLiteを使う FMDB

iOSアプリ開発でSQLiteを使う FMDB はじめに iOSアプリ開発で、SQLiteを使ってデータ管理する方法を調べてみました。SQLiteをそのまま使うよりは、SQLiteをラッパーしたFMDBライブラリを使うとより便利です。そちらもご紹介いたします。 iOSアプリ開発ではじめてSQLiteを使う libsqlite3.tbdをプロジェクトに追加 libsqlite3.tbdをプロジェクトに追加します。 Xcodeのプロジェクトナビゲータでプロジェクト名を選択し、「Build Phases」タブに移動します。「Link Binary With Libraries」セクションで、+ボタンを押して、libsqlite3.tbdを追加します。 ソースコード // // ViewController.swift // SampleSqlite // // Created by Toshihiko Arai on 2024/10/11. // import UIKit import SQLite3 // 日本語文字を登録するために必要な定数 let SQLITE_TRANSIENT = unsafeBitCast(-1, to: sqlite3_destructor_type.self) class ViewController: UIViewController { var db: OpaquePointer? override func viewDidLoad() { super.viewDidLoad() // データベースの準備 if let databasePath = createDatabase() { openDatabase(path: databasePath) createTable() insertData(name: "空条承太郎", age: 40) insertData(name: "空条徐倫", age: 17) fetchData() } } // データベースのパスを取得して作成 func createDatabase() -> String? { let fileURL = try? FileManager.default .url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false) .appendingPathComponent("sample.sqlite") return fileURL?.path } // データベースのオープン func openDatabase(path: String) { if sqlite3_open(path, &db) != SQLITE_OK { print("データベースを開くことができません") } } // テーブルの作成 func createTable() { let createTableString = """ CREATE TABLE IF NOT EXISTS Person( Id INTEGER PRIMARY KEY AUTOINCREMENT, Name CHAR(255), Age INTEGER); """ var createTableStatement: OpaquePointer? if sqlite3_prepare_v2(db, createTableString, -1, &createTableStatement, nil) == SQLITE_OK { if sqlite3_step(createTableStatement) == SQLITE_DONE { print("テーブルの作成に成功しました") } else { print("テーブルの作成に失敗しました") } } else { print("テーブル作成の準備に失敗しました") } sqlite3_finalize(createTableStatement) } // データの挿入 func insertData(name: String, age: Int32) { let insertStatementString = "INSERT INTO Person (Name, Age) VALUES (?, ?);" var insertStatement: OpaquePointer? if sqlite3_prepare_v2(db, insertStatementString, -1, &insertStatement, nil) == SQLITE_OK { sqlite3_bind_text(insertStatement, 1, name, -1, SQLITE_TRANSIENT) sqlite3_bind_int(insertStatement, 2, age) if sqlite3_step(insertStatement) == SQLITE_DONE { print("データの挿入に成功しました") } else { print("データの挿入に失敗しました") } } else { print("挿入ステートメントの準備に失敗しました") } sqlite3_finalize(insertStatement) } // データの取得 func fetchData() { let queryStatementString = "SELECT * FROM Person;" var queryStatement: OpaquePointer? if sqlite3_prepare_v2(db, queryStatementString, -1, &queryStatement, nil) == SQLITE_OK { while sqlite3_step(queryStatement) == SQLITE_ROW { let id = sqlite3_column_int(queryStatement, 0) let name = String(cString: sqlite3_column_text(queryStatement, 1)) let age = sqlite3_column_int(queryStatement, 2) print("Query Result:") print("ID: \(id), Name: \(name), Age: \(age)") } } else { print("クエリの準備に失敗しました") } sqlite3_finalize(queryStatement) } } SQLiteのデータ型 SQLiteには4つの基本的なストレージクラス(データ型)があります。 ...

公開: 2024年10月11日 · 更新: 2026年2月25日 · Toshihiko Arai

PostgreSQLと向き合うための 現場で使えるデータベース操作・SQLノート

はじめに この記事では、PostgreSQL を実務で使うときに毎回見直したくなる操作を 1 本にまとめています。Rocky Linux と macOS でのインストール手順、LAN 越しの外部接続設定、pg_dump / pg_restore を使ったバックアップ・リストア、そして psql の便利コマンドや SQL の基本までを順に確認できる構成です。 MySQL について同じ粒度でまとめたものは MySQLと向き合うための 現場で使えるデータベース操作・SQLノート をご覧ください。 Rocky Linuxにインストール PostgreSQL 13.15 を Rocky Linux にインストールする手順は以下の通りです。 メモ: 記事執筆時は PostgreSQL 13 系を入れています。PostgreSQL 13 はコミュニティサポートが 2025 年 11 月で終了しているため、新規構築では postgresql16-server など現役メジャーバージョンの利用を検討してください。手順の流れ自体はバージョンとリポジトリの番号を読み替えればそのまま使えます。また、Rocky Linux 9 系を使っている場合は EL-8 を EL-9 に置き換えてください。 リポジトリを有効化する まず、PostgreSQL 13 のリポジトリを追加します。 sudo dnf install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-8-x86_64/pgdg-redhat-repo-latest.noarch.rpm PostgreSQL 13をインストールする PostgreSQL 13 のインストールを行います。AppStream 側に同梱されている古い postgresql モジュールが優先されないよう、無効化してから入れるのが安全です。 sudo dnf -qy module disable postgresql sudo dnf install -y postgresql13-server PostgreSQLの初期化 PostgreSQLのデータベースを初期化します。 ...

公開: 2024年7月4日 · 更新: 2026年5月27日 · Toshihiko Arai

MySQLと向き合うための 現場で使えるデータベース操作・SQLノート

このページは MySQL を使うときに毎回開いて確認している自分用のノートをまとめたものです。CLI のセットアップから root パスワード設定、データベースとテーブルの基本操作、mysqldump でのバックアップと復元、\G や pager less -S といった結果表示の小技、よく使うデータ型、INSERT・UPDATE・JOIN・GROUP_CONCAT などの SQL までを 1 ページに集めています。 特定の章だけ拾い読みできるよう小さなトピック単位で並べているので、目次から目的の項目に飛んで使う想定です。PostgreSQL を使うときの同じ粒度のノートは PostgreSQLと向き合うための 現場で使えるデータベース操作・SQLノート にまとめています。 MySQL開発環境のセットアップ 項目 バージョン インストールコマンド macOS 14.2 (Sonoma) MySQL 8.0 brew install [email protected] 上記は記事執筆時に動作確認した組み合わせです。2026年5月時点では Homebrew で brew install mysql を実行すると MySQL 9.x が入り、長期サポート版の [email protected](LTS)も選べます。新規に環境を作るならどちらかをおすすめします。バージョンを固定したい場合は [email protected] も引き続き利用できますが、旧安定系列で現在は通常サポートが終了しており、新規環境には [email protected](LTS)などをおすすめします。macOS 15 (Sequoia) などより新しいバージョンでも、Homebrew を使う限り手順はほぼ変わりません。 インストールが完了したらCLIでmysqlコマンドを使えるように、環境パスを通しておきます。[email protected] 以外を入れた場合は、[email protected] などインストールした formula 名にパスを読み替えてください。 export PATH="/opt/homebrew/opt/[email protected]/bin:$PATH" mysqlの起動と停止 $ mysql.server start # 起動 $ mysql.server stop # 停止 MySQLの初期設定 rootユーザーにパスワードを設定する Homebrew でインストールした直後の MySQL 8.x は、root パスワードが未設定のまま起動するので、まずは無パスワードで入って初期パスワードを設定します。Linux ディストリビューションのパッケージ(apt / dnf など)で入れた場合は、初回起動時に一時パスワードがログ(/var/log/mysqld.log など)に出ているか、mysql_secure_installation で対話的に設定する流れになるので、環境に合わせて読み替えてください。 ...

公開: 2024年1月10日 · 更新: 2026年5月27日 · Toshihiko Arai