@ycookiey/yfuzzy 0.1.0
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/LICENSE +21 -0
- package/README.md +144 -0
- package/THIRD_PARTY_NOTICES.md +38 -0
- package/dist/ablate.d.ts +8 -0
- package/dist/ablate.d.ts.map +1 -0
- package/dist/ablate.js +11 -0
- package/dist/ablate.js.map +1 -0
- package/dist/bigram.d.ts +6 -0
- package/dist/bigram.d.ts.map +1 -0
- package/dist/bigram.js +17 -0
- package/dist/bigram.js.map +1 -0
- package/dist/charmask.d.ts +5 -0
- package/dist/charmask.d.ts.map +1 -0
- package/dist/charmask.js +23 -0
- package/dist/charmask.js.map +1 -0
- package/dist/entry.d.ts +29 -0
- package/dist/entry.d.ts.map +1 -0
- package/dist/entry.js +32 -0
- package/dist/entry.js.map +1 -0
- package/dist/expand.d.ts +12 -0
- package/dist/expand.d.ts.map +1 -0
- package/dist/expand.js +52 -0
- package/dist/expand.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +56 -0
- package/dist/index.js.map +1 -0
- package/dist/matches.d.ts +8 -0
- package/dist/matches.d.ts.map +1 -0
- package/dist/matches.js +68 -0
- package/dist/matches.js.map +1 -0
- package/dist/normalize.d.ts +24 -0
- package/dist/normalize.d.ts.map +1 -0
- package/dist/normalize.js +114 -0
- package/dist/normalize.js.map +1 -0
- package/dist/pipeline.d.ts +31 -0
- package/dist/pipeline.d.ts.map +1 -0
- package/dist/pipeline.js +75 -0
- package/dist/pipeline.js.map +1 -0
- package/dist/query.d.ts +21 -0
- package/dist/query.d.ts.map +1 -0
- package/dist/query.js +33 -0
- package/dist/query.js.map +1 -0
- package/dist/romaji/convert.d.ts +23 -0
- package/dist/romaji/convert.d.ts.map +1 -0
- package/dist/romaji/convert.js +34 -0
- package/dist/romaji/convert.js.map +1 -0
- package/dist/romaji/table-hepburn.d.ts +5 -0
- package/dist/romaji/table-hepburn.d.ts.map +1 -0
- package/dist/romaji/table-hepburn.js +46 -0
- package/dist/romaji/table-hepburn.js.map +1 -0
- package/dist/romaji/table-kunrei.d.ts +5 -0
- package/dist/romaji/table-kunrei.d.ts.map +1 -0
- package/dist/romaji/table-kunrei.js +46 -0
- package/dist/romaji/table-kunrei.js.map +1 -0
- package/dist/syllable.d.ts +37 -0
- package/dist/syllable.d.ts.map +1 -0
- package/dist/syllable.js +91 -0
- package/dist/syllable.js.map +1 -0
- package/dist/tiers/tier1-exact.d.ts +3 -0
- package/dist/tiers/tier1-exact.d.ts.map +1 -0
- package/dist/tiers/tier1-exact.js +59 -0
- package/dist/tiers/tier1-exact.js.map +1 -0
- package/dist/tiers/tier2-flexible.d.ts +3 -0
- package/dist/tiers/tier2-flexible.d.ts.map +1 -0
- package/dist/tiers/tier2-flexible.js +169 -0
- package/dist/tiers/tier2-flexible.js.map +1 -0
- package/dist/tiers/tier3-typo.d.ts +3 -0
- package/dist/tiers/tier3-typo.d.ts.map +1 -0
- package/dist/tiers/tier3-typo.js +95 -0
- package/dist/tiers/tier3-typo.js.map +1 -0
- package/dist/tiers/tier4-structural.d.ts +3 -0
- package/dist/tiers/tier4-structural.d.ts.map +1 -0
- package/dist/tiers/tier4-structural.js +29 -0
- package/dist/tiers/tier4-structural.js.map +1 -0
- package/dist/tiers/types.d.ts +17 -0
- package/dist/tiers/types.d.ts.map +1 -0
- package/dist/tiers/types.js +4 -0
- package/dist/tiers/types.js.map +1 -0
- package/dist/to-kana.d.ts +2 -0
- package/dist/to-kana.d.ts.map +1 -0
- package/dist/to-kana.js +8 -0
- package/dist/to-kana.js.map +1 -0
- package/dist/types.d.ts +42 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/vendor/wanakana/romaji-to-kana.d.ts +6 -0
- package/dist/vendor/wanakana/romaji-to-kana.d.ts.map +1 -0
- package/dist/vendor/wanakana/romaji-to-kana.js +182 -0
- package/dist/vendor/wanakana/romaji-to-kana.js.map +1 -0
- package/package.json +54 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ycookiey
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# yfuzzy
|
|
2
|
+
|
|
3
|
+
日本語特化のあいまい検索ライブラリ。ローマ字混じり入力・訓令式/ヘボン式の並列照合・子音(頭文字)マッチ・誤字許容を 1 パッケージに統合。依存ゼロ・ESM・TypeScript 型付き。
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
shinjuku / sinjuku / しんじゅく / シンジュク → すべて「シンジュク」にヒット
|
|
7
|
+
kmnr → 「カミナリ」にヒット(頭文字)
|
|
8
|
+
カナリ → 「カミナリ」にヒット(飛び石)
|
|
9
|
+
センタイ → 「センダイ」にヒット(誤字)
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## インストール
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
npm install @ycookiey/yfuzzy
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Node 18+ / ESM 専用。
|
|
19
|
+
|
|
20
|
+
## クイックスタート
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import { search } from '@ycookiey/yfuzzy';
|
|
24
|
+
|
|
25
|
+
const stations = ['トウキョウ', 'シンジュク', 'シブヤ', 'シナガワ'];
|
|
26
|
+
|
|
27
|
+
search('sinjuku', stations);
|
|
28
|
+
// → [{ item: 'シンジュク', refIndex: 1, score: 1, tier: 1 }]
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
オブジェクト配列は `getText` で検索対象文字列を取り出す:
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
const items = [
|
|
35
|
+
{ id: 1, yomi: 'トウキョウ' },
|
|
36
|
+
{ id: 2, yomi: 'シンジュク' },
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
search('しんじゅく', items, { getText: (x) => x.yomi });
|
|
40
|
+
// → [{ item: { id: 2, yomi: 'シンジュク' }, refIndex: 1, score: 1, tier: 1 }]
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
同じデータを繰り返し検索するなら `createIndex` で事前構築する(単発 `search` と結果は同一):
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
import { createIndex } from '@ycookiey/yfuzzy';
|
|
47
|
+
|
|
48
|
+
const index = createIndex(stations);
|
|
49
|
+
index.search('しぶ'); // → シブヤ
|
|
50
|
+
index.search('しな', { limit: 5 });
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## 漢字について
|
|
54
|
+
|
|
55
|
+
yfuzzy は**漢字を読みに変換しない**(リテラル一致のみ)。読みで検索させたい場合は、読み仮名を別途用意して `getText` で返す(読みの付与は利用者責務)。
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
const items = [{ label: '東京', yomi: 'トウキョウ' }];
|
|
59
|
+
search('toukyou', items, { getText: (x) => x.yomi }); // ヒット
|
|
60
|
+
search('toukyou', items, { getText: (x) => x.label }); // ヒットしない(漢字はそのまま)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## マッチの仕組み(4 層)
|
|
64
|
+
|
|
65
|
+
クエリは tier 昇順で評価され、最初に一致した層で確定する。`score` は 0–1(高いほど良い)で、層ごとにレンジが分離する。
|
|
66
|
+
|
|
67
|
+
| tier | 層 | 例 | score 帯 |
|
|
68
|
+
|---|---|---|---|
|
|
69
|
+
| 1 | 完全一致(かな/ローマ字の前方・部分一致) | `シンジュク`→`シンジュク` | (0.75, 1] |
|
|
70
|
+
| 2 | 柔軟(subsequence・境界/連続ボーナス・頭文字) | `kmnr`→`カミナリ` | (0.5, 0.75) |
|
|
71
|
+
| 3 | 誤字許容(編集距離、中間の置換/挿入/削除/転置) | `センタイ`→`センダイ` | (0.25, 0.5) |
|
|
72
|
+
| 4 | 構造類似(Tversky bigram、デフォルト無効) | 大きく崩れた入力 | (0, 0.25] |
|
|
73
|
+
|
|
74
|
+
`score` 帯が重ならないため、利用者は閾値だけで「確実なヒット」と「もしかして」を分離できる。
|
|
75
|
+
|
|
76
|
+
## ハイライト(matches)
|
|
77
|
+
|
|
78
|
+
`includeMatches: true` で、**元文字列上**の一致区間 `[start, end)`(半開・昇順・マージ済み)を返す(tier 1/2 のみ)。
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
const r = search('keki', ['ケーキ'], { includeMatches: true });
|
|
82
|
+
// r[0].matches → [[0, 3]] ← 長音「ー」を含めて穴なし
|
|
83
|
+
|
|
84
|
+
function highlight(text: string, ranges: readonly [number, number][]): string {
|
|
85
|
+
let out = '';
|
|
86
|
+
let last = 0;
|
|
87
|
+
for (const [s, e] of ranges) {
|
|
88
|
+
out += text.slice(last, s) + '<mark>' + text.slice(s, e) + '</mark>';
|
|
89
|
+
last = e;
|
|
90
|
+
}
|
|
91
|
+
return out + text.slice(last);
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## API
|
|
96
|
+
|
|
97
|
+
### `search(query, items, options?)`
|
|
98
|
+
|
|
99
|
+
単発検索。`createIndex(items, options).search(query)` と等価。
|
|
100
|
+
|
|
101
|
+
### `createIndex(items, options?) → SearchIndex`
|
|
102
|
+
|
|
103
|
+
事前構築した索引を返す。`index.search(query, searchOptions?)` で per-call の検索オプションを上書きできる。`index.size` で件数。
|
|
104
|
+
|
|
105
|
+
### オプション
|
|
106
|
+
|
|
107
|
+
構築時オプション(`createIndex` / `search` の `options`):
|
|
108
|
+
|
|
109
|
+
| 名前 | 型 | 既定 | 説明 |
|
|
110
|
+
|---|---|---|---|
|
|
111
|
+
| `getText` | `(item: T) => string` | — | オブジェクト配列で検索対象文字列を取り出す(`T[]` のとき必須) |
|
|
112
|
+
| `romaji` | `'hepburn' \| 'kunrei' \| 'both' \| false` | `'both'` | ローマ字照合の方式。`false` でかな空間のみ |
|
|
113
|
+
| `separatorExpansion` | `boolean \| string[]` | `false` | 区切り文字を「除去」「境界」両経路で展開(`true` で `['-', ' ', '・', '_']`) |
|
|
114
|
+
|
|
115
|
+
検索時オプション(`index.search` で上書き可):
|
|
116
|
+
|
|
117
|
+
| 名前 | 型 | 既定 | 説明 |
|
|
118
|
+
|---|---|---|---|
|
|
119
|
+
| `limit` | `number` | 無制限 | 返却件数の上限 |
|
|
120
|
+
| `maxTier` | `1 \| 2 \| 3 \| 4` | `3` | 実行する層の上限(`4` で構造類似層を有効化) |
|
|
121
|
+
| `minScore` | `number` | `0` | この score 未満を除外 |
|
|
122
|
+
| `includeMatches` | `boolean` | `false` | `matches` を含める |
|
|
123
|
+
|
|
124
|
+
### 結果 `SearchResult<T>`
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
type SearchResult<T> = {
|
|
128
|
+
item: T;
|
|
129
|
+
refIndex: number; // items 内の位置
|
|
130
|
+
score: number; // 0–1、高いほど良い
|
|
131
|
+
tier: 1 | 2 | 3 | 4;
|
|
132
|
+
matches?: readonly [number, number][]; // includeMatches 時、tier 1/2 のみ
|
|
133
|
+
};
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
結果は `score` 降順(同点は `refIndex` 昇順で安定)。
|
|
137
|
+
|
|
138
|
+
## 性能
|
|
139
|
+
|
|
140
|
+
短い文字列の索引向け(数百〜数十万件)。文字集合プレフィルタと tier 打ち切りにより、1 クエリの実測は概ね 千件 ~1.5ms / 1 万件 ~10ms / 10 万件 ~90ms(環境依存)。同条件で fuse.js より高速。`maxTier` を下げる / `romaji: 'kunrei'` で片方式に絞ると更に軽くなる。索引構築は 10 万件で ~0.7s(一度きり)。
|
|
141
|
+
|
|
142
|
+
## ライセンス
|
|
143
|
+
|
|
144
|
+
MIT。ローマ字→かな変換に [WanaKana](https://github.com/WaniKani/WanaKana)(MIT)のコードを改変同梱している。詳細は [`THIRD_PARTY_NOTICES.md`](./THIRD_PARTY_NOTICES.md)。
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Third-Party Notices
|
|
2
|
+
|
|
3
|
+
yfuzzy bundles (vendors) source code from the following third-party project.
|
|
4
|
+
The code has been ported to TypeScript and modified; see the header comment in
|
|
5
|
+
`src/vendor/wanakana/` for the list of modifications.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## WanaKana
|
|
10
|
+
|
|
11
|
+
- Project: https://github.com/WaniKani/WanaKana
|
|
12
|
+
- Version vendored: 5.3.1
|
|
13
|
+
- Files derived from: `src/toKana.js`, `src/utils/kanaMapping.js`,
|
|
14
|
+
`src/utils/romajiToKanaMap.js`
|
|
15
|
+
- Used in: `src/vendor/wanakana/romaji-to-kana.ts`
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
The MIT License (MIT)
|
|
19
|
+
|
|
20
|
+
Copyright (c) 2013 WaniKani Community Github
|
|
21
|
+
|
|
22
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
23
|
+
this software and associated documentation files (the "Software"), to deal in
|
|
24
|
+
the Software without restriction, including without limitation the rights to
|
|
25
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
|
26
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
|
27
|
+
subject to the following conditions:
|
|
28
|
+
|
|
29
|
+
The above copyright notice and this permission notice shall be included in all
|
|
30
|
+
copies or substantial portions of the Software.
|
|
31
|
+
|
|
32
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
33
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
34
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
35
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
36
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
37
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
38
|
+
```
|
package/dist/ablate.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ablate.d.ts","sourceRoot":"","sources":["../src/ablate.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,GAAG;;;;;;CAMf,CAAC"}
|
package/dist/ablate.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// 一時: ablation 計測用トグル。ABLATE 環境変数(カンマ区切り)で各最適化を OFF。
|
|
2
|
+
// 計測後に git checkout で破棄する。本番には残さない。
|
|
3
|
+
const off = (typeof process !== 'undefined' && process.env && process.env.ABLATE ? process.env.ABLATE : '').split(',');
|
|
4
|
+
export const OFF = {
|
|
5
|
+
A: off.includes('A'), // 文字集合マスク前置フィルタ
|
|
6
|
+
B: off.includes('B'), // scratch 配列再利用
|
|
7
|
+
C: off.includes('C'), // OSA バンド窓 + 早期打ち切り
|
|
8
|
+
E: off.includes('E'), // tier1 positions 遅延
|
|
9
|
+
F: off.includes('F'), // tier-first 転置 + limit 打ち切り
|
|
10
|
+
};
|
|
11
|
+
//# sourceMappingURL=ablate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ablate.js","sourceRoot":"","sources":["../src/ablate.ts"],"names":[],"mappings":"AAAA,qDAAqD;AACrD,oCAAoC;AACpC,MAAM,GAAG,GAAG,CAAC,OAAO,OAAO,KAAK,WAAW,IAAI,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;AACvH,MAAM,CAAC,MAAM,GAAG,GAAG;IACjB,CAAC,EAAE,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,gBAAgB;IACtC,CAAC,EAAE,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,gBAAgB;IACtC,CAAC,EAAE,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,oBAAoB;IAC1C,CAAC,EAAE,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,qBAAqB;IAC3C,CAAC,EAAE,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,6BAA6B;CACpD,CAAC"}
|
package/dist/bigram.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bigram.d.ts","sourceRoot":"","sources":["../src/bigram.ts"],"names":[],"mappings":"AAMA;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAOzD"}
|
package/dist/bigram.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// bigram 集合生成(spec 2.3)。tier 4(構造類似層)の Tversky 類似度に使う。
|
|
2
|
+
// 先頭/末尾を ^ $ でパディングし、語頭・語末の一致を 2-gram として捕捉する。
|
|
3
|
+
const START = '^';
|
|
4
|
+
const END = '$';
|
|
5
|
+
/**
|
|
6
|
+
* kana 文字列の文字 bigram 集合を生成する。
|
|
7
|
+
* 例: 'カミ' → {'^カ', 'カミ', 'ミ$'}。空文字列は {'^$'}。
|
|
8
|
+
*/
|
|
9
|
+
export function generateBigrams(kana) {
|
|
10
|
+
const padded = START + kana + END;
|
|
11
|
+
const set = new Set();
|
|
12
|
+
for (let i = 0; i + 1 < padded.length; i++) {
|
|
13
|
+
set.add(padded.slice(i, i + 2));
|
|
14
|
+
}
|
|
15
|
+
return set;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=bigram.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bigram.js","sourceRoot":"","sources":["../src/bigram.ts"],"names":[],"mappings":"AAAA,uDAAuD;AACvD,+CAA+C;AAE/C,MAAM,KAAK,GAAG,GAAG,CAAC;AAClB,MAAM,GAAG,GAAG,GAAG,CAAC;AAEhB;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,MAAM,MAAM,GAAG,KAAK,GAAG,IAAI,GAAG,GAAG,CAAC;IAClC,MAAM,GAAG,GAAG,IAAI,GAAG,EAAU,CAAC;IAC9B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAC3C,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IAClC,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"charmask.d.ts","sourceRoot":"","sources":["../src/charmask.ts"],"names":[],"mappings":"AAKA,8CAA8C;AAC9C,wBAAgB,QAAQ,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAM1C;AAED,gCAAgC;AAChC,wBAAgB,QAAQ,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAQ1C"}
|
package/dist/charmask.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// 文字集合ビットマスク(前置フィルタ用)。各文字を 31bit のいずれかへハッシュする。
|
|
2
|
+
// 健全性: ハッシュ衝突は「存在すると誤認」方向にしか働かないため、
|
|
3
|
+
// subsequence/編集距離の「不一致で即棄却」判定で誤棄却(false negative)を起こさない。
|
|
4
|
+
// 衝突時は棄却せず本判定(DP)へ落ちるだけなので結果は不変。
|
|
5
|
+
/** 文字列の文字集合を 31bit マスクへ(bit 0..30、符号ビット回避) */
|
|
6
|
+
export function charMask(s) {
|
|
7
|
+
let m = 0;
|
|
8
|
+
for (let i = 0; i < s.length; i++) {
|
|
9
|
+
m |= 1 << (s.charCodeAt(i) % 31);
|
|
10
|
+
}
|
|
11
|
+
return m;
|
|
12
|
+
}
|
|
13
|
+
/** 立っているビット数(≤31bit 前提の素朴実装) */
|
|
14
|
+
export function popcount(x) {
|
|
15
|
+
let c = 0;
|
|
16
|
+
let v = x;
|
|
17
|
+
while (v !== 0) {
|
|
18
|
+
v &= v - 1;
|
|
19
|
+
c++;
|
|
20
|
+
}
|
|
21
|
+
return c;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=charmask.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"charmask.js","sourceRoot":"","sources":["../src/charmask.ts"],"names":[],"mappings":"AAAA,+CAA+C;AAC/C,oCAAoC;AACpC,4DAA4D;AAC5D,iCAAiC;AAEjC,8CAA8C;AAC9C,MAAM,UAAU,QAAQ,CAAC,CAAS;IAChC,IAAI,CAAC,GAAG,CAAC,CAAC;IACV,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAClC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC;IACnC,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC;AAED,gCAAgC;AAChC,MAAM,UAAU,QAAQ,CAAC,CAAS;IAChC,IAAI,CAAC,GAAG,CAAC,CAAC;IACV,IAAI,CAAC,GAAG,CAAC,CAAC;IACV,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACf,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACX,CAAC,EAAE,CAAC;IACN,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC"}
|
package/dist/entry.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type Normalized } from './normalize.js';
|
|
2
|
+
import { type RomajiOption } from './romaji/convert.js';
|
|
3
|
+
import { type RomajiForm } from './syllable.js';
|
|
4
|
+
/** データ側エントリ(1 item 分の事前計算) */
|
|
5
|
+
export interface IndexEntry {
|
|
6
|
+
/** items 内の位置 */
|
|
7
|
+
readonly refIndex: number;
|
|
8
|
+
/** getText 結果(元文字列。matches 逆変換の終点) */
|
|
9
|
+
readonly raw: string;
|
|
10
|
+
/** 正規化カナ形 + normMap */
|
|
11
|
+
readonly kana: Normalized;
|
|
12
|
+
/** かな空間の境界位置(spec 2.6) */
|
|
13
|
+
readonly kanaBoundaries: ReadonlySet<number>;
|
|
14
|
+
/** 各空間の文字集合マスク(前置フィルタ用、空間が無効なら 0) */
|
|
15
|
+
readonly kanaMask: number;
|
|
16
|
+
readonly kunreiMask: number;
|
|
17
|
+
readonly hepburnMask: number;
|
|
18
|
+
/** 訓令式 RomajiForm(romaji オプションで無効時 null) */
|
|
19
|
+
readonly kunrei: RomajiForm | null;
|
|
20
|
+
/** ヘボン式 RomajiForm(romaji オプションで無効時 null) */
|
|
21
|
+
readonly hepburn: RomajiForm | null;
|
|
22
|
+
/** bigram 集合(tier 4 用、遅延生成) */
|
|
23
|
+
bigrams: Set<string> | null;
|
|
24
|
+
}
|
|
25
|
+
/** 1 item からエントリを構築する */
|
|
26
|
+
export declare function buildEntry(raw: string, refIndex: number, romaji: RomajiOption): IndexEntry;
|
|
27
|
+
/** tier 4 実行時に bigram を遅延生成してキャッシュする */
|
|
28
|
+
export declare function ensureBigrams(entry: IndexEntry): Set<string>;
|
|
29
|
+
//# sourceMappingURL=entry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entry.d.ts","sourceRoot":"","sources":["../src/entry.ts"],"names":[],"mappings":"AAGA,OAAO,EAAa,KAAK,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAC5D,OAAO,EAKL,KAAK,YAAY,EAClB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAgC,KAAK,UAAU,EAAE,MAAM,eAAe,CAAC;AAI9E,8BAA8B;AAC9B,MAAM,WAAW,UAAU;IACzB,iBAAiB;IACjB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,sCAAsC;IACtC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,uBAAuB;IACvB,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,0BAA0B;IAC1B,QAAQ,CAAC,cAAc,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC7C,qCAAqC;IACrC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,4CAA4C;IAC5C,QAAQ,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,CAAC;IACnC,6CAA6C;IAC7C,QAAQ,CAAC,OAAO,EAAE,UAAU,GAAG,IAAI,CAAC;IACpC,+BAA+B;IAC/B,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC;CAC7B;AAED,yBAAyB;AACzB,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,GAAG,UAAU,CAgB1F;AAED,wCAAwC;AACxC,wBAAgB,aAAa,CAAC,KAAK,EAAE,UAAU,GAAG,GAAG,CAAC,MAAM,CAAC,CAG5D"}
|
package/dist/entry.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// データ側派生表現(spec 2.3)。Index 構築時に item ごとの正規化・ローマ字を事前計算する。
|
|
2
|
+
// bigram は maxTier:4 が per-call で要否不明のため遅延生成(spec 判断メモ)。
|
|
3
|
+
import { normalize } from './normalize.js';
|
|
4
|
+
import { KUNREI, HEPBURN, kunreiEnabled, hepburnEnabled, } from './romaji/convert.js';
|
|
5
|
+
import { toRomajiForm, kanaBoundaries } from './syllable.js';
|
|
6
|
+
import { generateBigrams } from './bigram.js';
|
|
7
|
+
import { charMask } from './charmask.js';
|
|
8
|
+
/** 1 item からエントリを構築する */
|
|
9
|
+
export function buildEntry(raw, refIndex, romaji) {
|
|
10
|
+
const kana = normalize(raw);
|
|
11
|
+
const kunrei = kunreiEnabled(romaji) ? toRomajiForm(kana.text, KUNREI) : null;
|
|
12
|
+
const hepburn = hepburnEnabled(romaji) ? toRomajiForm(kana.text, HEPBURN) : null;
|
|
13
|
+
return {
|
|
14
|
+
refIndex,
|
|
15
|
+
raw,
|
|
16
|
+
kana,
|
|
17
|
+
kanaBoundaries: kanaBoundaries(kana.text),
|
|
18
|
+
kanaMask: charMask(kana.text),
|
|
19
|
+
kunreiMask: kunrei ? charMask(kunrei.text) : 0,
|
|
20
|
+
hepburnMask: hepburn ? charMask(hepburn.text) : 0,
|
|
21
|
+
kunrei,
|
|
22
|
+
hepburn,
|
|
23
|
+
bigrams: null,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/** tier 4 実行時に bigram を遅延生成してキャッシュする */
|
|
27
|
+
export function ensureBigrams(entry) {
|
|
28
|
+
if (entry.bigrams === null)
|
|
29
|
+
entry.bigrams = generateBigrams(entry.kana.text);
|
|
30
|
+
return entry.bigrams;
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=entry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entry.js","sourceRoot":"","sources":["../src/entry.ts"],"names":[],"mappings":"AAAA,yDAAyD;AACzD,yDAAyD;AAEzD,OAAO,EAAE,SAAS,EAAmB,MAAM,gBAAgB,CAAC;AAC5D,OAAO,EACL,MAAM,EACN,OAAO,EACP,aAAa,EACb,cAAc,GAEf,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,YAAY,EAAE,cAAc,EAAmB,MAAM,eAAe,CAAC;AAC9E,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAwBzC,yBAAyB;AACzB,MAAM,UAAU,UAAU,CAAC,GAAW,EAAE,QAAgB,EAAE,MAAoB;IAC5E,MAAM,IAAI,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;IAC5B,MAAM,MAAM,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAC9E,MAAM,OAAO,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACjF,OAAO;QACL,QAAQ;QACR,GAAG;QACH,IAAI;QACJ,cAAc,EAAE,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC;QACzC,QAAQ,EAAE,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC;QAC7B,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;QAC9C,WAAW,EAAE,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;QACjD,MAAM;QACN,OAAO;QACP,OAAO,EAAE,IAAI;KACd,CAAC;AACJ,CAAC;AAED,wCAAwC;AACxC,MAAM,UAAU,aAAa,CAAC,KAAiB;IAC7C,IAAI,KAAK,CAAC,OAAO,KAAK,IAAI;QAAE,KAAK,CAAC,OAAO,GAAG,eAAe,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC7E,OAAO,KAAK,CAAC,OAAO,CAAC;AACvB,CAAC"}
|
package/dist/expand.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** separatorExpansion オプションの値(spec 3章 / design.md) */
|
|
2
|
+
export type SeparatorConfig = boolean | readonly string[];
|
|
3
|
+
/** true 指定時のデフォルト separator セット */
|
|
4
|
+
export declare const DEFAULT_SEPARATORS: readonly string[];
|
|
5
|
+
/**
|
|
6
|
+
* クエリ raw を展開経路へ。順序は (c) リテラル → (a) 除去 → (b) 境界。
|
|
7
|
+
* - separator を含まない / 無効化時は [raw] のみ
|
|
8
|
+
* - 末尾に separator がある場合は (a) 除去経路を生成しない(入力継続中シグナル、過剰マッチ回避)
|
|
9
|
+
* - 重複・空文字列は除外((c) は raw が空でも保持しない=呼び出し側が空クエリ判定)
|
|
10
|
+
*/
|
|
11
|
+
export declare function expandQuery(raw: string, config: SeparatorConfig): string[];
|
|
12
|
+
//# sourceMappingURL=expand.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"expand.d.ts","sourceRoot":"","sources":["../src/expand.ts"],"names":[],"mappings":"AAKA,sDAAsD;AACtD,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,SAAS,MAAM,EAAE,CAAC;AAE1D,mCAAmC;AACnC,eAAO,MAAM,kBAAkB,EAAE,SAAS,MAAM,EAAyB,CAAC;AAa1E;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,GAAG,MAAM,EAAE,CAgC1E"}
|
package/dist/expand.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// separatorExpansion(spec 3章)。クエリを最大3経路に展開する。
|
|
2
|
+
// 展開は「追加」であって置換ではない。経路の順序は同点タイブレーク順(spec 5章):
|
|
3
|
+
// (c) リテラル経路(常設)> (a) 除去経路 > (b) 境界経路。
|
|
4
|
+
// データ側は展開しない(separator は 2.6 の境界として効く)。
|
|
5
|
+
/** true 指定時のデフォルト separator セット */
|
|
6
|
+
export const DEFAULT_SEPARATORS = ['-', ' ', '・', '_'];
|
|
7
|
+
function resolveSeparators(config) {
|
|
8
|
+
if (config === false || config === undefined)
|
|
9
|
+
return [];
|
|
10
|
+
if (config === true)
|
|
11
|
+
return DEFAULT_SEPARATORS;
|
|
12
|
+
return config;
|
|
13
|
+
}
|
|
14
|
+
/** 文字を正規表現クラス用にエスケープ */
|
|
15
|
+
function escapeForCharClass(ch) {
|
|
16
|
+
return ch.replace(/[\\\]^-]/g, '\\$&');
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* クエリ raw を展開経路へ。順序は (c) リテラル → (a) 除去 → (b) 境界。
|
|
20
|
+
* - separator を含まない / 無効化時は [raw] のみ
|
|
21
|
+
* - 末尾に separator がある場合は (a) 除去経路を生成しない(入力継続中シグナル、過剰マッチ回避)
|
|
22
|
+
* - 重複・空文字列は除外((c) は raw が空でも保持しない=呼び出し側が空クエリ判定)
|
|
23
|
+
*/
|
|
24
|
+
export function expandQuery(raw, config) {
|
|
25
|
+
const separators = resolveSeparators(config);
|
|
26
|
+
if (separators.length === 0)
|
|
27
|
+
return raw === '' ? [] : [raw];
|
|
28
|
+
const klass = separators.map(escapeForCharClass).join('');
|
|
29
|
+
const sepRe = new RegExp(`[${klass}]`, 'g');
|
|
30
|
+
if (!sepRe.test(raw))
|
|
31
|
+
return raw === '' ? [] : [raw];
|
|
32
|
+
const paths = [];
|
|
33
|
+
const seen = new Set();
|
|
34
|
+
const push = (s) => {
|
|
35
|
+
if (s !== '' && !seen.has(s)) {
|
|
36
|
+
seen.add(s);
|
|
37
|
+
paths.push(s);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
// (c) リテラル
|
|
41
|
+
push(raw);
|
|
42
|
+
// (a) 除去(末尾 separator のときはスキップ)
|
|
43
|
+
const lastCp = [...raw].at(-1);
|
|
44
|
+
const endsWithSeparator = separators.includes(lastCp);
|
|
45
|
+
if (!endsWithSeparator) {
|
|
46
|
+
push(raw.replace(sepRe, ''));
|
|
47
|
+
}
|
|
48
|
+
// (b) 境界(separator → 単一スペース)
|
|
49
|
+
push(raw.replace(sepRe, ' '));
|
|
50
|
+
return paths;
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=expand.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"expand.js","sourceRoot":"","sources":["../src/expand.ts"],"names":[],"mappings":"AAAA,8CAA8C;AAC9C,8CAA8C;AAC9C,yCAAyC;AACzC,wCAAwC;AAKxC,mCAAmC;AACnC,MAAM,CAAC,MAAM,kBAAkB,GAAsB,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;AAE1E,SAAS,iBAAiB,CAAC,MAAuB;IAChD,IAAI,MAAM,KAAK,KAAK,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,EAAE,CAAC;IACxD,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,kBAAkB,CAAC;IAC/C,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,wBAAwB;AACxB,SAAS,kBAAkB,CAAC,EAAU;IACpC,OAAO,EAAE,CAAC,OAAO,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;AACzC,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,GAAW,EAAE,MAAuB;IAC9D,MAAM,UAAU,GAAG,iBAAiB,CAAC,MAAM,CAAC,CAAC;IAC7C,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IAE5D,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC1D,MAAM,KAAK,GAAG,IAAI,MAAM,CAAC,IAAI,KAAK,GAAG,EAAE,GAAG,CAAC,CAAC;IAE5C,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IAErD,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,MAAM,IAAI,GAAG,CAAC,CAAS,EAAQ,EAAE;QAC/B,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;YAC7B,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YACZ,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAChB,CAAC;IACH,CAAC,CAAC;IAEF,WAAW;IACX,IAAI,CAAC,GAAG,CAAC,CAAC;IAEV,gCAAgC;IAChC,MAAM,MAAM,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAE,CAAC;IAChC,MAAM,iBAAiB,GAAG,UAAU,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IACtD,IAAI,CAAC,iBAAiB,EAAE,CAAC;QACvB,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC;IAC/B,CAAC;IAED,6BAA6B;IAC7B,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC;IAE9B,OAAO,KAAK,CAAC;AACf,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { IndexOptions, SearchOptions, SearchResult, SearchIndex } from './types.js';
|
|
2
|
+
export type { IndexOptions, SearchOptions, SearchResult, SearchIndex, RomajiOption, SeparatorConfig, } from './types.js';
|
|
3
|
+
export declare function createIndex(items: readonly string[], options?: IndexOptions<string> & SearchOptions): SearchIndex<string>;
|
|
4
|
+
export declare function createIndex<T>(items: readonly T[], options: IndexOptions<T> & SearchOptions & {
|
|
5
|
+
getText: (item: T) => string;
|
|
6
|
+
}): SearchIndex<T>;
|
|
7
|
+
export declare function search(query: string, items: readonly string[], options?: IndexOptions<string> & SearchOptions): SearchResult<string>[];
|
|
8
|
+
export declare function search<T>(query: string, items: readonly T[], options: IndexOptions<T> & SearchOptions & {
|
|
9
|
+
getText: (item: T) => string;
|
|
10
|
+
}): SearchResult<T>[];
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EACV,YAAY,EACZ,aAAa,EACb,YAAY,EACZ,WAAW,EACZ,MAAM,YAAY,CAAC;AAEpB,YAAY,EACV,YAAY,EACZ,aAAa,EACb,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,eAAe,GAChB,MAAM,YAAY,CAAC;AAsBpB,wBAAgB,WAAW,CACzB,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,OAAO,CAAC,EAAE,YAAY,CAAC,MAAM,CAAC,GAAG,aAAa,GAC7C,WAAW,CAAC,MAAM,CAAC,CAAC;AACvB,wBAAgB,WAAW,CAAC,CAAC,EAC3B,KAAK,EAAE,SAAS,CAAC,EAAE,EACnB,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,GAAG,aAAa,GAAG;IAAE,OAAO,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,CAAA;CAAE,GAC1E,WAAW,CAAC,CAAC,CAAC,CAAC;AA4ClB,wBAAgB,MAAM,CACpB,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,OAAO,CAAC,EAAE,YAAY,CAAC,MAAM,CAAC,GAAG,aAAa,GAC7C,YAAY,CAAC,MAAM,CAAC,EAAE,CAAC;AAC1B,wBAAgB,MAAM,CAAC,CAAC,EACtB,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,SAAS,CAAC,EAAE,EACnB,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,GAAG,aAAa,GAAG;IAAE,OAAO,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,CAAA;CAAE,GAC1E,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// yfuzzy 公開 API(design.md シグネチャ)。
|
|
2
|
+
// 単発 search は createIndex の薄いラッパ(spec 1章: 両者は完全等価)。
|
|
3
|
+
import { buildEntry } from './entry.js';
|
|
4
|
+
import { runSearch } from './pipeline.js';
|
|
5
|
+
import { computeMatches } from './matches.js';
|
|
6
|
+
const DEFAULT_MAX_TIER = 3;
|
|
7
|
+
const DEFAULT_MIN_SCORE = 0;
|
|
8
|
+
function resolveConfig(romaji, separatorExpansion, opts) {
|
|
9
|
+
return {
|
|
10
|
+
romaji,
|
|
11
|
+
separatorExpansion,
|
|
12
|
+
maxTier: opts.maxTier ?? DEFAULT_MAX_TIER,
|
|
13
|
+
minScore: opts.minScore ?? DEFAULT_MIN_SCORE,
|
|
14
|
+
limit: opts.limit,
|
|
15
|
+
includeMatches: opts.includeMatches ?? false,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export function createIndex(items, options = {}) {
|
|
19
|
+
const getText = options.getText ?? ((x) => x);
|
|
20
|
+
const romaji = options.romaji ?? 'both';
|
|
21
|
+
const separatorExpansion = options.separatorExpansion ?? false;
|
|
22
|
+
// 構築時に SearchOptions のデフォルトを保持(per-call で上書き可)
|
|
23
|
+
const defaults = {
|
|
24
|
+
limit: options.limit,
|
|
25
|
+
maxTier: options.maxTier,
|
|
26
|
+
minScore: options.minScore,
|
|
27
|
+
includeMatches: options.includeMatches,
|
|
28
|
+
};
|
|
29
|
+
const entries = items.map((item, i) => buildEntry(getText(item), i, romaji));
|
|
30
|
+
return {
|
|
31
|
+
size: items.length,
|
|
32
|
+
search(query, searchOptions = {}) {
|
|
33
|
+
const merged = { ...defaults, ...searchOptions };
|
|
34
|
+
const config = resolveConfig(romaji, separatorExpansion, merged);
|
|
35
|
+
const hits = runSearch(query, entries, config);
|
|
36
|
+
return hits.map((h) => {
|
|
37
|
+
const result = {
|
|
38
|
+
item: items[h.refIndex],
|
|
39
|
+
refIndex: h.refIndex,
|
|
40
|
+
score: h.score,
|
|
41
|
+
tier: h.tier,
|
|
42
|
+
};
|
|
43
|
+
// tier 1/2 のみ matches を付与(tier 3/4 は positions=null → undefined のまま)
|
|
44
|
+
if (config.includeMatches && h.match.positions !== null) {
|
|
45
|
+
result.matches = computeMatches(entries[h.refIndex], h.match);
|
|
46
|
+
}
|
|
47
|
+
return result;
|
|
48
|
+
});
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
export function search(query, items, options = {}) {
|
|
53
|
+
// createIndex が SearchOptions をデフォルト保持するため search(query) で同値
|
|
54
|
+
return createIndex(items, options).search(query);
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,kCAAkC;AAClC,oDAAoD;AAEpD,OAAO,EAAE,UAAU,EAAmB,MAAM,YAAY,CAAC;AACzD,OAAO,EAAE,SAAS,EAAqB,MAAM,eAAe,CAAC;AAC7D,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAiB9C,MAAM,gBAAgB,GAAG,CAAC,CAAC;AAC3B,MAAM,iBAAiB,GAAG,CAAC,CAAC;AAE5B,SAAS,aAAa,CACpB,MAA8B,EAC9B,kBAAsD,EACtD,IAAmB;IAEnB,OAAO;QACL,MAAM;QACN,kBAAkB;QAClB,OAAO,EAAE,IAAI,CAAC,OAAO,IAAI,gBAAgB;QACzC,QAAQ,EAAE,IAAI,CAAC,QAAQ,IAAI,iBAAiB;QAC5C,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,cAAc,EAAE,IAAI,CAAC,cAAc,IAAI,KAAK;KAC7C,CAAC;AACJ,CAAC;AAYD,MAAM,UAAU,WAAW,CACzB,KAAmB,EACnB,UAA2C,EAAE;IAE7C,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,CAAC,CAAC,CAAI,EAAE,EAAE,CAAC,CAAsB,CAAC,CAAC;IACtE,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,MAAM,CAAC;IACxC,MAAM,kBAAkB,GAAG,OAAO,CAAC,kBAAkB,IAAI,KAAK,CAAC;IAE/D,+CAA+C;IAC/C,MAAM,QAAQ,GAAkB;QAC9B,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,cAAc,EAAE,OAAO,CAAC,cAAc;KACvC,CAAC;IAEF,MAAM,OAAO,GAAiB,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;IAE3F,OAAO;QACL,IAAI,EAAE,KAAK,CAAC,MAAM;QAClB,MAAM,CAAC,KAAa,EAAE,gBAA+B,EAAE;YACrD,MAAM,MAAM,GAAkB,EAAE,GAAG,QAAQ,EAAE,GAAG,aAAa,EAAE,CAAC;YAChE,MAAM,MAAM,GAAG,aAAa,CAAC,MAAM,EAAE,kBAAkB,EAAE,MAAM,CAAC,CAAC;YACjE,MAAM,IAAI,GAAG,SAAS,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;YAC/C,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;gBACpB,MAAM,MAAM,GAAoB;oBAC9B,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAE;oBACxB,QAAQ,EAAE,CAAC,CAAC,QAAQ;oBACpB,KAAK,EAAE,CAAC,CAAC,KAAK;oBACd,IAAI,EAAE,CAAC,CAAC,IAAI;iBACb,CAAC;gBACF,qEAAqE;gBACrE,IAAI,MAAM,CAAC,cAAc,IAAI,CAAC,CAAC,KAAK,CAAC,SAAS,KAAK,IAAI,EAAE,CAAC;oBACxD,MAAM,CAAC,OAAO,GAAG,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC;gBACjE,CAAC;gBACD,OAAO,MAAM,CAAC;YAChB,CAAC,CAAC,CAAC;QACL,CAAC;KACF,CAAC;AACJ,CAAC;AAcD,MAAM,UAAU,MAAM,CACpB,KAAa,EACb,KAAmB,EACnB,UAA2C,EAAE;IAE7C,6DAA6D;IAC7D,OAAO,WAAW,CAAC,KAAqB,EAAE,OAAgB,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC5E,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { IndexEntry } from './entry.js';
|
|
2
|
+
import type { TierMatch } from './tiers/types.js';
|
|
3
|
+
/**
|
|
4
|
+
* 採用位置から元文字列上の一致区間を算出する。
|
|
5
|
+
* match.positions が null(tier 3/4)の場合は [] を返す(呼び出し側で undefined 扱い)。
|
|
6
|
+
*/
|
|
7
|
+
export declare function computeMatches(entry: IndexEntry, match: TierMatch): [number, number][];
|
|
8
|
+
//# sourceMappingURL=matches.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"matches.d.ts","sourceRoot":"","sources":["../src/matches.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AA+BlD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,SAAS,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAyBtF"}
|
package/dist/matches.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// matches 逆マップ(spec 6章)。tier 1/2 の採用位置(照合空間上)を元文字列上の
|
|
2
|
+
// 半開区間 [start, end) へ2段変換し、昇順・隣接/重複マージして返す。
|
|
3
|
+
// 1. romaji 位置 → kana 位置(syllableMap 経由。踏んだ音節の kana 全体を含める)
|
|
4
|
+
// 2. kana 位置 → 元文字列位置(normMap 経由。n:1 正規化は元区間全体)
|
|
5
|
+
// tier 3/4 は positions=null のため呼ばれない(呼ばれても [] を返す)。
|
|
6
|
+
/** romaji 位置 p を含む音節の kana 位置(音節全体)を返す */
|
|
7
|
+
function romajiPosToKana(p, syllables) {
|
|
8
|
+
for (const s of syllables) {
|
|
9
|
+
if (p >= s.romajiStart && p < s.romajiStart + s.romajiLen) {
|
|
10
|
+
const out = [];
|
|
11
|
+
for (let k = 0; k < s.kanaLen; k++)
|
|
12
|
+
out.push(s.kanaStart + k);
|
|
13
|
+
return out;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return [];
|
|
17
|
+
}
|
|
18
|
+
/** spans を昇順ソートし、重複・隣接(半開区間で接する)を連続区間にマージ */
|
|
19
|
+
function mergeSpans(spans) {
|
|
20
|
+
if (spans.length === 0)
|
|
21
|
+
return [];
|
|
22
|
+
spans.sort((a, b) => a[0] - b[0] || a[1] - b[1]);
|
|
23
|
+
const merged = [];
|
|
24
|
+
for (const [s, e] of spans) {
|
|
25
|
+
const last = merged[merged.length - 1];
|
|
26
|
+
if (last && s <= last[1]) {
|
|
27
|
+
if (e > last[1])
|
|
28
|
+
last[1] = e;
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
merged.push([s, e]);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return merged;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* 採用位置から元文字列上の一致区間を算出する。
|
|
38
|
+
* match.positions が null(tier 3/4)の場合は [] を返す(呼び出し側で undefined 扱い)。
|
|
39
|
+
*/
|
|
40
|
+
export function computeMatches(entry, match) {
|
|
41
|
+
if (match.positions === null)
|
|
42
|
+
return [];
|
|
43
|
+
// 1. 照合空間の位置 → kana 位置
|
|
44
|
+
const kanaPositions = new Set();
|
|
45
|
+
if (match.space === 'kana') {
|
|
46
|
+
for (const p of match.positions)
|
|
47
|
+
kanaPositions.add(p);
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
const form = match.space === 'kunrei' ? entry.kunrei : entry.hepburn;
|
|
51
|
+
if (form) {
|
|
52
|
+
for (const p of match.positions) {
|
|
53
|
+
for (const kp of romajiPosToKana(p, form.syllables))
|
|
54
|
+
kanaPositions.add(kp);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// 2. kana 位置 → 元文字列の span(normMap)
|
|
59
|
+
const map = entry.kana.map;
|
|
60
|
+
const spans = [];
|
|
61
|
+
for (const kp of kanaPositions) {
|
|
62
|
+
const span = map[kp];
|
|
63
|
+
if (span)
|
|
64
|
+
spans.push([span[0], span[1]]);
|
|
65
|
+
}
|
|
66
|
+
return mergeSpans(spans);
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=matches.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"matches.js","sourceRoot":"","sources":["../src/matches.ts"],"names":[],"mappings":"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"}
|