@ycookiey/yfuzzy 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +195 -144
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,144 +1,195 @@
|
|
|
1
|
-
# yfuzzy
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
```
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
1
|
+
# yfuzzy
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@ycookiey/yfuzzy)
|
|
4
|
+
[](https://www.npmjs.com/package/@ycookiey/yfuzzy)
|
|
5
|
+
[](https://bundlephobia.com/package/@ycookiey/yfuzzy)
|
|
6
|
+
[](./LICENSE)
|
|
7
|
+
|
|
8
|
+
日本語特化のあいまい検索ライブラリ。ローマ字混じり入力・訓令式/ヘボン式の並列照合・子音(頭文字)マッチ・誤字許容を 1 パッケージに統合。依存ゼロ・ESM・TypeScript 型付き。
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
shinjuku / sinjuku / しんじゅく / シンジュク → すべて「シンジュク」にヒット
|
|
12
|
+
kmnr → 「カミナリ」にヒット(頭文字)
|
|
13
|
+
カナリ → 「カミナリ」にヒット(飛び石)
|
|
14
|
+
センタイ → 「センダイ」にヒット(誤字)
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## なぜ yfuzzy か
|
|
18
|
+
|
|
19
|
+
既存のあいまい検索ライブラリ(fuse.js / fuzzysort / uFuzzy / match-sorter など)は英語・ラテン文字圏を前提に設計されており、アクセント除去や Unicode 正規化は持つものの、**ローマ字↔かな・訓令式/ヘボン式といった日本語入力の概念を持たない**。
|
|
20
|
+
|
|
21
|
+
日本語 UI の検索ボックスでは、利用者は—
|
|
22
|
+
|
|
23
|
+
- ローマ字で打つ(`sinjuku` / `shinjuku`)
|
|
24
|
+
- かなを部分的に打つ(`しぶ`)
|
|
25
|
+
- 子音だけ打つ(`kmnr`)
|
|
26
|
+
|
|
27
|
+
—のに対し、データはかな・カタカナ・漢字(の読み)で保持されている。yfuzzy はこの「入力とデータの表記ギャップ」を埋めることに特化する。
|
|
28
|
+
|
|
29
|
+
## スコープ
|
|
30
|
+
|
|
31
|
+
**向いている**: 日本語の読み(かな)を索引にした短い文字列の検索 — 駅名・地名・商品名・人名の読み、type-ahead / オートコンプリート。
|
|
32
|
+
|
|
33
|
+
**向いていない**:
|
|
34
|
+
|
|
35
|
+
- **漢字をそのまま検索**: yfuzzy は漢字を読みに変換しない(リテラル一致のみ)。読みで当てたいなら読み仮名を用意し `getText` で返す([使い方](#使い方)参照)。
|
|
36
|
+
- **ラテン文字の汎用あいまい検索 / 全文検索**(複数フィールド・トークン化・TF-IDF): fuse.js / MiniSearch 等が成熟している。用途で使い分けるとよい。
|
|
37
|
+
|
|
38
|
+
## 特長
|
|
39
|
+
|
|
40
|
+
- **ローマ字入力 → かな照合**(IME 不要。訓令式・ヘボン式どちらの綴りでも)
|
|
41
|
+
- **子音だけの頭文字検索**(`kmnr` → `カミナリ`)
|
|
42
|
+
- **飛び石 subsequence** + 境界/連続を優先するスコアリング
|
|
43
|
+
- **誤字許容**(編集距離。前方/末尾欠落は上位層が拾い、中間の置換/挿入/削除/転置に特化)
|
|
44
|
+
- **ハイライト用インデックス**を元文字列上で返す(ローマ字経由でも正確に逆マップし、長音の穴なし)
|
|
45
|
+
- **単発関数 / 索引構築**の両 API、TypeScript 型付き(`SearchResult<T>` に `item` が流れる)
|
|
46
|
+
- **依存ゼロ**(wanakana の必要部分のみ改変同梱)
|
|
47
|
+
|
|
48
|
+
## 類似パッケージとの比較
|
|
49
|
+
|
|
50
|
+
| 機能 | yfuzzy | [fuse.js](https://www.fusejs.io/) | [fuzzysort](https://github.com/farzher/fuzzysort) | [MiniSearch](https://github.com/lucaong/minisearch) | [uFuzzy](https://github.com/leeoniya/uFuzzy) | [match-sorter](https://github.com/kentcdodds/match-sorter) |
|
|
51
|
+
|---|:--:|:--:|:--:|:--:|:--:|:--:|
|
|
52
|
+
| ローマ字入力 → かな | ✅ | ➖ | ➖ | ➖ | ➖ | ➖ |
|
|
53
|
+
| 訓令式 / ヘボン式 並列 | ✅ | ➖ | ➖ | ➖ | ➖ | ➖ |
|
|
54
|
+
| かな正規化(半角/全角・ひら↔カタ) | ✅ | ➖ | ➖ | ➖ | ➖ | ➖ |
|
|
55
|
+
| 頭文字(子音)マッチ | ✅ | ➖ | 🔸 | ➖ | 🔸 | 🔸 |
|
|
56
|
+
| 飛び石 subsequence | ✅ | 🔸 | ✅ | ➖ | ✅ | 🔸 |
|
|
57
|
+
| 誤字許容(編集距離) | ✅ | ✅ | ➖ | ✅ | 🔸 | ➖ |
|
|
58
|
+
| ハイライト位置の返却 | ✅ | ✅ | ✅ | 🔸 | ✅ | ➖ |
|
|
59
|
+
| 索引の事前構築 | ✅ | ✅ | ✅ | ✅ | ➖ | ➖ |
|
|
60
|
+
| 全文検索(複数フィールド/トークン) | ➖ | ✅ | 🔸 | ✅ | ➖ | 🔸 |
|
|
61
|
+
| ラテン文字の diacritics 除去 | ➖ | ✅ | ➖ | 🔸 | ✅ | ✅ |
|
|
62
|
+
| 依存ゼロ | ✅ | ✅ | ✅ | ✅ | ✅ | ➖ |
|
|
63
|
+
|
|
64
|
+
凡例: ✅ 組み込み / 🔸 部分的・間接的(または要カスタム) / ➖ なし。各ライブラリの調査時点の既定挙動を基準とし、多くはカスタム関数で拡張できる。本表は「日本語入力対応+主要なあいまいマッチ機能」に観点を絞ったもの。
|
|
65
|
+
|
|
66
|
+
## インストール
|
|
67
|
+
|
|
68
|
+
```sh
|
|
69
|
+
npm install @ycookiey/yfuzzy
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Node 18+ / ESM 専用。
|
|
73
|
+
|
|
74
|
+
## 使い方
|
|
75
|
+
|
|
76
|
+
### 文字列配列
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
import { search } from '@ycookiey/yfuzzy';
|
|
80
|
+
|
|
81
|
+
const stations = ['トウキョウ', 'シンジュク', 'シブヤ', 'シナガワ'];
|
|
82
|
+
|
|
83
|
+
search('sinjuku', stations);
|
|
84
|
+
// → [{ item: 'シンジュク', refIndex: 1, score: 1, tier: 1 }]
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### オブジェクト配列(getText)
|
|
88
|
+
|
|
89
|
+
`getText` で検索対象の文字列を取り出す。漢字を読みで当てたい場合も、読み仮名を返せばよい(読みの付与は利用者責務)。
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
const items = [
|
|
93
|
+
{ label: '東京', yomi: 'トウキョウ' },
|
|
94
|
+
{ label: '新宿', yomi: 'シンジュク' },
|
|
95
|
+
];
|
|
96
|
+
|
|
97
|
+
search('しんじゅく', items, { getText: (x) => x.yomi });
|
|
98
|
+
// → [{ item: { label: '新宿', … }, refIndex: 1, score: 1, tier: 1 }]
|
|
99
|
+
|
|
100
|
+
search('toukyou', items, { getText: (x) => x.label });
|
|
101
|
+
// → [] ← label(漢字)はそのまま照合されるので当たらない
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### 索引の再利用(createIndex)
|
|
105
|
+
|
|
106
|
+
同じデータを繰り返し検索するなら事前構築する。単発 `search` と結果は同一。
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
import { createIndex } from '@ycookiey/yfuzzy';
|
|
110
|
+
|
|
111
|
+
const index = createIndex(stations);
|
|
112
|
+
index.search('しぶ'); // → シブヤ
|
|
113
|
+
index.search('しな', { limit: 5 }); // per-call で検索オプションを上書き
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### ハイライト(includeMatches)
|
|
117
|
+
|
|
118
|
+
`includeMatches: true` で、**元文字列上**の一致区間 `[start, end)`(半開・昇順・マージ済み)を返す(tier 1/2 のみ)。
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
const r = search('keki', ['ケーキ'], { includeMatches: true });
|
|
122
|
+
// r[0].matches → [[0, 3]] ← 長音「ー」を含めて穴なし
|
|
123
|
+
|
|
124
|
+
function highlight(text: string, ranges: readonly [number, number][]): string {
|
|
125
|
+
let out = '';
|
|
126
|
+
let last = 0;
|
|
127
|
+
for (const [s, e] of ranges) {
|
|
128
|
+
out += text.slice(last, s) + '<mark>' + text.slice(s, e) + '</mark>';
|
|
129
|
+
last = e;
|
|
130
|
+
}
|
|
131
|
+
return out + text.slice(last);
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## マッチの仕組み
|
|
136
|
+
|
|
137
|
+
クエリは tier 昇順で評価され、最初に一致した層で確定する。`score` は 0–1(高いほど良い)で、層ごとにレンジが分離する。
|
|
138
|
+
|
|
139
|
+
| tier | 層 | 例 | score 帯 |
|
|
140
|
+
|---|---|---|---|
|
|
141
|
+
| 1 | 完全一致(かな/ローマ字の前方・部分一致) | `シンジュク`→`シンジュク` | (0.75, 1] |
|
|
142
|
+
| 2 | 柔軟(subsequence・境界/連続ボーナス・頭文字) | `kmnr`→`カミナリ` | (0.5, 0.75) |
|
|
143
|
+
| 3 | 誤字許容(編集距離、中間の置換/挿入/削除/転置) | `センタイ`→`センダイ` | (0.25, 0.5) |
|
|
144
|
+
| 4 | 構造類似(Tversky bigram、デフォルト無効) | 大きく崩れた入力 | (0, 0.25] |
|
|
145
|
+
|
|
146
|
+
`score` 帯が重ならないため、利用者は閾値だけで「確実なヒット」と「もしかして」を分離できる。結果は `score` 降順(同点は `refIndex` 昇順で安定)。
|
|
147
|
+
|
|
148
|
+
## API
|
|
149
|
+
|
|
150
|
+
### `search(query, items, options?)`
|
|
151
|
+
|
|
152
|
+
単発検索。`createIndex(items, options).search(query)` と等価。
|
|
153
|
+
|
|
154
|
+
### `createIndex(items, options?) → SearchIndex`
|
|
155
|
+
|
|
156
|
+
事前構築した索引を返す。`index.search(query, searchOptions?)` で per-call の検索オプションを上書きできる。`index.size` で件数。
|
|
157
|
+
|
|
158
|
+
### オプション
|
|
159
|
+
|
|
160
|
+
構築時オプション(`createIndex` / `search` の `options`):
|
|
161
|
+
|
|
162
|
+
| 名前 | 型 | 既定 | 説明 |
|
|
163
|
+
|---|---|---|---|
|
|
164
|
+
| `getText` | `(item: T) => string` | — | オブジェクト配列で検索対象文字列を取り出す(`T[]` のとき必須) |
|
|
165
|
+
| `romaji` | `'hepburn' \| 'kunrei' \| 'both' \| false` | `'both'` | ローマ字照合の方式。`false` でかな空間のみ |
|
|
166
|
+
| `separatorExpansion` | `boolean \| string[]` | `false` | 区切り文字を「除去」「境界」両経路で展開(`true` で `['-', ' ', '・', '_']`) |
|
|
167
|
+
|
|
168
|
+
検索時オプション(`index.search` で上書き可):
|
|
169
|
+
|
|
170
|
+
| 名前 | 型 | 既定 | 説明 |
|
|
171
|
+
|---|---|---|---|
|
|
172
|
+
| `limit` | `number` | 無制限 | 返却件数の上限 |
|
|
173
|
+
| `maxTier` | `1 \| 2 \| 3 \| 4` | `3` | 実行する層の上限(`4` で構造類似層を有効化) |
|
|
174
|
+
| `minScore` | `number` | `0` | この score 未満を除外 |
|
|
175
|
+
| `includeMatches` | `boolean` | `false` | `matches` を含める |
|
|
176
|
+
|
|
177
|
+
### 結果 `SearchResult<T>`
|
|
178
|
+
|
|
179
|
+
```ts
|
|
180
|
+
type SearchResult<T> = {
|
|
181
|
+
item: T;
|
|
182
|
+
refIndex: number; // items 内の位置
|
|
183
|
+
score: number; // 0–1、高いほど良い
|
|
184
|
+
tier: 1 | 2 | 3 | 4;
|
|
185
|
+
matches?: readonly [number, number][]; // includeMatches 時、tier 1/2 のみ
|
|
186
|
+
};
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## 性能
|
|
190
|
+
|
|
191
|
+
短い文字列の索引向け(数百〜数十万件)。文字集合プレフィルタと tier 打ち切りにより、1 クエリの実測は概ね 千件 ~1.5ms / 1 万件 ~10ms / 10 万件 ~90ms(環境依存)。同条件で fuse.js より高速。`maxTier` を下げる / `romaji: 'kunrei'` で片方式に絞ると更に軽くなる。索引構築は 10 万件で ~0.7s(一度きり)。
|
|
192
|
+
|
|
193
|
+
## ライセンス
|
|
194
|
+
|
|
195
|
+
MIT。ローマ字→かな変換に [WanaKana](https://github.com/WaniKani/WanaKana)(MIT)のコードを改変同梱している。詳細は [`THIRD_PARTY_NOTICES.md`](./THIRD_PARTY_NOTICES.md)。
|