Repository navigation
Parse header lines in linear time - #46
Merged
Merged
Conversation
The header pattern's lazy text group was followed by an optional closing sequence and trailing whitespace, so a long run of spaces and tabs made matching quadratic. Match the rest of the line instead and strip the closing '#' sequence and trailing whitespace in _header_text. Also annotate the section stack for mypy --strict and note in the README that sections have no size limit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018jNL9rsodv5SMpDXVNMEia
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018jNL9rsodv5SMpDXVNMEia
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changed
markdown_chunkify/splitter.py:_HEADER_PATTERNis now^ {0,3}(#{1,6})(?:[ \t]+(.*))?$. A new_header_texthelper removes trailing spaces and tabs and an optional closing '#' sequence. As before, a closing sequence counts only when a space or tab comes before it, or when it is the whole text._find_headersadds a header only when its level is at mostmax_leveland its text is not empty.stackinsplit_textis annotatedlist[Section]formypy --strict.tests/test_splitter.py: one test splits"# a" + " \t" * 500_000 + "b"and checks the header. On main it ran for 30 s without finishing, and I stopped it. On this branch it takes about 30 ms.README.mdLimitations: a new bullet says sections have no size limit, so long sections need to be split before embedding.CHANGELOG.md0.3.0 "Fixed": an entry for this fix.Why
The old pattern had a lazy
(.*?)followed by an optional closing sequence and[ \t]*$. Each time the lazy group grew by one character, the regex scanned the trailing whitespace again, so matching time grew with the square of the line length. A header line with a 40,000-character run of spaces and tabs took about 18 seconds. 0.2.0 used the same lazy pattern, so this is also a fix compared with the last release. Text from PDF parsers can contain long runs of whitespace.Verification
_header_texton 400,017 lines and got identical results (117,461 of them headers). The lines were 400,000 random lines built from#, space, tab, letters, digits, non-ASCII letters, non-breaking space,\x0c,x#and##with 0-5 space indents, plus 17 fixed edge cases (## Title ##,# # #,## ##,# a#,# a ## ##,## # Fooand others).tests/test_splitter.py) atmax_level1-6. That is 24,018 comparisons, with 41,366 sections and 16,128 warnings.model_dump()output and warning messages were identical in every case. The random documents used headers at levels 1-7 with closing sequences and trailing whitespace, text, blank lines, fences, HTML blocks, front matter and CRLF line endings, and about 10% started with a BOM.MarkdownSplitter().split_text(...):"# a" + " \t" * n + "b""# a" + " \t" * n + "b""# a" + " \t" * n + "b""# a" + " " * n + "#x""# a" + " " * n + "#x""# a" + " " * n + "#x"ruff check,ruff format --checkandpytestall pass: 91 tests, 100% coverage, and the doctest is collected. The pydantic 2.0.2 job on Python 3.12 also passes: 91 tests.mypy --strict markdown_chunkifyleaves one error:models.py:30: Decorators on top of @property are not supported [prop-decorator]. This is a known mypy limitation with Pydanticcomputed_field.pyright markdown_chunkify: 0 errors, 0 warnings.