Skip to content

Commit 230407e

Browse files
committed
Make splitter classes more efficient
1 parent f0e394d commit 230407e

3 files changed

Lines changed: 49 additions & 23 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
v0.6.0 (in development)
1+
v0.5.2 (in development)
22
-----------------------
33
- Support Python 3.14
44
- Drop support for Python 3.8 and 3.9
5+
- Make splitter classes more efficient
56

67
v0.5.1 (2024-12-01)
78
-------------------

src/linesep/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
more information.
1010
"""
1111

12-
__version__ = "0.6.0.dev1"
12+
__version__ = "0.5.2.dev1"
1313
__author__ = "John Thorvald Wodder II"
1414
__author_email__ = "linesep@varonathe.org"
1515
__license__ = "MIT"

src/linesep/splitters.py

Lines changed: 46 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,16 @@ def __init__(self) -> None:
3232
self._closed: bool = False
3333
#: Whether we've handled the first split segment yet
3434
self._first: bool = True
35+
#: The index of ``_buff`` at which to start the search for the next
36+
#: separator
37+
self._index: int = 0
3538

3639
@abstractmethod
37-
def _find_separator(self, data: AnyStr) -> tuple[int, int] | None:
40+
def _find_separator(self, data: AnyStr, index: int) -> tuple[int, int] | int:
3841
"""
39-
Find the first occurrence of a separator in ``data`` and return the
40-
separator's starting and ending indices; if no separator is found,
41-
return `None`.
42+
Find the first occurrence of a separator in ``data[index:]`` and return
43+
the separator's starting and ending indices; if no separator is found,
44+
return the index to start search from at subsequent calls.
4245
"""
4346
...
4447

@@ -66,17 +69,20 @@ def _output(self, item: AnyStr) -> None:
6669
def _split(self) -> None:
6770
"""Split up the current contents of `_buff`"""
6871
while self._buff:
69-
span = self._find_separator(self._buff)
70-
if span is None:
72+
span = self._find_separator(self._buff, self._index)
73+
if isinstance(span, int):
74+
self._index = span
7175
break
7276
start, end = span
7377
self._handle_segment(self._buff[:start], first=self._first)
7478
self._first = False
7579
self._handle_separator(self._buff[start:end])
7680
self._buff = self._buff[end:]
81+
self._index = 0
7782
if self._closed and self._buff is not None:
7883
self._handle_segment(self._buff, first=self._first, last=True)
7984
self._buff = None
85+
self._index = 0
8086

8187
def feed(self, data: AnyStr) -> None:
8288
"""
@@ -163,6 +169,7 @@ def reset(self) -> None:
163169
self._hold = None
164170
self._closed = False
165171
self._first = True
172+
self._index = 0
166173

167174
def getstate(self) -> SplitterState[AnyStr]:
168175
"""Retrieve a representation of the splitter's current state"""
@@ -172,6 +179,7 @@ def getstate(self) -> SplitterState[AnyStr]:
172179
hold=self._hold,
173180
closed=self._closed,
174181
first=self._first,
182+
index=self._index,
175183
)
176184

177185
def setstate(self, state: SplitterState[AnyStr]) -> None:
@@ -185,6 +193,7 @@ def setstate(self, state: SplitterState[AnyStr]) -> None:
185193
self._hold = state.hold
186194
self._closed = state.closed
187195
self._first = state.first
196+
self._index = state.index
188197

189198
def itersplit(self, iterable: Iterable[AnyStr]) -> Iterator[AnyStr]:
190199
"""
@@ -247,11 +256,14 @@ def __init__(self, separator: AnyStr, retain: bool = False) -> None:
247256
self._separator: AnyStr = separator
248257
self._retain: bool = retain
249258

250-
def _find_separator(self, data: AnyStr) -> tuple[int, int] | None:
259+
def _find_separator(self, data: AnyStr, index: int) -> tuple[int, int] | int:
251260
try:
252-
i = data.index(self._separator)
261+
i = data.index(self._separator, index)
253262
except ValueError:
254-
return None
263+
if data:
264+
return len(data) - len(self._separator) + 1
265+
else:
266+
return 0
255267
else:
256268
return (i, i + len(self._separator))
257269

@@ -365,10 +377,15 @@ def __init__(self, retain: bool = False, translate: bool = True) -> None:
365377
self._translate = translate
366378
self._strs: NewlineStrs[AnyStr] | None = None
367379

368-
def _find_separator(self, data: AnyStr) -> tuple[int, int] | None:
380+
def _find_separator(self, data: AnyStr, index: int) -> tuple[int, int] | int:
369381
if self._strs is None:
370382
self._strs = NewlineStrs.for_type(data)
371-
return self._strs.search(data, self.closed)
383+
if (span := self._strs.search(data, self.closed, pos=index)) is not None:
384+
return span
385+
elif data:
386+
return len(data) - 1
387+
else:
388+
return 0
372389

373390
def _handle_segment(
374391
self, item: AnyStr, first: bool = False, last: bool = False # noqa: U100
@@ -415,12 +432,15 @@ def __init__(self, retain: bool = False, translate: bool = True) -> None:
415432
self._retain = retain
416433
self._translate = translate
417434

418-
def _find_separator(self, data: str) -> tuple[int, int] | None:
419-
m = self.SEP_RGX.search(data)
420-
if m and not (m.group() == "\r" and m.end() == len(data) and not self.closed):
421-
return m.span()
435+
def _find_separator(self, data: str, index: int) -> tuple[int, int] | int:
436+
m = self.SEP_RGX.search(data, pos=index)
437+
if m:
438+
if m.group() == "\r" and m.end() == len(data) and not self.closed:
439+
return len(data) - 1
440+
else:
441+
return m.span()
422442
else:
423-
return None
443+
return len(data)
424444

425445
def _handle_segment(
426446
self, item: str, first: bool = False, last: bool = False # noqa: U100
@@ -467,12 +487,15 @@ def __init__(self, retain: bool = False, translate: bool = True) -> None:
467487
def _split(self) -> None:
468488
if self._strs is None and self._buff is not None:
469489
self._strs = NewlineStrs.for_type(self._buff)
470-
pos = 0
471490
while self._buff:
472491
assert self._strs is not None
473492
if self._hold is None:
474-
span = self._strs.search(self._buff, self.closed, pos=pos)
493+
span = self._strs.search(self._buff, self.closed, pos=self._index)
475494
if span is None:
495+
if self._buff:
496+
self._index = len(self._buff) - 1
497+
else:
498+
self._index = 0
476499
break
477500
start, end = span
478501
if (self._first and start == 0) or self._strs.match(
@@ -486,13 +509,14 @@ def _split(self) -> None:
486509
self._handle_separator(self._buff[start:end])
487510
self._first = False
488511
self._buff = self._buff[end:]
512+
self._index = 0
489513
else:
490514
if self._translate and self._buff[start:end] != self._strs.LF:
491515
self._buff = (
492516
self._buff[:start] + self._strs.LF + self._buff[end:]
493517
)
494518
end = start + 1
495-
pos = end
519+
self._index = end
496520
else:
497521
end2 = self._strs.match(self._buff, self.closed)
498522
if end2 is None:
@@ -504,7 +528,7 @@ def _split(self) -> None:
504528
else:
505529
self._handle_separator(self._buff[:end2])
506530
self._buff = self._buff[end2:]
507-
pos = 0
531+
self._index = 0
508532
if self._closed and self._buff is not None:
509533
assert self._strs is not None
510534
if self._buff:
@@ -517,7 +541,7 @@ def _split(self) -> None:
517541
self._output(self._hold)
518542
self._buff = None
519543

520-
def _find_separator(self, data: AnyStr) -> tuple[int, int] | None:
544+
def _find_separator(self, data: AnyStr, index: int) -> tuple[int, int] | int:
521545
raise NotImplementedError("Not used by this subclass") # pragma: no cover
522546

523547
def _handle_segment(
@@ -661,3 +685,4 @@ class SplitterState(Generic[AnyStr]):
661685
hold: AnyStr | None
662686
closed: bool
663687
first: bool
688+
index: int

0 commit comments

Comments
 (0)