You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: CHANGELOG.md
+6Lines changed: 6 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,6 +1,11 @@
1
1
## Changelog 🔄
2
2
All notable changes to semchunk will be documented here. This project adheres to [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
3
3
4
+
## [4.1.1] - 2026-06-13
5
+
### Changed
6
+
- Sped up chunking, particularly of real-world documents at typical chunk sizes, by only ever materializing the comparatively rare multi-character whitespace runs when identifying the most structurally meaningful splitter, sparing `_split_text()` from materializing the far more numerous single-character runs (such as the single spaces between words) that can never be the longest run unless no longer run exists.
7
+
- Further sped up chunking by fusing the construction of splits' start offsets into a single pass and by iterating directly from the start of one chunk to the next rather than visiting, and skipping over, every split already merged into a chunk.
8
+
4
9
## [4.1.0] - 2026-06-12
5
10
### Changed
6
11
- Replaced [`mpire`](https://github.com/sybrenjansen/mpire) with the standard library's `multiprocessing` module for chunking multiple texts with multiple processes. On systems that support forking processes (namely, POSIX systems such as Linux and macOS), this makes [`tqdm`](https://github.com/tqdm/tqdm) semchunk's only dependency. On systems that do not support forking processes (namely, Windows), [`dill`](https://github.com/uqfoundation/dill) is now used to serialize the chunk function for spawned worker processes (as the standard library's `pickle` cannot serialize unpicklable token counters such as closures and lambdas), and so `dill` is now a Windows-only dependency, replacing the heavier `mpire`, which had depended on it.
@@ -191,6 +196,7 @@ All notable changes to semchunk will be documented here. This project adheres to
191
196
### Added
192
197
- Added the `chunk()` function, which splits text into semantically meaningful chunks of a specified size as determined by a provided token counter.
# NOTE Whitespace runs are matched with a `{2,}` quantifier rather than a `+` quantifier so that we only ever need the comparatively rare multi-character runs to find the longest run, sparing us from materializing the far more numerous single-character runs (e.g., the single spaces between words), which are never the longest run unless no multi-character run exists.
"""A tuple of `(escaped_preceder, compiled_pattern)` pairs used to target whitespace characters preceded by structurally meaningful non-whitespace splitters."""
# - the largest sequence of whitespace characters or, if the largest such sequence is only a single character and there exists a whitespace character preceded by a structurally meaningful non-whitespace splitter, then that whitespace character; and
85
97
# - a structurally meaningful non-whitespace splitter.
# If the splitter is only a single character, see if we can target whitespace characters that are preceded by structurally meaningful non-whitespace splitters to avoid splitting in the middle of sentences.
# Skip the split if it has already been added to a chunk.
508
-
ifiinskips:
509
-
continue
521
+
# Iterate through the splits, jumping straight to the start of each new chunk so that splits already merged into a chunk are never revisited.
522
+
i=0
523
+
524
+
whilei<num_splits:
525
+
split=splits[i]
526
+
527
+
# Compute the start offset of the split in the original text. NOTE `split_starts[i]` is `local_split_starts[i] + _start`.
528
+
split_start=local_split_starts[i] +_start
510
529
511
530
# If the split is over the chunk size, recursively chunk it.
512
531
iftoken_counter(split) >local_chunk_size:
@@ -522,10 +541,13 @@ def chunk(
522
541
chunks.extend(new_chunks)
523
542
offsets.extend(new_offsets)
524
543
544
+
# The next chunk starts at the very next split.
545
+
next_i=i+1
546
+
525
547
# If the split is equal to or under the chunk size, add it and any subsequent splits to a new chunk until the chunk size is reached.
526
548
else:
527
-
# Merge the split with subsequent splits until the chunk size is reached.
528
-
final_split_in_chunk_i, new_chunk=merge_splits(
549
+
# Merge the split with subsequent splits until the chunk size is reached. NOTE `next_i` is the index of the first split not included in the merged chunk and so the start of the next chunk.
550
+
next_i, new_chunk=merge_splits(
529
551
text=text,
530
552
split_starts=local_split_starts,
531
553
splitter_len=splitter_len,
@@ -536,20 +558,15 @@ def chunk(
536
558
high=num_splits_plus_one,
537
559
)
538
560
539
-
# Mark any splits included in the new chunk for exclusion from future chunks.
# If the splitter is not whitespace and the split is not the last split, add the splitter to the end of the latest chunk if doing so would not cause it to exceed the chunk size otherwise add the splitter as a new chunk.
# If the splitter is not whitespace and the chunk just added is not the last chunk, add the splitter to the end of the latest chunk if doing so would not cause it to exceed the chunk size otherwise add the splitter as a new chunk.
# If this is the first call, remove any empty chunks as well as chunks comprised entirely of whitespace and then overlap the chunks if desired and finally return the chunks, optionally with their offsets.
0 commit comments