├── .github
├── profile
│ ├── split-lang-banner.svg
│ └── split-lang-logo.svg
└── workflows
│ └── publish.yml
├── .gitignore
├── LICENSE
├── README.md
├── docs
├── ja
│ └── README.md
└── zh
│ └── README.md
├── event_log.txt
├── setup.py
├── split-lang-benchmark.ipynb
├── split-lang-demo.ipynb
├── split_lang
├── __init__.py
├── config.py
├── detect_lang
│ ├── __init__.py
│ └── detector.py
├── model.py
└── split
│ ├── __init__.py
│ ├── splitter.py
│ └── utils.py
└── tests
├── __init__.py
├── data
├── __init__.py
├── correct_split.txt
├── correct_split_merge_punc.txt
├── novel_1.txt
└── test_data.py
├── split_acc.py
├── test_config.py
├── test_split.py
└── test_split_to_substrings.py
/.github/profile/split-lang-banner.svg:
--------------------------------------------------------------------------------
1 |
49 |
--------------------------------------------------------------------------------
/.github/profile/split-lang-logo.svg:
--------------------------------------------------------------------------------
1 |
15 |
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | name: Publish Python Package
2 |
3 | on:
4 | push:
5 | tags:
6 | - "v*" # 仅在推送标签时触发
7 |
8 | jobs:
9 | publish:
10 | runs-on: ubuntu-latest
11 |
12 | steps:
13 | - name: Checkout code
14 | uses: actions/checkout@v2
15 |
16 | - name: Set up Python
17 | uses: actions/setup-python@v2
18 | with:
19 | python-version: "3.9" # 选择 Python 版本
20 |
21 | - name: Install dependencies
22 | run: |
23 | python -m pip install --upgrade pip
24 | pip install setuptools wheel twine
25 |
26 | - name: Build package
27 | run: python setup.py sdist bdist_wheel
28 |
29 | - name: Publish package to PyPI
30 | env:
31 | TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
32 | TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
33 | run: twine upload dist/*
34 |
35 | - name: Clean up dist folder
36 | run: rm -rf dist
37 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | __pycache__
2 | build/
3 | dist/
4 | split_lang.egg-info/
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 DoodleBear
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.
22 |
--------------------------------------------------------------------------------
/event_log.txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DoodleBears/split-lang/428c92de255530c3b83e02999abb31241c282084/event_log.txt
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from os import path
2 |
3 | from setuptools import find_packages, setup
4 |
5 |
6 | def packagefile(*relpath):
7 | return path.join(path.dirname(__file__), *relpath)
8 |
9 |
10 | def read(*relpath):
11 | with open(packagefile(*relpath), encoding="utf-8") as f:
12 | return f.read()
13 |
14 |
15 | setup(
16 | name="split_lang",
17 | version="2.1.0",
18 | description="A package for splitting text by languages through concatenating over split substrings based on their language",
19 | long_description=read("README.md"),
20 | long_description_content_type="text/markdown",
21 | url="https://github.com/DoodleBears/langsplit",
22 | author="DoodleBear",
23 | author_email="yangmufeng233@gmail.com",
24 | license="MIT",
25 | packages=find_packages(),
26 | install_requires=[
27 | "fast_langdetect",
28 | "pydantic",
29 | "budoux",
30 | ],
31 | classifiers=[
32 | "Programming Language :: Python :: 3",
33 | "License :: OSI Approved :: MIT License",
34 | "Operating System :: OS Independent",
35 | ],
36 | python_requires=">=3.9",
37 | )
38 |
--------------------------------------------------------------------------------
/split-lang-benchmark.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "## Import Language Detection Package"
8 | ]
9 | },
10 | {
11 | "cell_type": "code",
12 | "execution_count": 80,
13 | "metadata": {},
14 | "outputs": [],
15 | "source": [
16 | "import langdetect\n",
17 | "import fast_langdetect\n",
18 | "from lingua import Language, LanguageDetectorBuilder"
19 | ]
20 | },
21 | {
22 | "cell_type": "markdown",
23 | "metadata": {},
24 | "source": [
25 | "## Import Text Split Package"
26 | ]
27 | },
28 | {
29 | "cell_type": "code",
30 | "execution_count": 81,
31 | "metadata": {},
32 | "outputs": [],
33 | "source": [
34 | "from wtpsplit import SaT, WtP\n",
35 | "sat = SaT(\"sat-1l-sm\")\n",
36 | "sat.half().to(\"cuda\")\n",
37 | "wtp = WtP(\"wtp-bert-mini\")\n",
38 | "import budoux"
39 | ]
40 | },
41 | {
42 | "cell_type": "code",
43 | "execution_count": 82,
44 | "metadata": {},
45 | "outputs": [],
46 | "source": [
47 | "text = \"你喜欢看アニメ吗\""
48 | ]
49 | },
50 | {
51 | "cell_type": "code",
52 | "execution_count": 83,
53 | "metadata": {},
54 | "outputs": [
55 | {
56 | "name": "stderr",
57 | "output_type": "stream",
58 | "text": [
59 | "100%|██████████| 1/1 [00:00<00:00, 124.43it/s]\n"
60 | ]
61 | },
62 | {
63 | "data": {
64 | "text/plain": [
65 | "['你', '喜欢看', 'アニメ', '吗']"
66 | ]
67 | },
68 | "execution_count": 83,
69 | "metadata": {},
70 | "output_type": "execute_result"
71 | }
72 | ],
73 | "source": [
74 | "wtp.split(text_or_texts=text, threshold=4e-5, verbose=True)"
75 | ]
76 | },
77 | {
78 | "cell_type": "code",
79 | "execution_count": 84,
80 | "metadata": {},
81 | "outputs": [],
82 | "source": [
83 | "texts_with_digit = [\n",
84 | " \"你喜欢看アニメ吗?\",\n",
85 | " \"衬衫的价格是9.15便士\",\n",
86 | " \"衬衫的价格是233亿元\",\n",
87 | " \"衬衫的价格是233亿元人民币\",\n",
88 | "]\n",
89 | "\n",
90 | "texts_zh_jp_ko_en = [\n",
91 | " \"我是 VGroupChatBot,一个旨在支持多人通信的助手,通过可视化消息来帮助团队成员更好地交流。我可以帮助团队成员更好地整理和共享信息,特别是在讨论、会议和Brainstorming等情况下。你好我的名字是西野くまですmy name is bob很高兴认识你どうぞよろしくお願いいたします「こんにちは」是什么意思。\",\n",
92 | " \"你好,我的名字是西野くまです。I am from Tokyo, 日本の首都。今天的天气非常好,sky is clear and sunny。おはようございます、皆さん!我们一起来学习吧。Learning languages can be fun and exciting。昨日はとても忙しかったので、今日は少しリラックスしたいです。Let's take a break and enjoy some coffee。中文、日本語、and English are three distinct languages, each with its own unique charm。希望我们能一起进步,一起成长。Let's keep studying and improving our language skills together. ありがとう!\",\n",
93 | " \"你好,今日はどこへ行きますか?\",\n",
94 | " \"你好今日はどこへ行きますか?\",\n",
95 | " \"我的名字是田中さんです。\",\n",
96 | " \"我喜欢吃寿司和拉面おいしいです。\",\n",
97 | " \"今天の天気はとてもいいですね。\",\n",
98 | " \"我在学习日本語少し難しいです。\",\n",
99 | " \"日语真是おもしろい啊\",\n",
100 | " \"你喜欢看アニメ吗?\",\n",
101 | " \"我想去日本旅行、特に京都に行きたいです。\",\n",
102 | " \"昨天見た映画はとても感動的でした。我朋友是日本人彼はとても優しいです。\",\n",
103 | " \"我们一起去カラオケ吧、楽しそうです。\",\n",
104 | " \"我的家在北京、でも、仕事で東京に住んでいます。\",\n",
105 | " \"我在学做日本料理、日本料理を作るのを習っています。\",\n",
106 | " \"你会说几种语言、何ヶ国語話せますか?\",\n",
107 | " \"我昨天看了一本书、その本はとても面白かったです。\",\n",
108 | " \"你最近好吗、最近どうですか?\",\n",
109 | " \"我在学做日本料理와 한국 요리、日本料理を作るのを習っています。\",\n",
110 | " \"你会说几种语言、何ヶ国語話せますか?몇 개 언어를 할 수 있어요?\",\n",
111 | " \"我昨天看了一本书、その本はとても面白かったです。어제 책을 읽었는데, 정말 재미있었어요。\",\n",
112 | " \"我们一起去逛街와 쇼핑、買い物に行きましょう。쇼핑하러 가요。\",\n",
113 | " \"你最近好吗、最近どうですか?요즘 어떻게 지내요?\",\n",
114 | "]\n",
115 | "\n",
116 | "texts_zh_jp = [\n",
117 | " \"你好今日はどこへ行きますか\",\n",
118 | " \"我的名字是田中さんです\",\n",
119 | " \"我喜欢吃寿司和拉面おいしいです\",\n",
120 | " \"今天の天気はとてもいいですね\",\n",
121 | " \"我在学习日本語少し難しいです\",\n",
122 | " \"日语真是おもしろい啊\",\n",
123 | " \"你喜欢看アニメ吗\",\n",
124 | " \"我想去日本旅行特に京都に行きたいです\",\n",
125 | " \"昨天見た映画はとても感動的でした\",\n",
126 | " \"我朋友是日本人彼はとても優しいです\",\n",
127 | " \"我们一起去カラオケ吧\",\n",
128 | " \"我的家在北京でも仕事で東京に住んでいます\",\n",
129 | " \"我的名字是西野くまです\",\n",
130 | " \"我的名字是西野くまですよろしくお願いいたします\",\n",
131 | " \"好吃美味しい上手い\",\n",
132 | " \"我给你送的手紙\",\n",
133 | " \"真是面白い\",\n",
134 | " \"春の花香り\",\n",
135 | " \"何ヶ国語話せますか\",\n",
136 | " \"今晚どこに行きます\"\n",
137 | "]\n",
138 | "\n",
139 | "texts_de_fr_en = [\n",
140 | " \"Ich liebe Paris, c'est une belle ville, and the food is amazing!\",\n",
141 | " \"Berlin ist wunderbar, je veux y retourner, and explore more.\",\n",
142 | " \"Bonjour, wie geht's dir today?\",\n",
143 | " \"Die Musik hier ist fantastisch, la musique est superbe, and I enjoy it a lot.\",\n",
144 | " \"Guten Morgen, je t'aime, have a great day!\",\n",
145 | " \"Das Wetter ist heute schön, il fait beau aujourd'hui, and it's perfect for a walk.\",\n",
146 | " \"Ich mag dieses Buch, ce livre est intéressant, and it has a great story.\",\n",
147 | " \"Vielen Dank, merci beaucoup, for your help.\",\n",
148 | " \"Wir reisen nach Deutschland, nous voyageons en Allemagne, and we are excited.\",\n",
149 | " \"Ich bin müde, je suis fatigué, and I need some rest.\",\n",
150 | " \"Ich liebe Paris c'est une belle ville and the food is amazing!\",\n",
151 | " \"Berlin ist wunderbar je veux y retourner and explore more.\",\n",
152 | " \"Bonjour wie geht's dir today?\",\n",
153 | " \"Die Musik hier ist fantastisch la musique est superbe and I enjoy it a lot.\",\n",
154 | " \"Guten Morgen je t'aime have a great day!\",\n",
155 | " \"Das Wetter ist heute schön il fait beau aujourd'hui and it's perfect for a walk.\",\n",
156 | " \"Ich mag dieses Buch ce livre est intéressant and it has a great story.\",\n",
157 | " \"Vielen Dank merci beaucoup for your help.\",\n",
158 | " \"Wir reisen nach Deutschland nous voyageons en Allemagne and we are excited.\",\n",
159 | " \"Ich bin müde je suis fatigué and I need some rest.\",\n",
160 | "]\n",
161 | "\n",
162 | "texts = texts_zh_jp_ko_en + texts_de_fr_en + texts_with_digit"
163 | ]
164 | },
165 | {
166 | "cell_type": "markdown",
167 | "metadata": {},
168 | "source": [
169 | "## Simple Rule-Based judge"
170 | ]
171 | },
172 | {
173 | "cell_type": "code",
174 | "execution_count": 85,
175 | "metadata": {},
176 | "outputs": [],
177 | "source": [
178 | "import re\n",
179 | "\n",
180 | "chinese_char_pattern = re.compile(r\"[\\u4e00-\\u9fff]\")\n",
181 | "hangul_pattern = re.compile(r\"[\\uac00-\\ud7af]\")\n",
182 | "hiragana_pattern = re.compile(r\"[\\u3040-\\u309f]\")\n",
183 | "katakana_pattern = re.compile(r\"[\\u30a0-\\u30ff]\")\n",
184 | "\n",
185 | "\n",
186 | "def contains_chinese_char(text: str):\n",
187 | " return bool(chinese_char_pattern.search(text))\n",
188 | "\n",
189 | "\n",
190 | "def _contains_hiragana(text: str):\n",
191 | " return bool(hiragana_pattern.search(text))\n",
192 | "\n",
193 | "\n",
194 | "def _contains_katakana(text: str):\n",
195 | " return bool(katakana_pattern.search(text))\n",
196 | "\n",
197 | "\n",
198 | "def contains_ja(text):\n",
199 | " if (\n",
200 | " _contains_hiragana(text)\n",
201 | " or _contains_katakana(text)\n",
202 | " ):\n",
203 | " return True\n",
204 | " return False"
205 | ]
206 | },
207 | {
208 | "cell_type": "markdown",
209 | "metadata": {},
210 | "source": [
211 | "## Try different split logic"
212 | ]
213 | },
214 | {
215 | "cell_type": "code",
216 | "execution_count": 86,
217 | "metadata": {},
218 | "outputs": [
219 | {
220 | "name": "stdout",
221 | "output_type": "stream",
222 | "text": [
223 | "jp_budoux_parser----------\n",
224 | "['你好', '今日は', 'どこへ', '行きますか']\n",
225 | "['我的名字是田中さんです']\n",
226 | "['我喜欢吃寿司和拉面', 'おいしいです']\n",
227 | "['今天の', '天気は', 'とても', 'いいですね']\n",
228 | "['我在学习日本語少し', '難しいです']\n",
229 | "['日语真是おもしろい', '啊']\n",
230 | "['你喜欢看アニメ吗']\n",
231 | "['我想去日本旅行特に', '京都に', '行きたいです']\n",
232 | "['昨天見た', '映画は', 'とても', '感動的でした']\n",
233 | "['我朋友是日本人', '彼は', 'とても', '優しいです']\n",
234 | "['我们一起去カラオケ吧']\n",
235 | "['我的家在北京でも', '仕事で', '東京に', '住んでいます']\n",
236 | "['我的名字是西野くまです']\n",
237 | "['我的名字是西野くまですよろしく', 'お願い', 'いたします']\n",
238 | "['好吃美味しい', '上手い']\n",
239 | "['我给你送的手紙']\n",
240 | "['真是面白い']\n",
241 | "['春の', '花香り']\n",
242 | "['何ヶ国語話せますか']\n",
243 | "['今晚どこに', '行きます']\n",
244 | "----------jp_budoux_parser\n",
245 | "[['你好', '今日は', 'どこへ', '行きますか'], ['我的名字是田中さんです'], ['我喜欢吃寿司和拉面', 'おいしいです'], ['今天の', '天気は', 'とても', 'いいですね'], ['我在学习日本語少し', '難しいです'], ['日语真是おもしろい', '啊'], ['你喜欢看アニメ吗'], ['我想去日本旅行特に', '京都に', '行きたいです'], ['昨天見た', '映画は', 'とても', '感動的でした'], ['我朋友是日本人', '彼は', 'とても', '優しいです'], ['我们一起去カラオケ吧'], ['我的家在北京でも', '仕事で', '東京に', '住んでいます'], ['我的名字是西野くまです'], ['我的名字是西野くまですよろしく', 'お願い', 'いたします'], ['好吃美味しい', '上手い'], ['我给你送的手紙'], ['真是面白い'], ['春の', '花香り'], ['何ヶ国語話せますか'], ['今晚どこに', '行きます']]\n",
246 | "----------jp_budoux_parser+zh_budoux_parser\n",
247 | "['你', '好', '今日', 'は', 'どこへ', '行きますか']\n",
248 | "['我', '的', '名字', '是', '田', '中', 'さんです']\n",
249 | "['我', '喜欢', '吃', '寿司', '和', '拉面', 'おいしいです']\n",
250 | "['今天', 'の', '天', '気は', 'とても', 'いいですね']\n",
251 | "['我', '在', '学习', '日本', '語少', 'し', '難しいです']\n",
252 | "['日', '语真', '是', 'おも', 'しろい', '啊']\n",
253 | "['你', '喜欢', '看', 'アニメ', '吗']\n",
254 | "['我', '想', '去', '日本', '旅行', '特に', '京', '都', 'に', '行きたいです']\n",
255 | "['昨天', '見た', '映', '画', 'は', 'とても', '感動', '的', 'でし', 'た']\n",
256 | "['我', '朋友', '是', '日本', '人', '彼は', 'とても', '優しいです']\n",
257 | "['我们', '一起', '去', 'カラオケ', '吧']\n",
258 | "['我', '的', '家', '在', '北京', 'でも', '仕事で', '東京', 'に', '住ん', 'でいます']\n",
259 | "['我', '的', '名字', '是', '西野', 'くまです']\n",
260 | "['我', '的', '名字', '是', '西野', 'くまですよろしく', 'お願い', 'いたします']\n",
261 | "['好', '吃', '美味', 'しい', '上', '手い']\n",
262 | "['我', '给', '你', '送', '的', '手紙']\n",
263 | "['真', '是', '面白い']\n",
264 | "['春の', '花香り']\n",
265 | "['何', 'ヶ国', '語話', 'せますか']\n",
266 | "['今晚どこに', '行きます']\n",
267 | "[['你', '好', '今日', 'は', 'どこへ', '行きますか'], ['我', '的', '名字', '是', '田', '中', 'さんです'], ['我', '喜欢', '吃', '寿司', '和', '拉面', 'おいしいです'], ['今天', 'の', '天', '気は', 'とても', 'いいですね'], ['我', '在', '学习', '日本', '語少', 'し', '難しいです'], ['日', '语真', '是', 'おも', 'しろい', '啊'], ['你', '喜欢', '看', 'アニメ', '吗'], ['我', '想', '去', '日本', '旅行', '特に', '京', '都', 'に', '行きたいです'], ['昨天', '見た', '映', '画', 'は', 'とても', '感動', '的', 'でし', 'た'], ['我', '朋友', '是', '日本', '人', '彼は', 'とても', '優しいです'], ['我们', '一起', '去', 'カラオケ', '吧'], ['我', '的', '家', '在', '北京', 'でも', '仕事で', '東京', 'に', '住ん', 'でいます'], ['我', '的', '名字', '是', '西野', 'くまです'], ['我', '的', '名字', '是', '西野', 'くまですよろしく', 'お願い', 'いたします'], ['好', '吃', '美味', 'しい', '上', '手い'], ['我', '给', '你', '送', '的', '手紙'], ['真', '是', '面白い'], ['春の', '花香り'], ['何', 'ヶ国', '語話', 'せますか'], ['今晚どこに', '行きます']]\n",
268 | "----------jp_budoux_parser+zh_budoux_parser+combine single to left\n",
269 | "['你好', '今日', 'はどこへ行きますか']\n",
270 | "['我的', '名字是田中', 'さんです']\n",
271 | "['我喜欢吃', '寿司和', '拉面', 'おいしいです']\n",
272 | "['今天', 'の', '天', '気はとてもいいですね']\n",
273 | "['我在', '学习', '日本', '語少', 'し難しいです']\n",
274 | "['日语真是', 'おもしろい', '啊']\n",
275 | "['你喜欢看', 'アニメ', '吗']\n",
276 | "['我想去', '日本', '旅行', '特に', '京都', 'に行きたいです']\n",
277 | "['昨天', '見た', '映画', 'はとても', '感動的', 'でした']\n",
278 | "['我朋友是', '日本人', '彼はとても優しいです']\n",
279 | "['我们', '一起去', 'カラオケ', '吧']\n",
280 | "['我的家在', '北京', 'でも仕事で', '東京', 'に住んでいます']\n",
281 | "['我的', '名字是', '西野', 'くまです']\n",
282 | "['我的', '名字是', '西野', 'くまですよろしくお願いいたします']\n",
283 | "['好吃', '美味', 'しい', '上', '手い']\n",
284 | "['我给你送的', '手紙']\n",
285 | "['真是', '面白い']\n",
286 | "['春の花香り']\n",
287 | "['何ヶ国', '語話', 'せますか']\n",
288 | "['今晚どこに行きます']\n",
289 | "[['你好', '今日', 'はどこへ行きますか'], ['我的', '名字是田中', 'さんです'], ['我喜欢吃', '寿司和', '拉面', 'おいしいです'], ['今天', 'の', '天', '気はとてもいいですね'], ['我在', '学习', '日本', '語少', 'し難しいです'], ['日语真是', 'おもしろい', '啊'], ['你喜欢看', 'アニメ', '吗'], ['我想去', '日本', '旅行', '特に', '京都', 'に行きたいです'], ['昨天', '見た', '映画', 'はとても', '感動的', 'でした'], ['我朋友是', '日本人', '彼はとても優しいです'], ['我们', '一起去', 'カラオケ', '吧'], ['我的家在', '北京', 'でも仕事で', '東京', 'に住んでいます'], ['我的', '名字是', '西野', 'くまです'], ['我的', '名字是', '西野', 'くまですよろしくお願いいたします'], ['好吃', '美味', 'しい', '上', '手い'], ['我给你送的', '手紙'], ['真是', '面白い'], ['春の花香り'], ['何ヶ国', '語話', 'せますか'], ['今晚どこに行きます']]\n"
290 | ]
291 | }
292 | ],
293 | "source": [
294 | "from typing import List\n",
295 | "\n",
296 | "\n",
297 | "zh_budoux_parser = budoux.load_default_simplified_chinese_parser()\n",
298 | "zh_tc_budoux_parser = budoux.load_default_traditional_chinese_parser()\n",
299 | "jp_budoux_parser = budoux.load_default_japanese_parser()\n",
300 | "\n",
301 | "# print(\"zh_budoux_parser----------\")\n",
302 | "# for text in texts_zh_jp:\n",
303 | "# print(zh_budoux_parser.parse(text))\n",
304 | " \n",
305 | "# print(\"zh_tc_budoux_parser----------\")\n",
306 | "# for text in texts_zh_jp:\n",
307 | "# print(zh_tc_budoux_parser.parse(text))\n",
308 | " \n",
309 | "print(\"jp_budoux_parser----------\")\n",
310 | "for text in texts_zh_jp:\n",
311 | " print(jp_budoux_parser.parse(text))\n",
312 | "\n",
313 | "\n",
314 | "print(\"----------jp_budoux_parser\")\n",
315 | "\n",
316 | "splitted_texts_jp = []\n",
317 | "for text in texts_zh_jp:\n",
318 | " jp_split_text = jp_budoux_parser.parse(text)\n",
319 | " splitted_texts_jp.append(jp_split_text)\n",
320 | "print(splitted_texts_jp)\n",
321 | "print(\"----------jp_budoux_parser+zh_budoux_parser\")\n",
322 | "\n",
323 | "splitted_texts_zh_jp = []\n",
324 | "for substrings in splitted_texts_jp:\n",
325 | " words = []\n",
326 | " for substring in substrings:\n",
327 | " words.extend(zh_budoux_parser.parse(substring))\n",
328 | " print(words)\n",
329 | " splitted_texts_zh_jp.append(words)\n",
330 | "print(splitted_texts_zh_jp)\n",
331 | "\n",
332 | "print(\"----------jp_budoux_parser+zh_budoux_parser+combine single to left\")\n",
333 | "pre_split_texts:List[List[str]] = []\n",
334 | "for words in splitted_texts_zh_jp:\n",
335 | " new_words = [words[0]]\n",
336 | " for sub_text in words[1:]:\n",
337 | " is_left_ja = contains_ja(new_words[-1])\n",
338 | " is_cur_ja = contains_ja(sub_text)\n",
339 | " is_both_same_lang = is_left_ja == is_cur_ja\n",
340 | " is_both_ja = is_left_ja == True and is_both_same_lang\n",
341 | " is_both_zh = is_left_ja == False and is_both_same_lang\n",
342 | " if is_both_ja: # both substring is katakana or hiragana, then concat\n",
343 | " new_words[-1] += sub_text\n",
344 | " elif is_both_zh and len(sub_text) == 1:\n",
345 | " # NOTE: both substring is full Chinese character, and current one is only one character\n",
346 | " # NOTE: 90% is because we first use ja_parser then zh_parser (from `budoux`)\n",
347 | " # NOTE: Since kanji in Japanese usually not appear by them self, Single character is CAUSED BY zh_parser \n",
348 | " # NOTE: So we let single character concat together, if both substring did not contain kana\n",
349 | " new_words[-1] += sub_text\n",
350 | " else:\n",
351 | " new_words.append(sub_text)\n",
352 | " \n",
353 | " if len(new_words) >= 2 and len(new_words[0]) == 1:\n",
354 | " new_words[1] = new_words[0] + new_words[1]\n",
355 | " new_words = new_words[1:]\n",
356 | " pre_split_texts.append(new_words) \n",
357 | " print(new_words) \n",
358 | "print(pre_split_texts)"
359 | ]
360 | },
361 | {
362 | "cell_type": "code",
363 | "execution_count": 87,
364 | "metadata": {},
365 | "outputs": [],
366 | "source": [
367 | "from lingua import Language, LanguageDetectorBuilder\n",
368 | "all_detector = (\n",
369 | " LanguageDetectorBuilder.from_all_languages()\n",
370 | " .with_preloaded_language_models()\n",
371 | " .build()\n",
372 | ")"
373 | ]
374 | },
375 | {
376 | "cell_type": "code",
377 | "execution_count": 88,
378 | "metadata": {},
379 | "outputs": [
380 | {
381 | "name": "stdout",
382 | "output_type": "stream",
383 | "text": [
384 | "zh:你好|ja:今日|ja:はどこへ行きますか|\n",
385 | "zh:我的|zh:名字是田中|ja:さんです|\n",
386 | "zh:我喜欢吃|ja:寿司和|zh:拉面|ja:おいしいです|\n",
387 | "zh:今天|ja:の|zh:天|ja:気はとてもいいですね|\n",
388 | "zh:我在|zh:学习|ja:日本|zh:語少|ja:し難しいです|\n",
389 | "zh:日语真是|ja:おもしろい|zh:啊|\n",
390 | "zh:你喜欢看|ja:アニメ|zh:吗|\n",
391 | "zh:我想去|ja:日本|ja:旅行|ja:特に|ja:京都|ja:に行きたいです|\n",
392 | "zh:昨天|ja:見た|ja:映画|ja:はとても|zh:感動的|ja:でした|\n",
393 | "zh:我朋友是|zh:日本人|ja:彼はとても優しいです|\n",
394 | "zh:我们|zh:一起去|ja:カラオケ|zh:吧|\n",
395 | "zh:我的家在|ja:北京|ja:でも仕事で|ja:東京|ja:に住んでいます|\n",
396 | "zh:我的|zh:名字是|ja:西野|ja:くまです|\n",
397 | "zh:我的|zh:名字是|ja:西野|ja:くまですよろしくお願いいたします|\n",
398 | "zh:好吃|zh:美味|ja:しい|ja:上|ja:手い|\n",
399 | "zh:我给你送的|ja:手紙|\n",
400 | "zh:真是|ja:面白い|\n",
401 | "ja:春の花香り|\n",
402 | "zh:何ヶ国|ja:語話|ja:せますか|\n",
403 | "ja:今晚どこに行きます|\n"
404 | ]
405 | }
406 | ],
407 | "source": [
408 | "from langdetect.lang_detect_exception import LangDetectException\n",
409 | "\n",
410 | "def lingua_lang_detect_all(text: str) -> str:\n",
411 | " language: Language | None = all_detector.detect_language_of(text=text)\n",
412 | " if language is None:\n",
413 | " return \"x\"\n",
414 | " return language.iso_code_639_1.name.lower()\n",
415 | "\n",
416 | "def fast_lang_detect(text: str) -> str:\n",
417 | " result = str(fast_langdetect.detect(text, low_memory=False)[\"lang\"])\n",
418 | " result = result.lower()\n",
419 | " return result\n",
420 | "\n",
421 | "def lang_detect(text: str) -> str:\n",
422 | " try:\n",
423 | " result = str(langdetect.detect(text))\n",
424 | " result = result.lower()\n",
425 | " return result\n",
426 | " except LangDetectException as e:\n",
427 | " return \"zh\"\n",
428 | " except Exception as e:\n",
429 | " pass\n",
430 | " return \"x\"\n",
431 | "\n",
432 | "\n",
433 | "for substrings in pre_split_texts:\n",
434 | " for substring in substrings:\n",
435 | " # lang = lingua_lang_detect_all(substring)\n",
436 | " lang = fast_lang_detect(substring)\n",
437 | " \n",
438 | " print(f\"{lang}:{substring}\",end='|')\n",
439 | " print()"
440 | ]
441 | },
442 | {
443 | "cell_type": "markdown",
444 | "metadata": {},
445 | "source": [
446 | "## Difference between split by ` ` (space) and by `wtpsplit`"
447 | ]
448 | },
449 | {
450 | "cell_type": "code",
451 | "execution_count": 116,
452 | "metadata": {},
453 | "outputs": [
454 | {
455 | "name": "stderr",
456 | "output_type": "stream",
457 | "text": [
458 | "c:\\Users\\admin\\.conda\\envs\\melotts\\lib\\site-packages\\wtpsplit\\__init__.py:45: DeprecationWarning: You are using WtP, the old sentence segmentation model. It is highly encouraged to use SaT instead due to strongly improved performance and efficiency. See https://github.com/segment-any-text/wtpsplit for more info. To ignore this warning, set ignore_legacy_warning=True.\n",
459 | " warnings.warn(\n"
460 | ]
461 | },
462 | {
463 | "name": "stdout",
464 | "output_type": "stream",
465 | "text": [
466 | "['Ich ', 'bin müde ', 'je suis fatigué ', 'and ', 'I ', 'need some rest']\n",
467 | "\n",
468 | "0.8507270812988281\n",
469 | "['Ich', 'bin', 'müde', 'je', 'suis', 'fatigué', 'and', 'I', 'need', 'some', 'rest']\n",
470 | "Ich: ['de:1.0']\n",
471 | "\n",
472 | "bin: ['en:0.394521027803421', 'id:0.21292246878147125', 'tr:0.15927168726921082', 'ms:0.09677533060312271', 'eo:0.030221346765756607', 'jv:0.023466553539037704', 'sq:0.013604077510535717', 'sv:0.012493844144046307']\n",
473 | "\n",
474 | "müde: ['de:0.9626638293266296', 'tr:0.026752416044473648']\n",
475 | "\n",
476 | "je: ['sr:0.8350609540939331', 'fr:0.15938909351825714']\n",
477 | "\n",
478 | "suis: ['fr:0.9970543384552002']\n",
479 | "\n",
480 | "fatigué: ['fr:0.7745229601860046', 'es:0.14570455253124237', 'nl:0.03426196426153183', 'de:0.014598727226257324']\n",
481 | "\n",
482 | "and: ['en:0.9983668923377991']\n",
483 | "\n",
484 | "I: ['en:0.9979130625724792']\n",
485 | "\n",
486 | "need: ['en:0.9885499477386475']\n",
487 | "\n",
488 | "some: ['en:0.993599534034729']\n",
489 | "\n",
490 | "rest: ['en:0.46527764201164246', 'nl:0.05554379150271416', 'ja:0.039999693632125854', 'pl:0.033725958317518234', 'ar:0.028888104483485222', 'sv:0.028634123504161835', 'no:0.024481678381562233', 'id:0.022417806088924408', 'tr:0.02039233408868313', 'zh:0.014322302304208279']\n",
491 | "\n",
492 | "\n",
493 | "0.0\n"
494 | ]
495 | },
496 | {
497 | "name": "stderr",
498 | "output_type": "stream",
499 | "text": [
500 | "c:\\Users\\admin\\.conda\\envs\\melotts\\lib\\site-packages\\sklearn\\base.py:376: InconsistentVersionWarning: Trying to unpickle estimator LogisticRegression from version 1.2.2 when using version 1.5.0. This might lead to breaking code or invalid results. Use at your own risk. For more info please refer to:\n",
501 | "https://scikit-learn.org/stable/model_persistence.html#security-maintainability-limitations\n",
502 | " warnings.warn(\n"
503 | ]
504 | }
505 | ],
506 | "source": [
507 | "from wtpsplit import WtP\n",
508 | "from time import time\n",
509 | "from datetime import datetime\n",
510 | "# text = \"Vielen Dank merci beaucoup for your help.\"\n",
511 | "text = \"Ich bin müde je suis fatigué and I need some rest\"\n",
512 | "# text = \"日语使用者应超过一亿三千万人\"\n",
513 | "# text = \"我是 VGroupChatBot,一个旨在支持多人通信的助手,通过可视化消息来帮助团队成员更好地交流。我可以帮助团队成员更好地整理和共享信息,特别是在讨论、会议和Brainstorming等情况下。你好我的名字是西野くまですmy name is bob很高兴认识你どうぞよろしくお願いいたします「こんにちは」是什么意思。我的名字是西野くまです。I am from Tokyo, 日本の首都。今天的天气非常好\"\n",
514 | "time1 = datetime.now().timestamp()\n",
515 | "wtp = WtP('wtp-bert-mini')\n",
516 | "substrings = wtp.split(text_or_texts=text, threshold=2e-3)\n",
517 | "print(substrings)\n",
518 | "for substring in substrings:\n",
519 | " # lang = lingua_lang_detect_all(substring)\n",
520 | " lang = lingua_lang_detect_all(substring)\n",
521 | " # print(f\"{lang}:{substring}\",end='|')\n",
522 | "print()\n",
523 | "time2 = datetime.now().timestamp()\n",
524 | "\n",
525 | "print(time2 - time1)\n",
526 | "\n",
527 | "from split_lang import LangSplitter\n",
528 | "lang_splitter = LangSplitter()\n",
529 | "substrings = lang_splitter._parse_without_zh_ja(text=text)\n",
530 | "substrings = text.split(' ')\n",
531 | "# substrings = lang_splitter._parse_zh_ja(text=text)\n",
532 | "# substrings = lang_splitter._parse_without_zh_ja(text=text)\n",
533 | "print(substrings)\n",
534 | "for substring in substrings:\n",
535 | " # lang = lingua_lang_detect_all(substring)\n",
536 | " lang = fast_langdetect.detect_multilingual(substring, low_memory=False, k=10, threshold=0.01)\n",
537 | " lang_list = [f\"{item['lang']}:{item['score']}\" for item in lang]\n",
538 | " print(f\"{substring}: {lang_list}\",end='\\n\\n')\n",
539 | "time3 = datetime.now().timestamp()\n",
540 | "print()\n",
541 | "print(time3 - time2)"
542 | ]
543 | },
544 | {
545 | "cell_type": "code",
546 | "execution_count": 117,
547 | "metadata": {},
548 | "outputs": [
549 | {
550 | "name": "stdout",
551 | "output_type": "stream",
552 | "text": [
553 | "1.66e-05\n",
554 | "0.00126\n",
555 | "0.013174603174603174\n"
556 | ]
557 | }
558 | ],
559 | "source": [
560 | "from wordfreq import word_frequency\n",
561 | "ja_freq = word_frequency('bin ', 'en')\n",
562 | "zh_freq = word_frequency('bin ', 'de')\n",
563 | "print(ja_freq)\n",
564 | "print(zh_freq)\n",
565 | "print(ja_freq / zh_freq)"
566 | ]
567 | }
568 | ],
569 | "metadata": {
570 | "kernelspec": {
571 | "display_name": "melotts",
572 | "language": "python",
573 | "name": "python3"
574 | },
575 | "language_info": {
576 | "codemirror_mode": {
577 | "name": "ipython",
578 | "version": 3
579 | },
580 | "file_extension": ".py",
581 | "mimetype": "text/x-python",
582 | "name": "python",
583 | "nbconvert_exporter": "python",
584 | "pygments_lexer": "ipython3",
585 | "version": "3.10.14"
586 | }
587 | },
588 | "nbformat": 4,
589 | "nbformat_minor": 2
590 | }
591 |
--------------------------------------------------------------------------------
/split_lang/__init__.py:
--------------------------------------------------------------------------------
1 | from .split.splitter import LangSplitter
2 | from .model import SubString, SubStringSection, LangSectionType
3 | from .config import DEFAULT_LANG, DEFAULT_LANG_MAP
4 |
--------------------------------------------------------------------------------
/split_lang/config.py:
--------------------------------------------------------------------------------
1 | DEFAULT_LANG_MAP = {
2 | "zh": "zh",
3 | "yue": "zh", # 粤语
4 | "wuu": "zh", # 吴语
5 | "zh-cn": "zh",
6 | "zh-tw": "x",
7 | "ko": "ko",
8 | "ja": "ja",
9 | "de": "de",
10 | "fr": "fr",
11 | "en": "en",
12 | "hr": "en",
13 | }
14 | ZH_JA_LANG_MAP = {
15 | "zh": "zh",
16 | "yue": "zh", # 粤语
17 | "wuu": "zh", # 吴语
18 | "zh-cn": "zh",
19 | "zh-tw": "x",
20 | "ja": "ja",
21 | }
22 | NO_ZH_JA_LANG_MAP = {
23 | "de": "de",
24 | "fr": "fr",
25 | "en": "en",
26 | "hr": "en",
27 | }
28 |
29 | DEFAULT_LANG = "x"
30 |
--------------------------------------------------------------------------------
/split_lang/detect_lang/__init__.py:
--------------------------------------------------------------------------------
1 | from .detector import detect_lang_combined, possible_detection_list
2 |
--------------------------------------------------------------------------------
/split_lang/detect_lang/detector.py:
--------------------------------------------------------------------------------
1 | import logging
2 | from typing import List
3 |
4 | import fast_langdetect
5 |
6 | from ..model import LangSectionType
7 | from ..split.utils import contains_ja_kana
8 |
9 | logger = logging.getLogger(__name__)
10 |
11 |
12 | def fast_lang_detect(text: str) -> str:
13 | result = str(fast_langdetect.detect(text, low_memory=False)["lang"])
14 | result = result.lower()
15 | return result
16 |
17 |
18 | # For example '衬衫' cannot be detected by `langdetect`, and `fast_langdetect` will detect it as 'en'
19 | def detect_lang_combined(text: str, lang_section_type: LangSectionType) -> str:
20 | if lang_section_type is LangSectionType.ZH_JA:
21 | if contains_ja_kana(text):
22 | return "ja"
23 | return fast_lang_detect(text)
24 | return fast_lang_detect(text)
25 |
26 |
27 | def possible_detection_list(text: str) -> List[str]:
28 | text = text.replace("\n", "").strip()
29 | languages = [
30 | item["lang"]
31 | for item in fast_langdetect.detect_multilingual(
32 | text,
33 | low_memory=False,
34 | k=5,
35 | threshold=0.01,
36 | )
37 | ]
38 | return languages
39 |
--------------------------------------------------------------------------------
/split_lang/model.py:
--------------------------------------------------------------------------------
1 | from enum import Enum
2 | from typing import List
3 |
4 | from pydantic import BaseModel
5 |
6 |
7 | class LangSectionType(Enum):
8 | ZH_JA = "zh_ja"
9 | KO = "ko"
10 | PUNCTUATION = "punctuation"
11 | NEWLINE = "newline"
12 | DIGIT = "digit"
13 | OTHERS = "others"
14 | ALL = "all"
15 |
16 |
17 | class SubString(BaseModel):
18 | lang: str
19 | """language of `text`"""
20 | text: str
21 | """text of substring"""
22 | index: int
23 | """index of `text` of original string"""
24 | length: int
25 | """length of `text`"""
26 |
27 |
28 | class SubStringSection(BaseModel):
29 | lang_section_type: LangSectionType
30 | """
31 | Used for deal with different type of languages
32 | 1. Chinese and Japanese both have characters will be processed together
33 | """
34 | text: str
35 | """original text of this section (combines all of the substrings)"""
36 | substrings: List[SubString]
37 | """substrings that splitted from `text`"""
38 |
--------------------------------------------------------------------------------
/split_lang/split/__init__.py:
--------------------------------------------------------------------------------
1 | from .splitter import LangSplitter
2 | from .utils import PUNCTUATION, contains_hangul, contains_ja_kana, contains_zh_ja
3 |
--------------------------------------------------------------------------------
/split_lang/split/splitter.py:
--------------------------------------------------------------------------------
1 | import logging
2 | from typing import Dict, List
3 |
4 | import budoux
5 |
6 | from ..config import DEFAULT_LANG, DEFAULT_LANG_MAP, NO_ZH_JA_LANG_MAP, ZH_JA_LANG_MAP
7 | from ..detect_lang.detector import detect_lang_combined, possible_detection_list
8 | from ..model import LangSectionType, SubString, SubStringSection
9 | from .utils import PUNCTUATION, contains_hangul, contains_ja_kana, contains_zh_ja
10 |
11 | zh_budoux_parser = budoux.load_default_simplified_chinese_parser()
12 | jp_budoux_parser = budoux.load_default_japanese_parser()
13 |
14 | logger = logging.getLogger(__name__)
15 |
16 |
17 | class LangSplitter:
18 | def __init__(
19 | self,
20 | lang_map: Dict[str, str] = DEFAULT_LANG_MAP,
21 | default_lang: str = DEFAULT_LANG,
22 | punctuation: str = PUNCTUATION,
23 | not_merge_punctuation: str = "",
24 | merge_across_punctuation: bool = True,
25 | merge_across_digit: bool = True,
26 | merge_across_newline: bool = True,
27 | debug: bool = True,
28 | log_level: int = logging.INFO,
29 | ) -> None:
30 | """
31 | 1. pre-split stage will use `punctuation` and `digit` to split text then leave substring that contains language character
32 | 2. rule-based and machine learning model will be used for extract words from substring
33 | 3. if language detection of words are same, concat the words into a new substring
34 |
35 | Args:
36 | lang_map (Dict, optional): `{"zh-tw": "zh"}` will map `zh-tw` to `zh`. Defaults to `DEFAULT_LANG_MAP`.
37 | default_lang (str, optional): when `lang_map` did not have the language key detected, fallback to default_lang. Defaults to `DEFAULT_LANG`.
38 | punctuation (str, optional): character that should be treat as punctuation. Defaults to `PUNCTUATION`.
39 | not_merge_punctuation (str, optional): usually been set to `.。??!!` if you do not want to concat all substrings together. Defaults to "".
40 | merge_across_punctuation (bool, optional): merge substring across punctuation. Defaults to True.
41 | merge_across_digit (bool, optional): merge substring across number (e.g. `233`, `9.15`) will be concat to near substring. Defaults to True.
42 | debug (bool, optional): print process for debug. Defaults to False.
43 | """
44 | self.lang_map = lang_map
45 | self.default_lang = default_lang
46 | self.debug = debug
47 | self.punctuation = punctuation
48 | self.not_merge_punctuation = not_merge_punctuation
49 | self.merge_across_punctuation = merge_across_punctuation
50 | self.merge_across_digit = merge_across_digit
51 | self.merge_across_newline = merge_across_newline
52 | self.log_level = log_level
53 | logging.basicConfig(
54 | level=self.log_level,
55 | format="%(asctime)s %(levelname)s [%(name)s]: %(message)s",
56 | )
57 |
58 | # MARK: split_by_lang
59 | def split_by_lang(
60 | self,
61 | text: str,
62 | ) -> List[SubString]:
63 | """
64 | Args:
65 | text (str): text to split
66 |
67 | Returns:
68 | List[SubString]: which contains language, text, index, length, is_punctuation and is_digit data of substring text
69 | """
70 | # FIXME: 调整 merge 顺序,最后合并 punctuation
71 | pre_split_section = self.pre_split(text=text)
72 |
73 | sections = self._split(pre_split_section=pre_split_section)
74 |
75 | if self.merge_across_digit: # 合并跨数字的 SubString
76 | sections = self._merge_substrings_across_digit_based_on_sections(
77 | sections=sections
78 | )
79 | # sections = self._smart_merge_all(pre_split_section=pre_split_section)
80 |
81 | if self.merge_across_punctuation: # 合并跨标点符号的 SubString
82 | sections = self._merge_substrings_across_punctuation_based_on_sections(
83 | sections=sections
84 | )
85 | # sections = self._smart_merge_all(pre_split_section=pre_split_section)
86 |
87 | if self.merge_across_newline:
88 | sections = self._merge_substrings_across_newline_based_on_sections(
89 | sections=sections
90 | )
91 | # sections = self._smart_merge_all(pre_split_section=pre_split_section)
92 |
93 | for section in sections:
94 | if section.lang_section_type == LangSectionType.ZH_JA:
95 | section.substrings = self._special_merge_for_zh_ja(section.substrings)
96 |
97 | substrings: List[SubString] = []
98 | for section in sections:
99 | substrings.extend(section.substrings)
100 |
101 | return substrings
102 |
103 | # MARK: pre_split
104 | def pre_split(self, text: str) -> List[SubStringSection]:
105 | # NOTE: since some language share some characters (e.g. Chinese and Japanese)
106 | # NOTE: while Korean has their own characters,
107 | # NOTE: For Cyrillic alphabet, Latin alphabet, a lot of languages share the alphabet
108 | text = text.strip()
109 | if self.debug:
110 | logger.debug("---------before pre_split:")
111 | logger.debug(text)
112 | sections: List[SubStringSection] = []
113 | current_lang: LangSectionType = LangSectionType.OTHERS
114 | current_text = []
115 |
116 | def add_substring(lang_section_type: LangSectionType):
117 | if current_text:
118 | concat_text = "".join(current_text)
119 |
120 | sections.append(
121 | SubStringSection(
122 | lang_section_type=lang_section_type,
123 | text=concat_text,
124 | substrings=[],
125 | )
126 | )
127 | current_text.clear()
128 |
129 | for index, char in enumerate(text):
130 | if contains_zh_ja(char):
131 | if current_lang != LangSectionType.ZH_JA:
132 | add_substring(current_lang)
133 | current_lang = LangSectionType.ZH_JA
134 | elif contains_hangul(char):
135 | if current_lang != LangSectionType.KO:
136 | add_substring(current_lang)
137 | current_lang = LangSectionType.KO
138 | elif char.isdigit():
139 | if current_lang != LangSectionType.DIGIT:
140 | add_substring(current_lang)
141 | current_lang = LangSectionType.DIGIT
142 | elif char in self.punctuation:
143 | if char == "'" and index > 0 and text[index - 1].isspace() is False:
144 | # For English, "'" is a part of word
145 | pass
146 | # elif current_lang != LangSectionType.PUNCTUATION:
147 | else:
148 | # [, ] \n .都会单独作为一个 SubString
149 | add_substring(current_lang)
150 | current_lang = LangSectionType.PUNCTUATION
151 | elif char.isspace():
152 | # concat space to current text
153 | if char in ["\n", "\r"]:
154 | add_substring(current_lang)
155 | current_lang = LangSectionType.NEWLINE
156 | else:
157 | pass
158 | else:
159 | if current_lang != LangSectionType.OTHERS:
160 | add_substring(current_lang)
161 | current_lang = LangSectionType.OTHERS
162 |
163 | current_text.append(char)
164 |
165 | add_substring(current_lang)
166 | if self.debug:
167 | logger.debug("---------after pre_split:")
168 | for section in sections:
169 | logger.debug(section)
170 | return sections
171 |
172 | # MARK: _split
173 | def _split(
174 | self,
175 | pre_split_section: List[SubStringSection],
176 | ) -> List[SubStringSection]:
177 | # TODO: 针对不同语言 set 进行不同处理
178 | section_index = 0
179 | for section in pre_split_section:
180 | section_len = len(section.text)
181 | if section.lang_section_type is LangSectionType.PUNCTUATION:
182 | # NOTE: 标点符号作为单独的 SubString
183 | section.substrings.append(
184 | SubString(
185 | text=section.text,
186 | lang="punctuation",
187 | index=section_index,
188 | length=section_len,
189 | )
190 | )
191 | elif section.lang_section_type is LangSectionType.DIGIT:
192 | # NOTE: 数字作为单独的 SubString
193 | section.substrings.append(
194 | SubString(
195 | text=section.text,
196 | lang="digit",
197 | index=section_index,
198 | length=section_len,
199 | )
200 | )
201 | elif section.lang_section_type is LangSectionType.NEWLINE:
202 | # NOTE: 换行作为单独的 SubString
203 | section.substrings.append(
204 | SubString(
205 | text=section.text,
206 | lang="newline",
207 | index=section_index,
208 | length=section_len,
209 | )
210 | )
211 | else:
212 | substrings_with_lang: List[SubString] = []
213 | if section.lang_section_type is LangSectionType.ZH_JA:
214 | temp_substrings = self._parse_zh_ja(section.text)
215 | substrings_with_lang = self._init_substr_lang(
216 | texts=temp_substrings,
217 | lang_map=ZH_JA_LANG_MAP,
218 | lang_section_type=LangSectionType.ZH_JA,
219 | )
220 |
221 | elif section.lang_section_type is LangSectionType.KO:
222 | substrings_with_lang = [
223 | SubString(
224 | text=section.text,
225 | lang="ko",
226 | index=section_index,
227 | length=section_len,
228 | )
229 | ]
230 |
231 | else:
232 | temp_substrings = self._parse_without_zh_ja(section.text)
233 | substrings_with_lang = self._init_substr_lang(
234 | texts=temp_substrings,
235 | lang_section_type=LangSectionType.OTHERS,
236 | )
237 | # 更新 index
238 | for substr in substrings_with_lang:
239 | substr.index += section_index
240 | if self.debug:
241 | logger.debug("---------after _init_substr_lang:")
242 | for substr in substrings_with_lang:
243 | logger.debug(substr)
244 |
245 | section.substrings = substrings_with_lang
246 |
247 | section_index += section_len
248 |
249 | pre_split_section = self._smart_merge_all(pre_split_section)
250 | if self.debug:
251 | logger.debug("---------after split:")
252 | for section in pre_split_section:
253 | logger.debug(section)
254 |
255 | return pre_split_section
256 |
257 | # MARK: smart merge substring together
258 |
259 | def _smart_merge_all(self, pre_split_section: List[SubStringSection]):
260 | for section in pre_split_section:
261 | if (
262 | section.lang_section_type is LangSectionType.PUNCTUATION
263 | or section.lang_section_type is LangSectionType.NEWLINE
264 | ):
265 | # print(section.text)
266 | continue
267 | smart_concat_result = self._smart_merge(
268 | substr_list=section.substrings,
269 | lang_section_type=section.lang_section_type,
270 | )
271 | section.substrings.clear()
272 | section.substrings = smart_concat_result
273 |
274 | if self.debug:
275 | logger.debug("---------after smart_merge_all:")
276 | for section in pre_split_section:
277 | logger.debug(section)
278 |
279 | return pre_split_section
280 |
281 | # MARK: _parse_without_zh_ja
282 | def _parse_without_zh_ja(self, text: str):
283 | words: List[str] = []
284 | exist_space = False
285 | chars = []
286 | for char in text:
287 | if char.isspace() is False:
288 | if exist_space:
289 | words.append("".join(chars))
290 | chars.clear()
291 | exist_space = False
292 | chars.append(char)
293 | else:
294 | exist_space = True
295 | chars.append(char)
296 | if len(chars) > 0:
297 | words.append("".join(chars))
298 |
299 | return words
300 |
301 | # MARK: _parse_zh_ja
302 | def _parse_zh_ja(self, text) -> List[str]:
303 | splitted_texts_jp = []
304 | splitted_texts_jp = jp_budoux_parser.parse(text)
305 | if self.debug:
306 | logger.debug("---------after jp_budoux_parser:")
307 | logger.debug(splitted_texts_jp)
308 |
309 | splitted_texts_zh_jp = []
310 | for substring in splitted_texts_jp:
311 | splitted_texts_zh_jp.extend(zh_budoux_parser.parse(substring))
312 | if self.debug:
313 | logger.debug("---------after zh_budoux_parser:")
314 | logger.debug(splitted_texts_zh_jp)
315 |
316 | new_substrings = [splitted_texts_zh_jp[0]]
317 | for substring in splitted_texts_zh_jp[1:]:
318 | is_left_ja_kana = contains_ja_kana(new_substrings[-1])
319 | is_cur_ja_kana = contains_ja_kana(substring)
320 | is_both_same_lang = is_left_ja_kana == is_cur_ja_kana
321 | is_both_ja_kana = is_left_ja_kana is True and is_both_same_lang
322 | is_both_zh = is_left_ja_kana is False and is_both_same_lang
323 | if is_both_ja_kana: # both substring is katakana or hiragana, then concat
324 | new_substrings[-1] += substring
325 | elif is_both_zh and len(substring) == 1:
326 | # NOTE: both substring is full Chinese character, and current one is only one character
327 | # NOTE: 90% is because we first use ja_parser then zh_parser (from `budoux`)
328 | # NOTE: Since kanji in Japanese usually not appear by them self, Single character is CAUSED BY zh_parser
329 | # NOTE: So we let single character concat together, if both substring did not contain kana
330 | new_substrings[-1] += substring
331 | else:
332 | new_substrings.append(substring)
333 |
334 | if len(new_substrings) >= 2 and len(new_substrings[0]) == 1:
335 | new_substrings[1] = new_substrings[0] + new_substrings[1]
336 | new_substrings = new_substrings[1:]
337 |
338 | if self.debug:
339 | logger.debug("---------after parse_zh_ja:")
340 | logger.debug(new_substrings)
341 |
342 | return new_substrings
343 |
344 | # MARK: _smart_merge
345 | def _smart_merge(
346 | self,
347 | substr_list: List[SubString],
348 | lang_section_type: LangSectionType,
349 | ) -> List[SubString]:
350 | lang_text_list = substr_list
351 | lang_text_list = self._merge_substrings(lang_text_list)
352 | lang_text_list = self._merge_middle_substr_to_two_side(lang_text_list)
353 | lang_text_list = self._merge_substrings(lang_text_list)
354 | if self.debug:
355 | logger.debug("---------after _merge_middle_substr_to_two_side:")
356 | for substr in lang_text_list:
357 | logger.debug(substr)
358 | # NOTE: get_languages 需要根据不同语言进行不同处理
359 | lang_text_list = self._get_languages(
360 | lang_text_list=lang_text_list,
361 | lang_section_type=lang_section_type,
362 | lang_map=ZH_JA_LANG_MAP
363 | if lang_section_type is LangSectionType.ZH_JA
364 | else NO_ZH_JA_LANG_MAP,
365 | )
366 | lang_text_list = self._merge_middle_substr_to_two_side(lang_text_list)
367 | lang_text_list = self._merge_substrings(lang_text_list)
368 | if self.debug:
369 | logger.debug("---------after _merge_middle_substr_to_two_side:")
370 | for substr in lang_text_list:
371 | logger.debug(substr)
372 | lang_text_list = self._fill_unknown_language(lang_text_list)
373 | lang_text_list = self._merge_side_substr_to_near(lang_text_list)
374 | lang_text_list = self._merge_substrings(lang_text_list)
375 | if self.debug:
376 | logger.debug("---------after _merge_side_substr_to_near:")
377 | for substr in lang_text_list:
378 | logger.debug(substr)
379 | lang_text_list = self._get_languages(
380 | lang_text_list=lang_text_list,
381 | lang_section_type=lang_section_type,
382 | lang_map=ZH_JA_LANG_MAP
383 | if lang_section_type is LangSectionType.ZH_JA
384 | else NO_ZH_JA_LANG_MAP,
385 | )
386 | lang_text_list = self._merge_middle_substr_to_two_side(lang_text_list)
387 | lang_text_list = self._merge_substrings(lang_text_list)
388 | if self.debug:
389 | logger.debug("---------after _merge_middle_substr_to_two_side:")
390 | for substr in lang_text_list:
391 | logger.debug(substr)
392 |
393 | return lang_text_list
394 |
395 | def _is_merge_middle_to_two_side(
396 | self, left: SubString, middle: SubString, right: SubString
397 | ):
398 | is_middle_contains_no_kana = not contains_ja_kana(middle.text)
399 |
400 | middle_lang_is_possible_the_same_as_left = (
401 | left.lang in possible_detection_list(middle.text)
402 | and is_middle_contains_no_kana
403 | )
404 | middle_lang_is_x = middle.lang == "x"
405 |
406 | is_middle_short_and_two_side_long = (
407 | middle.length <= 4
408 | and left.length + right.length >= 6
409 | and is_middle_contains_no_kana
410 | )
411 |
412 | return (
413 | middle_lang_is_possible_the_same_as_left
414 | or middle_lang_is_x
415 | or is_middle_short_and_two_side_long
416 | )
417 |
418 | def _is_cur_short_and_near_long(self, cur: SubString, near: SubString):
419 | is_cur_contains_no_kana = not contains_ja_kana(cur.text)
420 | is_near_contains_no_kana = not contains_ja_kana(near.text)
421 | is_both_contains_no_kana = is_cur_contains_no_kana and is_near_contains_no_kana
422 | # NOTE: e.g. 日本人, 今晚, 国外移民
423 | is_near_str_is_long_zh = (
424 | near.length > cur.length and near.length >= 6 and near.lang == "zh"
425 | )
426 | is_cur_short_and_near_is_zh_and_long = (
427 | is_near_str_is_long_zh and is_both_contains_no_kana
428 | )
429 | cur_lang_is_possible_the_same_as_near = (
430 | near.lang in possible_detection_list(cur.text) and is_both_contains_no_kana
431 | )
432 | return (
433 | is_cur_short_and_near_is_zh_and_long
434 | or cur_lang_is_possible_the_same_as_near
435 | )
436 |
437 | # MARK: _merge_middle_substr_to_two_side
438 | def _merge_middle_substr_to_two_side(self, substrings: List[SubString]):
439 | substr_len = len(substrings)
440 | if substr_len <= 2:
441 | return substrings
442 | for index in range(substr_len - 2):
443 | left_block = substrings[index]
444 | middle_block = substrings[index + 1]
445 | right_block = substrings[index + 2]
446 |
447 | if (
448 | left_block.lang == right_block.lang
449 | and left_block.lang != "x"
450 | and left_block.lang != "newline"
451 | ):
452 | # if different detectors results contains near block's language, then combine
453 |
454 | if self._is_merge_middle_to_two_side(
455 | left_block, middle_block, right_block
456 | ):
457 | substrings[index + 1].lang = left_block.lang
458 |
459 | return substrings
460 |
461 | # MARK: _merge_side_substr_to_near
462 | def _merge_side_substr_to_near(self, substrings: List[SubString]):
463 | # NOTE: Merge leftest substr
464 | is_lang_x = substrings[0].lang == "x"
465 | is_cur_short_and_near_long = False
466 | is_possible_same_lang_with_near = False
467 | if len(substrings) >= 2:
468 | # NOTE: if current substr is short and near substr is long, then merge
469 | is_cur_short_and_near_long = self._is_cur_short_and_near_long(
470 | substrings[0], substrings[1]
471 | )
472 |
473 | # NOTE: if language of near substr is possible the same as current substr, then merge
474 | is_possible_same_lang_with_near = (
475 | substrings[1].lang in possible_detection_list(substrings[0].text)
476 | and substrings[1].length <= 5
477 | )
478 |
479 | is_need_merge_to_right = (
480 | is_lang_x or is_cur_short_and_near_long or is_possible_same_lang_with_near
481 | )
482 |
483 | if is_need_merge_to_right:
484 | substrings[0].lang = self._get_nearest_lang_with_direction(
485 | substrings, 0, search_left=False
486 | )
487 | # NOTE: Merge rightest substr
488 | is_lang_x = substrings[-1].lang == "x"
489 | is_cur_short_and_near_long = False
490 | is_possible_same_lang_with_near = False
491 | if len(substrings) >= 2:
492 | is_cur_short_and_near_long = self._is_cur_short_and_near_long(
493 | substrings[-1], substrings[-2]
494 | )
495 | is_possible_same_lang_with_near = (
496 | substrings[-2].lang in possible_detection_list(substrings[-1].text)
497 | and substrings[-2].length <= 5
498 | )
499 |
500 | is_need_merge_to_left = (
501 | is_lang_x or is_cur_short_and_near_long or is_possible_same_lang_with_near
502 | )
503 |
504 | if is_need_merge_to_left:
505 | substrings[-1].lang = self._get_nearest_lang_with_direction(
506 | substrings, len(substrings) - 1, search_left=True
507 | )
508 | return substrings
509 |
510 | # MARK: _fill_unknown_language
511 | def _fill_unknown_language(
512 | self,
513 | substrings: List[SubString],
514 | ):
515 | for index, substr in enumerate(substrings):
516 | if substr.lang == "x":
517 | if index == 0:
518 | # For head substring, find right substring
519 | substrings[index].lang = self._get_nearest_lang_with_direction(
520 | substrings, index, search_left=False
521 | )
522 | elif index == len(substrings) - 1:
523 | # For tail substring, find left substring
524 | substrings[index].lang = self._get_nearest_lang_with_direction(
525 | substrings, index, search_left=True
526 | )
527 | else:
528 | # For body (middle) substring, find based on rule
529 | substrings[index].lang = self._get_nearest_lang_with_direction(
530 | substrings, index, self._get_merge_direction(substrings, index)
531 | )
532 | return substrings
533 |
534 | # MARK: _find_nearest_lang_with_direction
535 | def _get_nearest_lang_with_direction(
536 | self, substrings: List[SubString], index: int, search_left: bool
537 | ) -> str:
538 | if search_left:
539 | for i in range(1, len(substrings)):
540 | left_i_index = index - i
541 | if (
542 | left_i_index >= 0
543 | and substrings[left_i_index].lang != "x"
544 | and substrings[left_i_index].lang != "digit"
545 | ):
546 | return substrings[left_i_index].lang
547 | else:
548 | for i in range(1, len(substrings)):
549 | right_i_index = index + i
550 | if (
551 | right_i_index < len(substrings)
552 | and substrings[right_i_index].lang != "x"
553 | and substrings[right_i_index].lang != "digit"
554 | ):
555 | return substrings[right_i_index].lang
556 | return substrings[index].lang
557 |
558 | # MARK: _get_merge_direction
559 | def _get_merge_direction(self, substrings: List[SubString], index: int) -> bool:
560 | is_left = False
561 | if index == 0:
562 | is_left = False
563 | return is_left
564 | elif index == len(substrings) - 1:
565 | is_left = True
566 | return is_left
567 | left_block = substrings[index - 1]
568 | right_block = substrings[index + 1]
569 | if len(left_block.text) >= len(right_block.text):
570 | is_left = True
571 | else:
572 | is_left = False
573 | return is_left
574 |
575 | # MARK: _merge_substrings
576 | def _merge_substrings(
577 | self,
578 | substrings: List[SubString],
579 | ):
580 | smart_concat_result: List[SubString] = []
581 | lang = ""
582 | for block in substrings:
583 | cur_lang = block.lang
584 | if cur_lang != lang:
585 | smart_concat_result.append(block)
586 | else:
587 | smart_concat_result[-1].text += block.text
588 | smart_concat_result[-1].length += block.length
589 | lang = cur_lang
590 | return smart_concat_result
591 |
592 | # MARK: _special_merge_for_zh_ja
593 | def _special_merge_for_zh_ja(
594 | self,
595 | substrings: List[SubString],
596 | ) -> List[SubString]:
597 | new_substrings: List[SubString] = []
598 |
599 | if len(substrings) == 1:
600 | return substrings
601 | # NOTE: 统计每个语言的字符串长度
602 | substring_text_len_by_lang = {
603 | "zh": 0,
604 | "ja": 0,
605 | "x": 0,
606 | "digit": 0,
607 | "punctuation": 0,
608 | "newline": 0,
609 | }
610 | index = 0
611 | while index < len(substrings):
612 | current_block = substrings[index]
613 | substring_text_len_by_lang[current_block.lang] += current_block.length
614 | if index == 0:
615 | if (
616 | substrings[index + 1].lang in ["zh", "ja"]
617 | and substrings[index].lang in ["zh", "ja", "x"]
618 | and substrings[index].length * 10 < substrings[index + 1].length
619 | ):
620 | right_block = substrings[index + 1]
621 | new_substrings.append(
622 | SubString(
623 | is_digit=False,
624 | is_punctuation=False,
625 | lang=right_block.lang,
626 | text=current_block.text + right_block.text,
627 | length=current_block.length + right_block.length,
628 | index=current_block.index,
629 | )
630 | )
631 | index += 1
632 | else:
633 | new_substrings.append(substrings[index])
634 | elif index == len(substrings) - 1:
635 | left_block = new_substrings[-1]
636 | if (
637 | left_block.lang in ["zh", "ja"]
638 | and current_block.lang in ["zh", "ja", "x"]
639 | and current_block.length * 10 < left_block.length
640 | ):
641 | new_substrings[-1].text += current_block.text
642 | new_substrings[-1].length += current_block.length
643 |
644 | index += 1
645 | else:
646 | new_substrings.append(substrings[index])
647 | else:
648 | if (
649 | new_substrings[-1].lang == substrings[index + 1].lang
650 | and new_substrings[-1].lang in ["zh", "ja"]
651 | # and substrings[index].lang in ["zh", "ja", "x"]
652 | and substrings[index].lang != "en"
653 | and substrings[index].length * 10
654 | < new_substrings[-1].length + substrings[index + 1].length
655 | ):
656 | left_block = new_substrings[-1]
657 | right_block = substrings[index + 1]
658 | current_block = substrings[index]
659 | new_substrings[-1].text += current_block.text + right_block.text
660 | new_substrings[-1].length += (
661 | current_block.length + right_block.length
662 | )
663 | index += 1
664 | else:
665 | new_substrings.append(substrings[index])
666 | index += 1
667 |
668 | # NOTE: 如果 substring_count 中 存在 x,则将 x 设置为最多的 lang
669 | if substring_text_len_by_lang["x"] > 0:
670 | max_lang = max(
671 | substring_text_len_by_lang, key=substring_text_len_by_lang.get
672 | )
673 | for index, substr in enumerate(new_substrings):
674 | if substr.lang == "x":
675 | new_substrings[index].lang = max_lang
676 | # NOTE: 如果 ja 数量是 zh 数量的 10 倍以上,则该 zh 设置为 ja
677 | if substring_text_len_by_lang["ja"] >= substring_text_len_by_lang["zh"] * 10:
678 | for index, substr in enumerate(new_substrings):
679 | if substr.lang == "zh":
680 | new_substrings[index].lang = "ja"
681 | new_substrings = self._merge_substrings(substrings=new_substrings)
682 | return new_substrings
683 |
684 | # MARK: _merge_substrings_across_newline
685 | def _merge_substrings_across_newline(
686 | self,
687 | substrings: List[SubString],
688 | ) -> List[SubString]:
689 | new_substrings: List[SubString] = []
690 | last_lang = ""
691 |
692 | for _, substring in enumerate(substrings):
693 | if new_substrings:
694 | if substring.lang == "newline":
695 | # If the last substring is also a newline, merge them
696 | new_substrings[-1].text += substring.text
697 | new_substrings[-1].length += substring.length
698 | else:
699 | if substring.lang == last_lang or last_lang == "":
700 | new_substrings[-1].text += substring.text
701 | new_substrings[-1].length += substring.length
702 | new_substrings[-1].lang = (
703 | substring.lang
704 | if new_substrings[-1].lang == "newline"
705 | else new_substrings[-1].lang
706 | )
707 | else:
708 | new_substrings.append(substring)
709 | else:
710 | new_substrings.append(substring)
711 | last_lang = substring.lang
712 |
713 | return new_substrings
714 |
715 | # MARK: _merge_substrings_across_newline_based_on_sections
716 | def _merge_substrings_across_newline_based_on_sections(
717 | self,
718 | sections: List[SubStringSection],
719 | ) -> List[SubStringSection]:
720 | new_sections: List[SubStringSection] = [sections[0]]
721 | # NOTE: 将 sections 中的 newline 合并到临近的非 punctuation 的 section
722 | for index, _ in enumerate(sections):
723 | if index == 0:
724 | continue
725 | if index >= len(sections):
726 | break
727 |
728 | current_section = sections[index]
729 | if new_sections[-1].lang_section_type == LangSectionType.NEWLINE:
730 | # NOTE: 如果前一个 section 是 newline,则合并
731 | new_sections[-1].lang_section_type = current_section.lang_section_type
732 | new_sections[-1].text += current_section.text
733 | new_sections[-1].substrings.extend(current_section.substrings)
734 | for index, substr in enumerate(new_sections[-1].substrings):
735 | if index == 0:
736 | continue
737 | else:
738 | substr.index = (
739 | new_sections[-1].substrings[index - 1].index
740 | + new_sections[-1].substrings[index - 1].length
741 | )
742 |
743 | elif current_section.lang_section_type == LangSectionType.NEWLINE:
744 | # NOTE: 如果前一个 section 不是 punctuation,则合并
745 | new_sections[-1].text += current_section.text
746 | new_sections[-1].substrings.extend(current_section.substrings)
747 | new_sections[-1].substrings[-1].index = (
748 | new_sections[-1].substrings[-2].index
749 | + new_sections[-1].substrings[-2].length
750 | )
751 | else:
752 | new_sections.append(current_section)
753 | # NOTE: 将相同类型的 section 合并
754 | new_sections_merged: List[SubStringSection] = [new_sections[0]]
755 | for index, _ in enumerate(new_sections):
756 | if index == 0:
757 | continue
758 | if (
759 | new_sections_merged[-1].lang_section_type
760 | == new_sections[index].lang_section_type
761 | ):
762 | new_sections_merged[-1].text += new_sections[index].text
763 | new_sections_merged[-1].substrings.extend(
764 | new_sections[index].substrings
765 | )
766 | else:
767 | new_sections_merged.append(new_sections[index])
768 | # NOTE: 重新计算 index
769 | for section_index, section in enumerate(new_sections_merged):
770 | if section_index == 0:
771 | for substr_index, substr in enumerate(section.substrings):
772 | if substr_index == 0:
773 | continue
774 | else:
775 | substr.index = (
776 | section.substrings[substr_index - 1].index
777 | + section.substrings[substr_index - 1].length
778 | )
779 | else:
780 | for substr_index, substr in enumerate(section.substrings):
781 | if substr_index == 0:
782 | substr.index = (
783 | new_sections_merged[section_index - 1].substrings[-1].index
784 | + new_sections_merged[section_index - 1]
785 | .substrings[-1]
786 | .length
787 | )
788 | else:
789 | substr.index = (
790 | section.substrings[substr_index - 1].index
791 | + section.substrings[substr_index - 1].length
792 | )
793 | # NOTE: 合并 sections 中的 substrings 里面的 text
794 | for section in new_sections_merged:
795 | section.substrings = self._merge_substrings_across_newline(
796 | substrings=section.substrings
797 | )
798 | if self.debug:
799 | logger.debug(
800 | "---------------------------------after_merge_newline_sections:"
801 | )
802 | for section in new_sections_merged:
803 | logger.debug(section)
804 | return new_sections_merged
805 |
806 | # MARK: _merge_substring_across_digit
807 | def _merge_substrings_across_digit(
808 | self,
809 | substrings: List[SubString],
810 | ) -> List[SubString]:
811 | new_substrings: List[SubString] = []
812 | last_lang = ""
813 |
814 | for _, substring in enumerate(substrings):
815 | if new_substrings:
816 | if substring.lang == "digit":
817 | new_substrings[-1].text += substring.text
818 | new_substrings[-1].length += substring.length
819 | else:
820 | if substring.lang == last_lang or last_lang == "":
821 | new_substrings[-1].text += substring.text
822 | new_substrings[-1].length += substring.length
823 | if substring.lang != "punctuation":
824 | new_substrings[-1].lang = substring.lang
825 | else:
826 | new_substrings.append(substring)
827 | else:
828 | new_substrings.append(substring)
829 | last_lang = substring.lang
830 |
831 | return new_substrings
832 |
833 | # MARK: _merge_substrings_across_digit_based_on_sections
834 | def _merge_substrings_across_digit_based_on_sections(
835 | self,
836 | sections: List[SubStringSection],
837 | ) -> List[SubStringSection]:
838 | new_sections: List[SubStringSection] = [sections[0]]
839 | # NOTE: 将 sections 中的 digit 合并到临近的非 digit 的 section
840 | for index, _ in enumerate(sections):
841 | if index == 0:
842 | continue
843 | if index >= len(sections):
844 | break
845 |
846 | current_section = sections[index]
847 | # print(f"当前 section:{current_section.lang_section_type}")
848 | # 如果前一个 section 和当前的 section 类型不同,则合并
849 | one_of_section_is_digit = (
850 | new_sections[-1].lang_section_type == LangSectionType.DIGIT
851 | or current_section.lang_section_type == LangSectionType.DIGIT
852 | )
853 | if one_of_section_is_digit:
854 | # print(f"测试:{new_sections[-1].text} | {current_section.text}")
855 | if new_sections[-1].lang_section_type == LangSectionType.DIGIT:
856 | if current_section.lang_section_type != LangSectionType.PUNCTUATION:
857 | new_sections[
858 | -1
859 | ].lang_section_type = current_section.lang_section_type
860 | new_sections[-1].text += current_section.text
861 | new_sections[-1].substrings[-1].text += current_section.substrings[
862 | 0
863 | ].text
864 | new_sections[-1].substrings[
865 | -1
866 | ].length += current_section.substrings[0].length
867 | new_sections[-1].substrings[-1].lang = (
868 | current_section.substrings[0].lang
869 | if current_section.substrings[0].lang != "punctuation"
870 | else new_sections[-1].substrings[-1].lang
871 | )
872 | new_sections[-1].substrings.extend(current_section.substrings[1:])
873 | for index, substr in enumerate(new_sections[-1].substrings):
874 | if index == 0:
875 | continue
876 | else:
877 | substr.index = (
878 | new_sections[-1].substrings[index - 1].index
879 | + new_sections[-1].substrings[index - 1].length
880 | )
881 | elif current_section.lang_section_type == LangSectionType.DIGIT:
882 | new_sections[-1].text += current_section.text
883 | new_sections[-1].substrings[-1].text += current_section.substrings[
884 | 0
885 | ].text
886 | new_sections[-1].substrings[
887 | -1
888 | ].length += current_section.substrings[0].length
889 | if new_sections[-1].substrings[-1].lang == "punctuation":
890 | new_sections[-1].substrings[-1].lang = "digit"
891 |
892 | if (
893 | new_sections[-1].lang_section_type
894 | == LangSectionType.PUNCTUATION
895 | ):
896 | new_sections[-1].lang_section_type = LangSectionType.DIGIT
897 |
898 | else:
899 | new_sections.append(current_section)
900 | # NOTE: 将相同类型的 section 合并
901 | new_sections_merged: List[SubStringSection] = [new_sections[0]]
902 | for index, _ in enumerate(new_sections):
903 | if index == 0:
904 | continue
905 | if (
906 | new_sections_merged[-1].lang_section_type
907 | == new_sections[index].lang_section_type
908 | ):
909 | new_sections_merged[-1].text += new_sections[index].text
910 | new_sections_merged[-1].substrings.extend(
911 | new_sections[index].substrings
912 | )
913 | else:
914 | new_sections_merged.append(new_sections[index])
915 | # NOTE: 重新计算 index
916 | for section_index, section in enumerate(new_sections_merged):
917 | if section_index == 0:
918 | for substr_index, substr in enumerate(section.substrings):
919 | if substr_index == 0:
920 | continue
921 | else:
922 | substr.index = (
923 | section.substrings[substr_index - 1].index
924 | + section.substrings[substr_index - 1].length
925 | )
926 | else:
927 | for substr_index, substr in enumerate(section.substrings):
928 | if substr_index == 0:
929 | substr.index = (
930 | new_sections_merged[section_index - 1].substrings[-1].index
931 | + new_sections_merged[section_index - 1]
932 | .substrings[-1]
933 | .length
934 | )
935 | else:
936 | substr.index = (
937 | section.substrings[substr_index - 1].index
938 | + section.substrings[substr_index - 1].length
939 | )
940 | # NOTE: 再次合并 sections 中的 substrings 里面的 text
941 | for section in new_sections_merged:
942 | section.substrings = self._merge_substrings_across_digit(section.substrings)
943 | if self.debug:
944 | logger.debug("---------------------------------after_merge_digit_sections:")
945 | for section in new_sections_merged:
946 | logger.debug(section)
947 | return new_sections_merged
948 |
949 | def _merge_substrings_across_punctuation(
950 | self,
951 | substrings: List[SubString],
952 | ) -> List[SubString]:
953 | new_substrings: List[SubString] = []
954 | last_lang = "" # Changed from 'lang' to 'last_lang' for consistency
955 |
956 | for _, substring in enumerate(substrings):
957 | if new_substrings:
958 | if substring.lang == "punctuation":
959 | # If the last substring is also a punctuation, merge them
960 | new_substrings[-1].text += substring.text
961 | new_substrings[-1].length += substring.length
962 | else:
963 | if substring.lang == last_lang or last_lang == "":
964 | new_substrings[-1].text += substring.text
965 | new_substrings[-1].length += substring.length
966 | new_substrings[-1].lang = (
967 | substring.lang
968 | if new_substrings[-1].lang == "punctuation"
969 | else new_substrings[-1].lang
970 | )
971 | else:
972 | new_substrings.append(substring)
973 | else:
974 | new_substrings.append(substring)
975 | last_lang = substring.lang
976 |
977 | return new_substrings
978 |
979 | # MARK: _merge_substrings_across_punctuation based on sections
980 | def _merge_substrings_across_punctuation_based_on_sections(
981 | self,
982 | sections: List[SubStringSection],
983 | ) -> List[SubStringSection]:
984 | new_sections: List[SubStringSection] = [sections[0]]
985 | # NOTE: 将 sections 中的 punctuation 合并到临近的非 punctuation 的 section
986 | for index, _ in enumerate(sections):
987 | if index == 0:
988 | continue
989 | # 检查当前 section 和前一个 section 是否可以合并
990 | if index >= len(sections):
991 | break
992 |
993 | current_section = sections[index]
994 | # 如果前一个 section 和当前的 section 类型不同,且其中一个是 punctuation,则合并
995 | one_of_section_is_punctuation = (
996 | new_sections[-1].lang_section_type == LangSectionType.PUNCTUATION
997 | or current_section.lang_section_type == LangSectionType.PUNCTUATION
998 | )
999 | if one_of_section_is_punctuation:
1000 | # 如果当前 section 是 punctuation,且第一个元素不是 not_merge_punctuation,则合并
1001 |
1002 | if (
1003 | new_sections[-1].lang_section_type == LangSectionType.PUNCTUATION
1004 | and new_sections[-1].substrings[0].text
1005 | not in self.not_merge_punctuation
1006 | ):
1007 | # 将前一个 punctuation section 和当前的 section 合并
1008 | new_sections[-1].text += current_section.text
1009 | new_sections[
1010 | -1
1011 | ].lang_section_type = current_section.lang_section_type
1012 | new_sections[-1].substrings[-1].text += current_section.substrings[
1013 | 0
1014 | ].text
1015 | new_sections[-1].substrings[
1016 | -1
1017 | ].length += current_section.substrings[0].length
1018 | new_sections[-1].substrings[-1].lang = current_section.substrings[
1019 | 0
1020 | ].lang
1021 | new_sections[-1].substrings.extend(current_section.substrings[1:])
1022 | for index, substr in enumerate(new_sections[-1].substrings):
1023 | if index == 0:
1024 | # 第一个元素是前一个 punctuation section 的最后一个元素,不需要修改
1025 | continue
1026 | else:
1027 | # 其他元素是当前 section 的元素,需要修改 index
1028 | substr.index = (
1029 | new_sections[-1].substrings[index - 1].index
1030 | + new_sections[-1].substrings[index - 1].length
1031 | )
1032 | elif (
1033 | current_section.lang_section_type == LangSectionType.PUNCTUATION
1034 | and current_section.substrings[0].text
1035 | not in self.not_merge_punctuation
1036 | ):
1037 | # 将当前的 punctuation section 和前一个 section 合并
1038 | new_sections[-1].text += current_section.text
1039 | new_sections[-1].substrings[-1].text += current_section.substrings[
1040 | 0
1041 | ].text
1042 | new_sections[-1].substrings[
1043 | -1
1044 | ].length += current_section.substrings[0].length
1045 |
1046 | new_sections[-1].substrings.extend(current_section.substrings[1:])
1047 | for index, substr in enumerate(new_sections[-1].substrings):
1048 | if index == 0:
1049 | continue
1050 | else:
1051 | substr.index = (
1052 | new_sections[-1].substrings[index - 1].index
1053 | + new_sections[-1].substrings[index - 1].length
1054 | )
1055 | else:
1056 | new_sections.append(current_section)
1057 | else:
1058 | new_sections.append(current_section)
1059 |
1060 | # NOTE: 将相同类型的 section 合并
1061 | new_sections_merged: List[SubStringSection] = [new_sections[0]]
1062 | for index, _ in enumerate(new_sections):
1063 | if index == 0:
1064 | continue
1065 | if (
1066 | new_sections_merged[-1].lang_section_type
1067 | == new_sections[index].lang_section_type
1068 | ):
1069 | new_sections_merged[-1].text += new_sections[index].text
1070 | new_sections_merged[-1].substrings.extend(
1071 | new_sections[index].substrings
1072 | )
1073 | else:
1074 | new_sections_merged.append(new_sections[index])
1075 | # NOTE: 重新计算 index
1076 | for section_index, section in enumerate(new_sections_merged):
1077 | if section_index == 0:
1078 | for substr_index, substr in enumerate(section.substrings):
1079 | if substr_index == 0:
1080 | continue
1081 | else:
1082 | substr.index = (
1083 | section.substrings[substr_index - 1].index
1084 | + section.substrings[substr_index - 1].length
1085 | )
1086 | else:
1087 | for substr_index, substr in enumerate(section.substrings):
1088 | if substr_index == 0:
1089 | substr.index = (
1090 | new_sections_merged[section_index - 1].substrings[-1].index
1091 | + new_sections_merged[section_index - 1]
1092 | .substrings[-1]
1093 | .length
1094 | )
1095 | else:
1096 | substr.index = (
1097 | section.substrings[substr_index - 1].index
1098 | + section.substrings[substr_index - 1].length
1099 | )
1100 | # NOTE: 再次合并 sections 中的 substrings 里面的 text
1101 | for section in new_sections_merged:
1102 | section.substrings = self._merge_substrings_across_punctuation(
1103 | section.substrings
1104 | )
1105 | if self.debug:
1106 | logger.debug(
1107 | "---------------------------------after_merge_punctuation_sections:"
1108 | )
1109 | for section in new_sections_merged:
1110 | logger.debug(section)
1111 | return new_sections_merged
1112 |
1113 | # MARK: _init_substr_lang
1114 | def _init_substr_lang(
1115 | self,
1116 | texts: List[str],
1117 | lang_section_type: LangSectionType,
1118 | lang_map: Dict[str, str] = None,
1119 | ) -> List[SubString]:
1120 | substrings: List[SubString] = []
1121 | substring_index = 0
1122 | lang_map = self.lang_map if lang_map is None else lang_map
1123 | if self.debug:
1124 | logger.debug("---------lang_map:")
1125 | logger.debug(lang_map)
1126 | for text in texts:
1127 | length = len(text)
1128 |
1129 | cur_lang = detect_lang_combined(text, lang_section_type=lang_section_type)
1130 | cur_lang = lang_map.get(cur_lang, self.default_lang)
1131 | temp_substr = SubString(
1132 | lang=cur_lang,
1133 | text=text,
1134 | length=length,
1135 | index=substring_index,
1136 | )
1137 | if self.debug:
1138 | logger.debug(
1139 | f"---------lang_map: {lang_map}, temp_substr: {temp_substr}"
1140 | )
1141 | substrings.append(temp_substr)
1142 |
1143 | substring_index += length
1144 | return substrings
1145 |
1146 | # MARK: _get_languages
1147 | def _get_languages(
1148 | self,
1149 | lang_text_list: List[SubString],
1150 | lang_section_type: LangSectionType,
1151 | lang_map: Dict[str, str] = None,
1152 | ):
1153 | if lang_section_type in [
1154 | LangSectionType.DIGIT,
1155 | LangSectionType.KO,
1156 | LangSectionType.PUNCTUATION,
1157 | ]:
1158 | return lang_text_list
1159 |
1160 | if lang_map is None:
1161 | lang_map = self.lang_map
1162 |
1163 | for _, substr in enumerate(lang_text_list):
1164 | cur_lang = detect_lang_combined(
1165 | text=substr.text, lang_section_type=lang_section_type
1166 | )
1167 | cur_lang = lang_map.get(cur_lang, self.default_lang)
1168 |
1169 | return lang_text_list
1170 |
--------------------------------------------------------------------------------
/split_lang/split/utils.py:
--------------------------------------------------------------------------------
1 | import re
2 |
3 | PUNCTUATION = r"""〜~,.;:!?,。!?;:、·([{<(【《〈「『“‘)]}>)】》〉」』”’"-_——#$%&……¥'*+<=>?@[\]^_`{|}~"""
4 |
5 | chinese_char_pattern = re.compile(r"[\u4e00-\u9fff]")
6 | hangul_pattern = re.compile(r"[\uac00-\ud7af]")
7 | hiragana_katakana_pattern = re.compile(r"[\u3040-\u30ff々]") # 添加了对“々”符号的判断
8 |
9 | zh_ja_pattern = re.compile(r"[\u4e00-\u9fff\u3040-\u30ff々]")
10 |
11 |
12 | def contains_chinese_char(text: str) -> bool:
13 | """
14 | check if the text contains Chinese character
15 |
16 | Args:
17 | text (str): the text to check
18 |
19 | Returns:
20 | """
21 | return bool(chinese_char_pattern.search(text))
22 |
23 | def contains_hangul(text: str) -> bool:
24 | """
25 | check if the text contains hangul
26 |
27 | Args:
28 | text (str): the text to check
29 |
30 | Returns:
31 | """
32 | return bool(hangul_pattern.search(text))
33 | def contains_ja_kana(text: str) -> bool:
34 | """
35 | check if the text contains Japanese or kana
36 |
37 | Args:
38 | text (str): the text to check
39 |
40 | Returns:
41 | """
42 | return bool(hiragana_katakana_pattern.search(text))
43 |
44 | def contains_zh_ja(text: str) -> bool:
45 | """
46 | check if the text contains Chinese or Japanese
47 |
48 | Args:
49 | text (str): the text to check
50 |
51 | Returns:
52 | bool: if the text contains Chinese or Japanese, return True, otherwise return False
53 | """
54 | return bool(zh_ja_pattern.search(text))
55 |
56 | def contains_only_kana(text: str) -> bool:
57 | """
58 | check if the text only contains hiragana or katakana
59 |
60 | Args:
61 | text (str): the text to check
62 |
63 | Returns:
64 | bool: if the text only contains hiragana or katakana, return True, otherwise return False
65 | """
66 | is_only_kana = True
67 | for char in text:
68 | if not hiragana_katakana_pattern.match(char):
69 | is_only_kana = False
70 | break
71 | return is_only_kana
72 |
73 |
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
1 | from .data.test_data import texts_zh_jp_ko_en, texts_de_fr_en
2 |
--------------------------------------------------------------------------------
/tests/data/__init__.py:
--------------------------------------------------------------------------------
1 | from .test_data import *
2 |
--------------------------------------------------------------------------------
/tests/data/correct_split.txt:
--------------------------------------------------------------------------------
1 | 我是 |VGroupChatBot|,|一个旨在支持多人通信的助手|,|通过可视化消息来帮助团队成员更好地交流|。|我可以帮助团队成员更好地整理和共享信息|,|特别是在讨论|、|会议和|Brainstorming|等情况下|。|你好我的名字是|西野くまです|my name is bob|很高兴认识你|どうぞよろしくお願いいたします|「|こんにちは|」|是什么意思|。
2 | 我的名字是|西野くまです|。|I am from Tokyo|, |日本の首都|。|今天的天气非常好
3 | 你好|,|今日はどこへ行きますか|?
4 | 你好|今日はどこへ行きますか|?
5 | 我的名字是|田中さんです|。
6 | 我喜欢吃寿司和拉面|おいしいです|。
7 | 今天|の天気はとてもいいですね|。
8 | 我在学习|日本語少し難しいです|。
9 | 日语真是|おもしろい|啊
10 | 你喜欢看|アニメ|吗|?
11 | 我想去日本旅行|、|特に京都に行きたいです|。
12 | 昨天|見た映画はとても感動的でした|。|我朋友是日本人|彼はとても優しいです|。
13 | 我们一起去|カラオケ|吧|、|楽しそうです|。
14 | 我的家在北京|、|でも|、|仕事で東京に住んでいます|。
15 | 我在学做日本料理|、|日本料理を作るのを習っています|。
16 | 你会说几种语言|、|何ヶ国語話せますか|?
17 | 我昨天看了一本书|、|その本はとても面白かったです|。
18 | 你最近好吗|、|最近どうですか|?
19 | 你最近好吗|最近どうですか|?
20 | 我在学做日本料理|와 한국 요리|、|日本料理を作るのを習っています|。
21 | 你会说几种语言|、|何ヶ国語話せますか|?|몇 개 언어를 할 수 있어요|?
22 | 我昨天看了一本书|、|その本はとても面白かったです|。|어제 책을 읽었는데|, |정말 재미있었어요|。
23 | 我们一起去逛街|와 쇼핑|、|買い物に行きましょう|。|쇼핑하러 가요|。
24 | 你最近好吗|、|最近どうですか|?|요즘 어떻게 지내요|?
25 | Bonjour|, |wie geht's dir |today|?
26 | Vielen Dank |merci beaucoup |for your help|.
27 | Ich bin müde |je suis fatigué |and I need some rest|.
28 | Ich mag dieses Buch |ce livre est intéressant |and it has a great story|.
29 | Ich mag dieses Buch|, |ce livre est intéressant|, |and it has a great story|.
30 | The shirt is |9|.|15 |dollars|.
31 | The shirt is |233 |dollars|.
32 | lang|-|split
33 | I have |10|, |€
34 | 日本のメディアでは|「|匿名掲示板|」|であると紹介されることが多いが|、|2003|年|1|月|7|日から全書き込みについて|IP|アドレスの記録・保存を始めており|、|厳密には匿名掲示板ではなくなっていると|CNET Japan|は報じている
35 | 日本語|(|にほんご|、|にっぽんご|)|は|、|日本国内や|、|かつての日本領だった国|、|そして国外移民や移住者を含む日本人同士の間で使用されている言語|。|日本は法令によって公用語を規定していないが|、|法令その他の公用文は全て日本語で記述され|、|各種法令において日本語を用いることが規定され|、|学校教育においては|「|国語|」|の教科として学習を行うなど|、|事実上日本国内において唯一の公用語となっている|。
36 | 日语是日本通用语及事实上的官方语言|。|没有精确的日语使用人口的统计|,|如果计算日本人口以及居住在日本以外的日本人|、|日侨和日裔|,|日语使用者应超过一亿三千万人|。
37 | MyGO|?,|你也喜欢|まいご|吗|?
38 | まいご|我可太喜欢了
39 | 我的名字|is|小明|。
40 | 我的|名字は小田です|。
41 | 我喜欢|食べる|蔬菜和水果|。
42 | 食べる|蔬菜和水果我喜欢|。
43 | 寿司和蔬菜我都喜欢|。
44 | すし|和蔬菜我都喜欢|。
45 | 寝る|和蔬菜我都喜欢|。
46 | 料理私は大好きです|。
47 | 游戏|大好きです|。
48 | 游戏|面白いです|。
49 | 初次见面|,|私の名前は|Doodle|です|。
50 | 中国語と日本語のミックス|中文和日文的混合
51 | 真的很难分离|玉子焼き
52 | 真的很难分离|すしリンゴ|苹果
53 | 全是汉字很难分离|果物|苹果
54 | 寂しい|的人
55 | 我的姓名|は|爱音
56 | 我的|名前は|爱音
57 | 我的|名字は|爱音
58 | 我的|名前|是爱音
59 |
--------------------------------------------------------------------------------
/tests/data/correct_split_merge_punc.txt:
--------------------------------------------------------------------------------
1 | 匿名掲示板
2 | 2024年の日本
3 | 你喜欢看|アニメ|吗?
4 | 我的名字是|田中さんです。
5 | 我的名字是田中|さんです。
6 | 日语真是|おもしろい|啊
7 | 衬衫的价格是9.15便士。
8 | 衬衫的价格是233亿元。
9 | 衬衫的价格是233亿元人民币。
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 | 我的名字是|西野くまです。|I am from Tokyo, |日本の首都。|今天的天气非常好
39 | 我是 |VGroupChatBot,|一个旨在支持多人通信的助手,通过可视化消息来帮助团队成员更好地交流。我可以帮助团队成员更好地整理和共享信息,特别是在讨论、会议和|Brainstorming|等情况下。你好我的名字是|西野くまです|my name is bob|很高兴认识你|どうぞよろしくお願いいたします「こんにちは」|是什么意思。
40 | Bonjour, |wie geht's dir |today?
41 | Vielen Dank |merci beaucoup |for your help.
42 | Ich bin müde |je suis fatigué |and I need some rest.
43 | Ich mag dieses Buch |ce livre est intéressant |and it has a great story.
44 | Ich mag dieses Buch, |ce livre est intéressant, |and it has a great story.
45 | The shirt is 9.15 dollars.
46 | The shirt is 233 dollars.
47 | lang-split
48 | I have 10, |€
49 | 日本のメディアでは「匿名掲示板」であると紹介されることが多いが、2003年1月7日から全書き込みについて|IP|アドレスの記録・保存を始めており、厳密には匿名掲示板ではなくなっていると|CNET Japan|は報じている
50 | 日本語(にほんご、にっぽんご)は、日本国内や、かつての日本領だった国、そして国外移民や移住者を含む日本人同士の間で使用されている言語。日本は法令によって公用語を規定していないが、法令その他の公用文は全て日本語で記述され、各種法令において日本語を用いることが規定され、学校教育においては「国語」の教科として学習を行うなど、事実上日本国内において唯一の公用語となっている。
51 | 日语是日本通用语及事实上的官方语言。没有精确的日语使用人口的统计,如果计算日本人口以及居住在日本以外的日本人、日侨和日裔,日语使用者应超过一亿三千万人。
--------------------------------------------------------------------------------
/tests/data/novel_1.txt:
--------------------------------------------------------------------------------
1 | 入学早々隣のクラスの絵奈は男子からの人気者。
2 |
3 | 新入生代表の挨拶も絵奈だったから目立ってしまって余計にあの子誰?ってなった。
4 |
5 | 絵奈と私には近所に住む幼稚園の頃から仲の良い幼なじみの澤村 和樹(さわむら かずき)という同じ中学の同級生でもある男子生徒がいる。
6 |
7 | 絵奈も私もかずくんと呼んでいる。
8 |
9 | かずくんは小学1年生の頃からサッカーをやっていて、高学年になる頃には県の選抜に選ばれたりと、丸山台中の新入生では期待の星。
10 |
11 | 何でユースとかにいかないで、公立の中学校でサッカー部なのかはわからない。
12 |
13 | かずくんとは絵奈より私の方が話が合うことが多かった。
14 |
15 | 小学生の頃もよく3人で遊んだりもしたし、中学生になってからも3人で朝一緒に行こうなんて約束していた。
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 | 3人でちょっと気まずくなって歩くのに耐えられない私が絵奈に言った。
61 |
62 | 「まぁまぁ、そんなツンケンしないでさ(汗)絵奈らしくないよ?かずくんも悪気があって言ったわけじゃないんだし、許してあげなよ(笑)」
63 |
64 | 「私らしいって何?紗羅は悔しくないの?残念な方の妹とか言われて。そもそも紗羅がそんなお気楽な感じだから駄目なんでしょ(怒)私は紗羅が悪口言われてたら自分のことのように面白くないよ(怒)」
65 |
66 | (怒りの矛先が私に向き始めたな…)
67 |
68 | 絵奈は温厚で優しく優等生タイプだけど、怒るとちょっと面倒くさいタイプ…何日も根に持つし、頑固なところもある。
69 |
70 | 私は対照的でカッとなっても、1日経てばそんなこと忘れてるし、あんまり根に持つこともない。頑固な所は同じかもしれないけど。
71 |
72 | 怒り出すと絵奈をなだめるのは結構大変…普段あんまり怒らないタイプだから、かもしれないけど…
73 |
74 | 「そのさ、絵奈は私と違っておとなしめな品のあるタイプなんだから、あんまりカッカしてるとイメージ崩れるよ?なんちゃって(笑)かずくんも少しびっくりしてるでしょ(汗)ほらほら、せっかく入学して登校初日なのに空気悪いのおしまいにして楽しく行こうよ♪(笑)」
75 |
76 | 一応、私がそう言うとカッカしてた絵奈も機嫌を少し直してくれて、かずくんもごめんって謝って一件落着し、その後は仲良く登校し中学校へ無事に到着。
77 |
78 | 「んじゃまた帰りね〜♪」そう言って、絵奈とは別々の教室へ。私はかずくんと同じクラスへ入る。
79 |
80 | 先生が来て、授業が始まる。
81 |
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 | (おぉーかずくんが助けてくれた(笑)もしかして、絵奈に朝怒られたから?もしかして、かずくんも昔から絵奈のことが…いや、考えるのはやめよう。私達3人は幼馴染。変に関係がおかしくなるのも嫌だし。)なんてふと思った。
107 |
108 | そして、この先、私達3人の関係は壊れていくことにこの時は気づかなかった…
--------------------------------------------------------------------------------
/tests/data/test_data.py:
--------------------------------------------------------------------------------
1 | texts_with_digit = [
2 | "你喜欢看アニメ吗?",
3 | "衬衫的价格是9.15便士",
4 | "衬衫的价格是233亿元",
5 | "衬衫的价格是233亿元人民币",
6 | ]
7 |
8 | texts_zh_jp_ko_en = [
9 | "我是 VGroupChatBot,一个旨在支持多人通信的助手,通过可视化消息来帮助团队成员更好地交流。我可以帮助团队成员更好地整理和共享信息,特别是在讨论、会议和Brainstorming等情况下。你好我的名字是西野くまですmy name is bob很高兴认识你どうぞよろしくお願いいたします「こんにちは」是什么意思。",
10 | "你好,我的名字是西野くまです。I am from Tokyo, 日本の首都。今天的天气非常好,sky is clear and sunny。おはようございます、皆さん!我们一起来学习吧。Learning languages can be fun and exciting。昨日はとても忙しかったので、今日は少しリラックスしたいです。Let's take a break and enjoy some coffee。中文、日本語、and English are three distinct languages, each with its own unique charm。希望我们能一起进步,一起成长。Let's keep studying and improving our language skills together. ありがとう!",
11 | "你好,今日はどこへ行きますか?",
12 | "你好今日はどこへ行きますか?",
13 | "我的名字是田中さんです。",
14 | "我喜欢吃寿司和拉面おいしいです。",
15 | "今天の天気はとてもいいですね。",
16 | "我在学习日本語少し難しいです。",
17 | "日语真是おもしろい啊",
18 | "你喜欢看アニメ吗?",
19 | "我想去日本旅行、特に京都に行きたいです。",
20 | "昨天見た映画はとても感動的でした。我朋友是日本人彼はとても優しいです。",
21 | "我们一起去カラオケ吧、楽しそうです。",
22 | "我的家在北京、でも、仕事で東京に住んでいます。",
23 | "我在学做日本料理、日本料理を作るのを習っています。",
24 | "你会说几种语言、何ヶ国語話せますか?",
25 | "我昨天看了一本书、その本はとても面白かったです。",
26 | "你最近好吗、最近どうですか?",
27 | "我在学做日本料理와 한국 요리、日本料理を作るのを習っています。",
28 | "你会说几种语言、何ヶ国語話せますか?몇 개 언어를 할 수 있어요?",
29 | "我昨天看了一本书、その本はとても面白かったです。어제 책을 읽었는데, 정말 재미있었어요。",
30 | "我们一起去逛街와 쇼핑、買い物に行きましょう。쇼핑하러 가요。",
31 | "你最近好吗、最近どうですか?요즘 어떻게 지내요?",
32 | ]
33 |
34 | texts_de_fr_en = [
35 | "Ich liebe Paris, c'est une belle ville, and the food is amazing!",
36 | "Berlin ist wunderbar, je veux y retourner, and explore more.",
37 | "Bonjour, wie geht's dir today?",
38 | "Die Musik hier ist fantastisch, la musique est superbe, and I enjoy it a lot.",
39 | "Guten Morgen, je t'aime, have a great day!",
40 | "Das Wetter ist heute schön, il fait beau aujourd'hui, and it's perfect for a walk.",
41 | "Ich mag dieses Buch, ce livre est intéressant, and it has a great story.",
42 | "Vielen Dank, merci beaucoup, for your help.",
43 | "Wir reisen nach Deutschland, nous voyageons en Allemagne, and we are excited.",
44 | "Ich bin müde, je suis fatigué, and I need some rest.",
45 | "Ich liebe Paris c'est une belle ville and the food is amazing!",
46 | "Berlin ist wunderbar je veux y retourner and explore more.",
47 | "Bonjour wie geht's dir today?",
48 | "Die Musik hier ist fantastisch la musique est superbe and I enjoy it a lot.",
49 | "Guten Morgen je t'aime have a great day!",
50 | "Das Wetter ist heute schön il fait beau aujourd'hui and it's perfect for a walk.",
51 | "Ich mag dieses Buch ce livre est intéressant and it has a great story.",
52 | "Vielen Dank merci beaucoup for your help.",
53 | "Wir reisen nach Deutschland nous voyageons en Allemagne and we are excited.",
54 | "Ich bin müde je suis fatigué and I need some rest.",
55 | ]
56 |
57 |
58 | texts_with_newline = [
59 | """newline,
60 | 123.
61 | abc
62 | 233""",
63 | ]
64 |
--------------------------------------------------------------------------------
/tests/split_acc.py:
--------------------------------------------------------------------------------
1 | import time
2 | from typing import List
3 |
4 | from split_lang.config import DEFAULT_LANG
5 | from split_lang.model import LangSectionType
6 | from split_lang.split.splitter import LangSplitter, SubString
7 | from split_lang.split.utils import PUNCTUATION
8 | from tests.test_config import TEST_DATA_FOLDER
9 |
10 |
11 | def get_corrected_split_result(
12 | splitter: LangSplitter, text_file_path: str
13 | ) -> List[List[SubString]]:
14 | """
15 | # 1. split by `|`
16 | # 2. convert to SubString, concat to list
17 | """
18 | corrected_split_result: List[List[SubString]] = []
19 |
20 | with open(text_file_path, "r", encoding="utf-8") as file:
21 | for line in file:
22 | substrings = line.strip().split("|")
23 | # print(substrings)
24 |
25 | substring_objects: List[SubString] = []
26 |
27 | current_index = 0
28 |
29 | for substring in substrings:
30 | is_punctuation = substring.strip() in PUNCTUATION
31 | is_digit = substring.strip().isdigit()
32 | lang = DEFAULT_LANG
33 | if is_punctuation:
34 | lang = "punctuation"
35 | elif is_digit:
36 | lang = "digit"
37 |
38 | substring_objects.append(
39 | SubString(
40 | lang=lang,
41 | text=substring,
42 | index=current_index,
43 | length=len(substring),
44 | is_punctuation=is_punctuation,
45 | is_digit=is_digit,
46 | )
47 | )
48 |
49 | current_index += len(substring)
50 | substring_objects = splitter._get_languages(
51 | lang_text_list=substring_objects,
52 | lang_section_type=LangSectionType.ALL,
53 | )
54 | corrected_split_result.append(substring_objects)
55 |
56 | return corrected_split_result
57 |
58 |
59 | def simple_test(splitter: LangSplitter, debug: bool = False):
60 | text_file_name = "correct_split.txt"
61 | correct_split = get_corrected_split_result(
62 | splitter=splitter, text_file_path=f"{TEST_DATA_FOLDER}/{text_file_name}"
63 | )
64 | correct_total_substring_len = 0
65 | test_total_substring_len = 0
66 | correct_split_num = 0
67 |
68 | original_strings = []
69 | test_split: List[List[SubString]] = []
70 | original_strings.clear()
71 | test_split.clear()
72 | total_text_len = sum([len(item) for item in correct_split])
73 | time_1 = time.time()
74 | # MARK: collect original_strings from .txt and test `split()`
75 | for str_index, correct_substrings in enumerate(correct_split):
76 | current_correct_num = 0
77 | correct_total_substring_len += len(correct_substrings)
78 | substrings_text = []
79 | for correct_substring in correct_substrings:
80 | substrings_text.append(correct_substring.text)
81 | original_string = "".join(substrings_text)
82 | original_strings.append(original_string)
83 | # print(original_string)
84 |
85 | test_split_substrings = splitter.split_by_lang(
86 | text=original_string,
87 | )
88 | test_split.append(test_split_substrings)
89 | test_total_substring_len += len(test_split_substrings)
90 |
91 | correct_substrings_text = [
92 | f"{' '*(3 - len(item.lang))}{'pun' if item.lang == 'punctuation' else item.lang}|{item.text}"
93 | for item in correct_substrings
94 | ]
95 | test_split_substrings_text = [
96 | f"{' '*(3 - len(item.lang))}{'pun' if item.lang == 'punctuation' else item.lang}|{item.text}"
97 | for item in test_split_substrings
98 | ]
99 |
100 | result_emoji_str = ''
101 |
102 | for test_substring in test_split_substrings:
103 | is_correct = False
104 | for correct_substring in correct_substrings:
105 | if (
106 | test_substring.text == correct_substring.text
107 | and test_substring.index == correct_substring.index
108 | ):
109 | correct_split_num += 1
110 | current_correct_num += 1
111 | result_emoji_str += '✅'
112 | is_correct = True
113 | break
114 | if not is_correct:
115 | result_emoji_str += '❌'
116 |
117 |
118 | if debug:
119 | print(f"{str_index+1}{result_emoji_str}{'-'*(100 - len(str(str_index+1)))}")
120 | print(f"original_string: {original_string}")
121 | print(f"correct result : {correct_substrings_text}")
122 | print(f"test result : {test_split_substrings_text}")
123 | print(
124 | f"acc : {current_correct_num}/{len(correct_substrings_text)}"
125 | )
126 |
127 | time_2 = time.time()
128 | precision = correct_split_num / correct_total_substring_len
129 | recall = correct_split_num / test_total_substring_len
130 | f1_score = 2 * precision * recall / (precision + recall)
131 | if debug:
132 | print(f"total substring num: {correct_total_substring_len}")
133 | print(f"test total substring num: {test_total_substring_len}")
134 | print(f"text acc num: {correct_split_num}")
135 | print(f"precision: {precision}")
136 | print(f"recall: {recall}")
137 | print(f"F1 Score: {f1_score}")
138 | print(f"time: {time_2-time_1}")
139 | print(f"process speed char/s: {total_text_len/(time_2-time_1)}")
140 |
141 | return precision
142 |
143 |
144 | def find_best_threshold(splitter: LangSplitter):
145 | best_score = 0
146 | best_threshold = 0
147 | for times in range(5):
148 | for i in range(1, 10):
149 | zeros = "0" * times
150 | threshold = float(f"0.{zeros}{str(i)}")
151 | score = simple_test(splitter=splitter, debug=False)
152 | if score > best_score:
153 | best_score = score
154 | best_threshold = threshold
155 | print(f"updated: best_f1_score: {best_score}")
156 | print(f"updated: best_threshold: {best_threshold}")
157 | print("------")
158 |
159 | print(f"best_score: {best_score}")
160 | print(f"best_threshold: {best_threshold}")
161 |
162 |
163 | def main():
164 | splitter = LangSplitter(
165 | merge_across_punctuation=False,
166 | merge_across_digit=False,
167 | # log_level=logging.DEBUG,
168 | )
169 | # find_best_threshold(splitter=splitter)
170 | simple_test(splitter=splitter, debug=True)
171 |
172 |
173 | if __name__ == "__main__":
174 | main()
175 |
--------------------------------------------------------------------------------
/tests/test_config.py:
--------------------------------------------------------------------------------
1 | TEST_DATA_FOLDER = "tests/data"
2 |
--------------------------------------------------------------------------------
/tests/test_split.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 | from split_lang import LangSplitter
4 |
5 | texts = [
6 | "你喜欢看アニメ吗?",
7 | "衬衫的价格是9.15便士",
8 | """。3入学早々隣のクラスの絵奈は男子からの人気者。
9 |
10 | 新入生代表の挨拶も絵奈だったから目立ってしまって余計にあの子誰?ってなった。
11 |
12 | 絵奈と私には近所に住む幼稚園の頃から仲の良い幼なじみの澤村 和樹(さわむら かずき)という同じ中学の同級生でもある男子生徒がいる。
13 |
14 | 絵奈も私もかずくんと呼んでいる。
15 |
16 | かずくんは小学1年生の頃からサッカーをやっていて、高学年になる頃には県の選抜に選ばれたりと、丸山台中の新入生では期待の星。
17 |
18 | 何でユースとかにいかないで、公立の中学校でサッカー部なのかはわからない。
19 |
20 | かずくんとは絵奈より私の方が話が合うことが多かった。
21 |
22 | 小学生の頃もよく3人で遊んだりもしたし、中学生になってからも3人で朝一緒に行こうなんて約束していた。
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 | 3人でちょっと気まずくなって歩くのに耐えられない私が絵奈に言った。
68 |
69 | 「まぁまぁ、そんなツンケンしないでさ(汗)絵奈らしくないよ?かずくんも悪気があって言ったわけじゃないんだし、許してあげなよ(笑)」
70 |
71 | 「私らしいって何?紗羅は悔しくないの?残念な方の妹とか言われて。そもそも紗羅がそんなお気楽な感じだから駄目なんでしょ(怒)私は紗羅が悪口言われてたら自分のことのように面白くないよ(怒)」
72 |
73 | (怒りの矛先が私に向き始めたな…)
74 |
75 | 絵奈は温厚で優しく優等生タイプだけど、怒るとちょっと面倒くさいタイプ…何日も根に持つし、頑固なところもある。
76 |
77 | 私は対照的でカッとなっても、1日経てばそんなこと忘れてるし、あんまり根に持つこともない。頑固な所は同じかもしれないけど。
78 |
79 | 怒り出すと絵奈をなだめるのは結構大変…普段あんまり怒らないタイプだから、かもしれないけど…
80 |
81 | 「そのさ、絵奈は私と違っておとなしめな品のあるタイプなんだから、あんまりカッカしてるとイメージ崩れるよ?なんちゃって(笑)かずくんも少しびっくりしてるでしょ(汗)ほらほら、せっかく入学して登校初日なのに空気悪いのおしまいにして楽しく行こうよ♪(笑)」
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 | (おぉーかずくんが助けてくれた(笑)もしかして、絵奈に朝怒られたから?もしかして、かずくんも昔から絵奈のことが…いや、考えるのはやめよう。私達3人は幼馴染。変に関係がおかしくなるのも嫌だし。)なんてふと思った。
114 |
115 | そして、この先、私達3人の関係は壊れていくことにこの時は気づかなかった…""",
116 | "入学早々隣のクラスの絵奈は男子からの人気者。新入生代表の挨拶も絵奈だったから目立ってしまって余計にあの子誰?ってなった。絵奈と私には近所に住む幼稚園の頃から仲の良い幼なじみの澤村 和樹(さわむら かずき)という同じ中学の同級生でもある男子生徒がいる。絵奈も私もかずくんと呼んでいる。かずくんは小学1年生の頃からサッカーをやっていて、高学年になる頃には県の選抜に選ばれたりと、丸山台中の新入生では期待の星。何でユースとかにいかないで、公立の中学校でサッカー部なのかはわからない。かずくんとは絵奈より私の方が話が合うことが多かった。小学生の頃もよく3人で遊んだりもしたし、中学生になってからも3人で朝一緒に行こうなんて約束していた。迎えた入学式後の初登校日。「忘れ物とかない?気を付けて行くんだよ。急がなくても充分間に合うからね。」私にだけそう言ったママ。絵奈には「いってらっしゃい」だけ。(二人とも一緒に行くのに、私だけ落ち着きないみたいに思われてる…?)「ほら、早く行くよ。ママ、いってきます♪」私の横で姉の絵奈が私に向かってそう言うと、ママには笑顔で挨拶をしていた。そそくさと出てしまう絵奈。「ちょ、ちょっと(汗)充分間に合うからねってママ言ってるじゃん(汗)んじゃいってきま〜す!」そう言って、私も絵奈の後を急いで追う。外に出るとかずくんがちょっと遠くから歩いてきているのが見えたので、私は大声で手を振って呼んだ。「かずく〜ん!!おはよー!!」私の大声に気づいたかずくんは走ってきた。「よぉ!!絵奈と残念な方の妹!(笑)」(こいつ…面白がってやがるな…)絵奈が新入生代表で目立ってしまって、私は自分のクラスで残念な方の妹だと、昨日、ふざけた男子に言われたのだ…そしてかずくんと私は同じクラスなのでそれを面白がって言ってきたのだろう…うるせぇーよって言ってやろうと思ったら、私より先に絵奈が口を開いた。「かずくん、残念な方の妹って何?」絵奈に聞かれるとちょっとバツが悪そうなかずくんが渋々答える。「いや、その、実は俺達のクラスで、ふざけて紗羅が残念な方の妹って男子にからかわれてたんだよ(汗)」「は?で、かずくんも面白がって、そんなこと言ってるわけ?くだらないことやめなよ。そういう時は普通、助ける男がカッコいいんでしょ。一緒になって紗羅が気にしてないからって最悪だよ。」絵奈はどちらかと言うと温厚で優しいタイプなのに少し怒っているように言ったので、かずくんはしょんぼり、私も珍しく絵奈が怒っているので少しびっくりした…3人でちょっと気まずくなって歩くのに耐えられない私が絵奈に言った。「まぁまぁ、そんなツンケンしないでさ(汗)絵奈らしくないよ?かずくんも悪気があって言ったわけじゃないんだし、許してあげなよ(笑)」「私らしいって何?紗羅は悔しくないの?残念な方の妹とか言われて。そもそも紗羅がそんなお気楽な感じだから駄目なんでしょ(怒)私は紗羅が悪口言われてたら自分のことのように面白くないよ(怒)」(怒りの矛先が私に向き始めたな…)絵奈は温厚で優しく優等生タイプだけど、怒るとちょっと面倒くさいタイプ…何日も根に持つし、頑固なところもある。私は対照的でカッとなっても、1日経てばそんなこと忘れてるし、あんまり根に持つこともない。頑固な所は同じかもしれないけど。怒り出すと絵奈をなだめるのは結構大変…普段あんまり怒らないタイプだから、かもしれないけど…「そのさ、絵奈は私と違っておとなしめな品のあるタイプなんだから、あんまりカッカしてるとイメージ崩れるよ?なんちゃって(笑)かずくんも少しびっくりしてるでしょ(汗)ほらほら、せっかく入学して登校初日なのに空気悪いのおしまいにして楽しく行こうよ♪(笑)」一応、私がそう言うとカッカしてた絵奈も機嫌を少し直してくれて、かずくんもごめんって謝って一件落着し、その後は仲良く登校し中学校へ無事に到着。「んじゃまた帰りね〜♪」そう言って、絵奈とは別々の教室へ。私はかずくんと同じクラスへ入る。先生が来て、授業が始まる。勉強は嫌いだ…(今日の給食なんだっけかなぁ〜)なんて授業に集中していないと見事に先生に当てられているのに気づかない。隣の席のかずくんが小さな声で「おーい。紗羅。おーい」って言っているのに気づく。(ん?何?教科書忘れたのかこいつは本当にサッカーしかできない奴だからなぁ。しょうがないなぁ)机をくっつけて教科書を見せてやろうとするとかずくんはオドオドする。「ちげーよ(汗)紗羅(汗)」また小さな声でかずくんが言った後には怒った先生が私の目の前にいた…「山本、お前何やってんだ?あくびしたり、外をボケーっと見てたり。同じ山本のお前の姉とは大違いだな。」先生は不機嫌そうに言う。(すいませんね〜絵奈だったら、ちゃんと授業聞いてるんでしょうね〜私は残念な方の妹なので(笑))なんて怒られているはずなのに思っていると、朝、絵奈から怒られたからか、かずくんが私を庇った。「その、先生。絵奈は絵奈。紗羅は紗羅ですよ。」かずくんの言葉に先生もちょっと悪いと思ったのか、ブツブツ言いながら戻っていき、私がボケーっとしていたことは流されて終わった。(おぉーかずくんが助けてくれた(笑)もしかして、絵奈に朝怒られたから?もしかして、かずくんも昔から絵奈のことが…いや、考えるのはやめよう。私達3人は幼馴染。変に関係がおかしくなるのも嫌だし。)なんてふと思った。そして、この先、私達3人の関係は壊れていくことにこの時は気づかなかった…",
117 | "衬衫的价格是9.15便士",
118 | "I have 10 €",
119 | "2.术语和定义 2.Terms and Definitions",
120 | "(2.2)术语和定义 (2.2)Terms and Definitions",
121 | "MyGO|?|,|你也喜欢|まいご|吗|?",
122 | ]
123 |
124 | lang_splitter = LangSplitter(log_level=logging.DEBUG)
125 |
126 |
127 | def test_split_step_by_step():
128 | for text in texts:
129 | pre_split_sections = lang_splitter.pre_split(
130 | text=text,
131 | )
132 | # for section in pre_split_sections:
133 | # print(section)
134 |
135 | split_sections = lang_splitter._split(
136 | pre_split_section=pre_split_sections,
137 | )
138 |
139 | # for section in split_sections:
140 | # print(section)
141 |
142 | after_merge_punctuation_sections = (
143 | lang_splitter._merge_substrings_across_punctuation_based_on_sections(
144 | sections=split_sections,
145 | )
146 | )
147 | # for section in after_merge_punctuation_sections:
148 | # print(section)
149 | after_merge_digit_sections = (
150 | lang_splitter._merge_substrings_across_digit_based_on_sections(
151 | sections=after_merge_punctuation_sections,
152 | )
153 | )
154 |
155 | # for section in after_merge_digit_sections:
156 | # print(section)
157 |
158 | after_merge_newline_sections = (
159 | lang_splitter._merge_substrings_across_newline_based_on_sections(
160 | sections=after_merge_digit_sections,
161 | )
162 | )
163 | # for section in after_merge_newline_sections:
164 | # print(section)
165 |
166 |
167 | def test_split():
168 | print("===========test_split===========")
169 | lang_splitter.merge_across_digit = False
170 | lang_splitter.merge_across_punctuation = False
171 | # lang_splitter.not_merge_punctuation = ["。"]
172 | for text in texts:
173 | substrings = lang_splitter.split_by_lang(text=text)
174 | for index, item in enumerate(substrings):
175 | print(f"{index}|{item.lang}:{item.text}")
176 | print("----------------------")
177 |
178 |
179 | def main():
180 | # test_split_step_by_step()
181 | test_split()
182 | pass
183 |
184 |
185 | if __name__ == "__main__":
186 | main()
187 |
--------------------------------------------------------------------------------
/tests/test_split_to_substrings.py:
--------------------------------------------------------------------------------
1 | from split_lang import LangSplitter
2 | from tests.data.test_data import texts_with_newline, texts_zh_jp_ko_en
3 |
4 | lang_splitter = LangSplitter(special_merge_for_zh_ja=True)
5 |
6 |
7 | def test_split_to_substring():
8 | for text in texts_zh_jp_ko_en:
9 | substr = lang_splitter.split_by_lang(
10 | text=text,
11 | )
12 | for _, item in enumerate(substr):
13 | print(item)
14 | # print(f"{index}|{item.lang}:{item.text}")
15 | print("----------------------")
16 |
17 | # for text in texts_de_fr_en:
18 | # substr = lang_splitter.split_by_lang(
19 | # text=text,
20 | # )
21 | # for _, item in enumerate(substr):
22 | # print(item)
23 | # # print(f"{index}|{item.lang}:{item.text}")
24 | # print("----------------------")
25 |
26 | # lang_splitter.merge_across_digit = False
27 | # for text in texts_with_digit:
28 | # substr = lang_splitter.split_by_lang(
29 | # text=text,
30 | # )
31 | # for _, item in enumerate(substr):
32 | # print(item)
33 | # # print(f"{index}|{item.lang}:{item.text}")
34 | # print("----------------------")
35 |
36 | # lang_splitter.merge_across_digit = True
37 | # lang_splitter.merge_across_punctuation = True
38 | # for text in texts_with_digit:
39 | # substr = lang_splitter.split_by_lang(
40 | # text=text,
41 | # )
42 | # for _, item in enumerate(substr):
43 | # print(item)
44 | # # print(f"{index}|{item.lang}:{item.text}")
45 | # print("----------------------")
46 |
47 |
48 | def test_split_to_substring_newline():
49 | for text in texts_with_newline:
50 | substr = lang_splitter.split_by_lang(
51 | text=text,
52 | )
53 | for index, item in enumerate(substr):
54 | print(item)
55 | # print(f"{index}|{item.lang}:{item.text}")
56 | print("----------------------")
57 |
58 |
59 | def main():
60 | # test_split_to_substring()
61 | test_split_to_substring_newline()
62 |
63 |
64 | if __name__ == "__main__":
65 | main()
66 |
--------------------------------------------------------------------------------