Skip to content

Commit 64f9b09

Browse files
committed
Don't rely on .index to locate pairs in main chain
Just in case an object might override __eq__, use object identity.
1 parent 1d5e7ae commit 64f9b09

File tree

3 files changed

+22
-2
lines changed

3 files changed

+22
-2
lines changed

merge_insertion/__init__.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,12 @@ async def _bin_insert_index(array :Sequence[T], item :T, comp :Comparator) -> in
122122
left = mid + 1
123123
return left
124124

125+
def _ident_find(array :Sequence[T], item :T) -> int:
126+
for i,e in enumerate(array):
127+
if e is item:
128+
return i
129+
raise IndexError(f"failed to find item {item!r} in array")
130+
125131
async def merge_insertion_sort(array :Sequence[T], comparator :Comparator) -> Sequence[T]:
126132
"""Merge-Insertion Sort (Ford-Johnson algorithm) with async comparison.
127133
@@ -156,7 +162,7 @@ async def merge_insertion_sort(array :Sequence[T], comparator :Comparator) -> Se
156162
idx = await _bin_insert_index([ i[0] for i in main_chain ], item, comparator)
157163
else:
158164
assert len(pair)==2
159-
pair_idx = main_chain.index(pair)
165+
pair_idx = _ident_find(main_chain, pair)
160166
item = pair.pop()
161167
idx = await _bin_insert_index([ i[0] for i in main_chain[:pair_idx] ], item, comparator)
162168
main_chain.insert(idx, [item])

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
55
[project]
66
name = "merge-insertion"
77
description = "The merge-insertion sort (aka the Ford-Johnson algorithm) is optimized for using few comparisons."
8-
version = "1.0.0"
8+
version = "1.1.0"
99
authors = [ { name="Hauke Dämpfling", email="haukex@zero-g.net" } ]
1010
readme = "README.md"
1111
requires-python = ">=3.10"

tests/test_merge_insertion.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@
3030
async def _comp(ab :tuple[str,str]) -> Literal[0,1]:
3131
return 0 if ab[0] > ab[1] else 1
3232

33+
class AlwaysEqual:
34+
def __eq__(self, other):
35+
return isinstance(other, AlwaysEqual)
36+
3337
class TestMergeInsertionSort(unittest.IsolatedAsyncioTestCase):
3438

3539
def _test_comp(self, comp :uut.Comparator, max_calls :int, log :Optional[list[tuple[uut.T,uut.T]]] = None) -> uut.Comparator:
@@ -115,6 +119,16 @@ async def test_bin_insert_index(self):
115119
self.assertEqual( await bin_insert_index(a[:7], 'G', self._test_comp(_comp,3)), 3 )
116120
self.assertEqual( await bin_insert_index(a[:7], 'O', self._test_comp(_comp,3)), 7 )
117121

122+
def test_ident_find(self):
123+
ident_find = uut._ident_find # pyright: ignore[reportPrivateUsage] # pylint: disable=protected-access
124+
o = AlwaysEqual()
125+
a = (1, 'foo', AlwaysEqual(), o)
126+
self.assertEqual( a.index(AlwaysEqual()), 2 )
127+
self.assertEqual( a.index(o), 2 )
128+
self.assertEqual( ident_find(a, o), 3 )
129+
with self.assertRaises(IndexError):
130+
ident_find(a, AlwaysEqual())
131+
118132
async def test_merge_insertion_sort_detail(self):
119133
log :list[tuple[str,str]] = []
120134
self.assertEqual( await uut.merge_insertion_sort('ABCDE', self._test_comp(_comp, 7, log)), ['A','B','C','D','E'] )

0 commit comments

Comments
 (0)