文字列のあいまい比較を行うfstrcmpを使ってみる

はじめに

プログラムで文字列の類似度を判定したい場面は多い。
スペルチェック、エラーメッセージでの候補提示、検索機能での曖昧マッチなどに利用される。

fstrcmpは、文字列や配列の曖昧比較を行うライブラリとコマンドラインツールである。
GNU Gettextの曖昧比較機能をベースに開発されており、信頼性が高い。

環境

Ubuntu 24.04 LTS
fstrcmp 0.7

fstrcmpの特徴

比較アルゴリズム

  • 編集距離(レーベンシュタイン距離)ベース
  • 文字の挿入、削除、置換を考慮
  • 0.0〜1.0の範囲で類似度を出力

対応データ

  • ASCII文字列
  • マルチバイト文字列(UTF-8)
  • バイト配列

インストール

sudo apt update
sudo apt install fstrcmp
ログ
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
The following packages were automatically installed and are no longer required:
  crack-common gyp libcares2 libgl1-amber-dri libglapi-mesa libjs-async libjs-events libjs-inherits
  libjs-is-typedarray libjs-prettify libjs-regenerate libjs-source-map libjs-sprintf-js
  libjs-typedarray-to-buffer libllvm17t64 liblttng-ust-common1t64 liblttng-ust-ctl5t64 liblttng-ust1t64
  libnotify-bin libqt5x11extras5 libre2-10 libssl-dev libuv1-dev libxcb-damage0 mesa-utils-bin node-abbrev
  node-ampproject-remapping node-ansi-regex node-ansi-styles node-aproba node-are-we-there-yet node-arrify
  node-async node-async-each node-auto-bind node-babel-plugin-add-module-exports node-babel7-runtime
  node-balanced-match node-base64-js node-binary-extensions node-brace-expansion node-busboy
  node-camelcase node-caniuse-lite node-chownr node-chrome-trace-event node-ci-info node-cjs-module-lexer
  node-cli-boxes node-cli-cursor node-clone node-collection-visit node-color-convert node-color-name
  node-colors node-commander node-commondir node-concat-stream node-console-control-strings
  node-convert-source-map node-core-js node-core-js-pure node-core-util-is node-data-uri-to-buffer
  node-decompress-response node-deep-is node-defaults node-define-property node-delegates node-depd
  node-diff node-electron-to-chromium node-encoding node-end-of-stream node-err-code node-error-ex
  node-es-module-lexer node-escape-string-regexp node-eslint-utils node-eslint-visitor-keys node-esquery
  node-estraverse node-esutils node-events node-fancy-log node-fast-deep-equal node-fast-levenshtein
  node-fetch node-find-up node-flatted node-foreground-child node-fs-readdir-recursive
  node-fs-write-stream-atomic node-fs.realpath node-function-bind node-get-caller-file node-get-stream
  node-get-value node-glob node-globals node-got node-graceful-fs node-growl node-has-flag
  node-has-unicode node-has-value node-has-values node-hosted-git-info node-iconv-lite node-ieee754
  node-iferr node-imurmurhash node-indent-string node-inflight node-inherits node-ini node-interpret
  node-ip node-ip-regex node-is-arrayish node-is-binary-path node-is-buffer node-is-descriptor
  node-is-extglob node-is-path-cwd node-is-plain-obj node-is-plain-object node-is-stream
  node-is-typedarray node-is-windows node-isarray node-isexe node-isobject node-js-tokens node-json-buffer
  node-json-parse-better-errors node-json-schema node-json-schema-traverse node-json-stable-stringify
  node-jsonify node-jsonparse node-kind-of node-levn node-loader-runner node-locate-path
  node-lodash-packages node-lowercase-keys node-lru-cache node-map-visit node-memfs node-merge-stream
  node-mimic-response node-minimatch node-minimist node-minipass node-mute-stream node-n3 node-negotiator
  node-npm-run-path node-object-inspect node-object-visit node-once node-optimist node-optionator
  node-osenv node-p-cancelable node-p-limit node-p-locate node-p-map node-pascalcase node-path-dirname
  node-path-exists node-path-is-absolute node-path-is-inside node-path-type node-pify node-pkg-dir
  node-postcss-value-parser node-prelude-ls node-process-nextick-args node-promise-inflight
  node-promise-retry node-promzard node-prr node-pump node-punycode node-quick-lru node-randombytes
  node-read node-readable-stream node-rechoir node-regenerator-runtime node-regenerator-transform
  node-regexpp node-regjsgen node-repeat-string node-require-directory node-require-from-string
  node-resolve node-resolve-cwd node-resolve-from node-restore-cursor node-resumer node-retry
  node-run-queue node-safe-buffer node-serialize-javascript node-set-blocking node-set-immediate-shim
  node-shebang-command node-shebang-regex node-shell-quote node-signal-exit node-slash node-slice-ansi
  node-source-list-map node-source-map node-spdx-correct node-spdx-exceptions node-spdx-expression-parse
  node-spdx-license-ids node-sprintf-js node-ssri node-stack-utils node-string-decoder node-strip-bom
  node-supports-color node-tapable node-terser node-text-table node-through node-time-stamp
  node-to-fast-properties node-tslib node-type-check node-typedarray node-typedarray-to-buffer node-undici
  node-unicode-canonical-property-names-ecmascript node-unicode-match-property-value-ecmascript
  node-unicode-property-aliases-ecmascript node-unset-value node-uri-js node-util-deprecate node-uuid
  node-v8flags node-validate-npm-package-license node-wcwidth.js node-webpack-sources node-wordwrap
  node-wrappy node-write-file-atomic node-xtend node-y18n node-yallist node-yaml nodejs-doc xbitmaps
Use 'sudo apt autoremove' to remove them.
The following additional packages will be installed:
  libfstrcmp0
The following NEW packages will be installed:
  fstrcmp libfstrcmp0
0 upgraded, 2 newly installed, 0 to remove and 48 not upgraded.
Need to get 15.5 kB of archives.
After this operation, 64.5 kB of additional disk space will be used.
Do you want to continue? [Y/n] y
Get:1 http://archive.ubuntu.com/ubuntu noble/universe amd64 libfstrcmp0 amd64 0.7.D001-3build1 [7562 B]
Get:2 http://archive.ubuntu.com/ubuntu noble/universe amd64 fstrcmp amd64 0.7.D001-3build1 [7910 B]
Fetched 15.5 kB in 1s (19.1 kB/s)
Selecting previously unselected package libfstrcmp0.
(Reading database ... 116786 files and directories currently installed.)
Preparing to unpack .../libfstrcmp0_0.7.D001-3build1_amd64.deb ...
Unpacking libfstrcmp0 (0.7.D001-3build1) ...
Selecting previously unselected package fstrcmp.
Preparing to unpack .../fstrcmp_0.7.D001-3build1_amd64.deb ...
Unpacking fstrcmp (0.7.D001-3build1) ...
Setting up libfstrcmp0 (0.7.D001-3build1) ...
Setting up fstrcmp (0.7.D001-3build1) ...
Processing triggers for man-db (2.12.0-4build2) ...
Processing triggers for libc-bin (2.39-0ubuntu8.5) ...

使ってみる

コマンドライン実行

fstrcmp "文字列1" "文字列2"
# 出力: 0.9000

※出力は0.0〜1.0の類似度スコア

# 完全一致
fstrcmp "hello" "hello"
# 出力: 1.0000

# 部分一致
fstrcmp "hello" "helo"
# 出力: 0.8889

# 全く異なる
fstrcmp "hello" "world"
# 出力: 0.2000

サンプル例

1. スペルチェック候補

# 正しいスペル: "receive"
fstrcmp "receive" "recieve"
# 出力: 0.8571

fstrcmp "receive" "recive"
# 出力: 0.9231

fstrcmp "receive" "recei"
# 出力: 0.8333

2. ファイル名の類似性

fstrcmp "document.txt" "Document.txt"
# 出力: 0.9167

fstrcmp "report_2025.pdf" "report_2024.pdf"
# 出力: 0.9333

fstrcmp "config.json" "config.yaml"
# 出力: 0.6364

3. コマンド名の候補提示

# "ls" の入力ミス
fstrcmp "ls" "sl"
# 出力: 0.5000

fstrcmp "ls" "lz"
# 出力: 0.5000

fstrcmp "grep" "gerp"
# 出力: 0.7500

4. 日本語文字列の比較

fstrcmp "こんにちは" "こんにちわ"
# 出力: 0.8667

fstrcmp "プログラム" "プログラミング"
# 出力: 0.7778

fstrcmp "データベース" "データーベース"
# 出力: 0.9231

閾値を使った判定

シェルスクリプトでの活用

#!/bin/bash

check_similarity() {
    local str1="$1"
    local str2="$2"
    local threshold="$3"
    
    score=$(fstrcmp "$str1" "$str2")
    
    if (( $(echo "$score >= $threshold" | bc -l) )); then
        echo "類似: $str1$str2 (スコア: $score)"
    else
        echo "非類似: $str1$str2 (スコア: $score)"
    fi
}

# 使用例
check_similarity "hello" "helo" 0.8
check_similarity "hello" "world" 0.8

候補一覧からの検索

#!/bin/bash

candidates=("apple" "orange" "banana" "grape" "pineapple")
input="aple"
threshold=0.6

echo "入力: $input"
echo "候補:"

for candidate in "${candidates[@]}"; do
    score=$(fstrcmp "$input" "$candidate")
    if (( $(echo "$score >= $threshold" | bc -l) )); then
        printf "  %s (%.3f)\n" "$candidate" "$score"
    fi
done

実行結果例:

入力: aple
候補:
  apple (0.800)

実際の活用場面

エラーメッセージでの候補提示

コマンドが見つからない場合の候補表示

# 存在しないコマンド "grp" に対して
# "grep", "gzip", "groups" などから最も類似度の高いものを提示する

検索機能での部分マッチ

ファイル検索やデータベース検索でのマッチング

重複データの検出

  • 顧客名の表記ゆれ検出
  • 商品名の類似性チェック
  • データクレンジング

参考

おわりに

fstrcmpは文字列の類似度判定のツールである。
コマンドラインでの簡単な比較から、プログラムでの利用までできるようだ。

実際に活用するとすれば、スペルチェック、検索機能、データクレンジング、候補一覧の提示などの場面で活用できそう。
gitとかでもサブコマンド間違えると、実はこれじゃないみたいな候補を出してくれるけど、これもこういうの使っているのかな。

Hugo で構築されています。
テーマ StackJimmy によって設計されています。