TwAddresses: clean up bisect func, move to algo mod, add key-getting funcs
This commit is contained in:
parent
1422e89eaf
commit
527fedd7d5
3 changed files with 149 additions and 30 deletions
36
mmgen/algo.py
Executable file
36
mmgen/algo.py
Executable file
|
|
@ -0,0 +1,36 @@
|
|||
#!/usr/bin/env python3
|
||||
#
|
||||
# MMGen Wallet, a terminal-based cryptocurrency wallet
|
||||
# Copyright (C)2013-2026 The MMGen Project <mmgen@tuta.io>
|
||||
# Licensed under the GNU General Public License, Version 3:
|
||||
# https://www.gnu.org/licenses
|
||||
# Public project repositories:
|
||||
# https://github.com/mmgen/mmgen-wallet
|
||||
# https://gitlab.com/mmgen/mmgen-wallet
|
||||
|
||||
"""
|
||||
algo: algorithms for the MMGen Wallet suite
|
||||
"""
|
||||
|
||||
def get_start(*, data, bot, top, target, bisect_key_func, match_key_func):
|
||||
"""
|
||||
Bisecting algorithm to find first entry in a sorted list a substring of which, as
|
||||
returned by match_key_func(), matches the requested target.
|
||||
|
||||
Return None if no match was found.
|
||||
|
||||
The target substring must sort below the desired start element but above any possible
|
||||
previous element.
|
||||
"""
|
||||
|
||||
assert bot <= top, f'get_start(): {bot=} > {top=}'
|
||||
assert top < len(data), f'get_start(): {top=} >= {len(data)=}'
|
||||
|
||||
while True:
|
||||
if bot == top:
|
||||
return bot if match_key_func(data[bot]) == target else None
|
||||
n = (top + bot) >> 1
|
||||
if bisect_key_func(data[n]) < target:
|
||||
bot = n + 1
|
||||
else:
|
||||
top = n
|
||||
|
|
@ -333,9 +333,20 @@ class TwAddresses(TwView):
|
|||
return e.is_used
|
||||
return None # addr not in tracking wallet
|
||||
|
||||
def get_target(self, key_in):
|
||||
return key_in
|
||||
|
||||
@staticmethod
|
||||
def get_bisect_key(d):
|
||||
return d.twmmid
|
||||
|
||||
@staticmethod
|
||||
def get_match_key(d):
|
||||
return d.al_id
|
||||
|
||||
def get_change_address(
|
||||
self,
|
||||
key_in,
|
||||
key,
|
||||
*,
|
||||
bot = None,
|
||||
top = None,
|
||||
|
|
@ -349,43 +360,23 @@ class TwAddresses(TwView):
|
|||
False: no unused addresses in wallet with requested AddrListID
|
||||
"""
|
||||
|
||||
def get_start(bot, top):
|
||||
"""
|
||||
bisecting algorithm to find first entry matching requested match key
|
||||
|
||||
Since 'btc' > 'F' and target sorts below the first twmmid of the match key
|
||||
stringwise, we can just search on raw twmmids.
|
||||
"""
|
||||
|
||||
# bisect target key sorts just below the desired target and never matches the bisect key
|
||||
pre_target_key = key + ':0'
|
||||
n = top >> 1
|
||||
|
||||
while True:
|
||||
|
||||
if bot == top:
|
||||
return bot if self.data[bot].al_id == key else None
|
||||
|
||||
if self.data[n].twmmid < pre_target_key:
|
||||
bot = n + 1
|
||||
else:
|
||||
top = n
|
||||
|
||||
n = (top + bot) >> 1
|
||||
|
||||
assert self.sort_key == 'twmmid'
|
||||
assert self.reverse is False
|
||||
|
||||
key = key_in
|
||||
from ..algo import get_start
|
||||
target = self.get_target(key)
|
||||
start = get_start(
|
||||
bot = 0 if bot is None else bot,
|
||||
top = len(self.data) - 1 if top is None else top)
|
||||
|
||||
data = self.data,
|
||||
bot = 0 if bot is None else bot,
|
||||
top = len(self.data) - 1 if top is None else top,
|
||||
target = target,
|
||||
bisect_key_func = self.get_bisect_key,
|
||||
match_key_func = self.get_match_key)
|
||||
if start is None:
|
||||
return None
|
||||
|
||||
for d in self.data[start:]:
|
||||
if d.al_id == key:
|
||||
if self.get_match_key(d) == target:
|
||||
if (
|
||||
not d.is_used
|
||||
and not d.twmmid in exclude
|
||||
|
|
|
|||
92
test/modtest_d/algo.py
Executable file
92
test/modtest_d/algo.py
Executable file
|
|
@ -0,0 +1,92 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
"""
|
||||
test.modtest_d.algo: algorithm unit tests for the MMGen suite
|
||||
"""
|
||||
|
||||
from ..include.common import vmsg
|
||||
|
||||
class unit_tests:
|
||||
|
||||
def get_start(self, name, ut, desc='get_start() bisecting algorithm'):
|
||||
|
||||
from mmgen.algo import get_start
|
||||
|
||||
def gs(data, bot, top, target, bisect_key_func, match_key_func):
|
||||
return get_start(
|
||||
data = data,
|
||||
bot = bot,
|
||||
top = top,
|
||||
target = target,
|
||||
bisect_key_func = bisect_key_func,
|
||||
match_key_func = match_key_func)
|
||||
|
||||
def bisect_key_func(d):
|
||||
return d
|
||||
|
||||
def substr_3(d):
|
||||
return d[:3]
|
||||
|
||||
fs = '{:<3} {:<3} {:<6} {:<14} {:<5} {}'
|
||||
|
||||
data = ('aaaa', 'aaba', 'aabb', 'aaca', 'aada', 'aaea', 'aaeb', 'aaec', 'aafa', 'aaga')
|
||||
last = len(data) - 1
|
||||
|
||||
vmsg('\nDATA:\n {}\n {}\n'.format(
|
||||
' '.join(str(n) for n in range(len(data))),
|
||||
' '.join(data)))
|
||||
|
||||
vmsg(fs.format('BOT', 'TOP', 'TARGET', 'MATCH_KEY_FUNC', 'START', 'START_VAL'))
|
||||
|
||||
for bot, top, target, match_key_func, chk in (
|
||||
(0, 0, 'aaa', substr_3, 0),
|
||||
(0, 1, 'aaa', substr_3, 0),
|
||||
(0, 2, 'aaa', substr_3, 0),
|
||||
(0, last, 'aaa', substr_3, 0),
|
||||
(0, last, 'aag', substr_3, 9),
|
||||
(last, last, 'aag', substr_3, 9),
|
||||
(7, last, 'aag', substr_3, 9),
|
||||
(8, last, 'aag', substr_3, 9),
|
||||
(0, last, 'aac', substr_3, 3),
|
||||
(2, 3, 'aac', substr_3, 3),
|
||||
(0, 3, 'aac', substr_3, 3),
|
||||
(3, 3, 'aac', substr_3, 3),
|
||||
(3, 4, 'aac', substr_3, 3),
|
||||
(2, 4, 'aac', substr_3, 3),
|
||||
(2, 5, 'aac', substr_3, 3),
|
||||
(1, 3, 'aac', substr_3, 3),
|
||||
(3, 5, 'aac', substr_3, 3),
|
||||
(3, last, 'aac', substr_3, 3),
|
||||
(4, 4, 'aad', substr_3, 4),
|
||||
(4, 4, 'aae', substr_3, None),
|
||||
(6, 6, 'aae', substr_3, 6),
|
||||
(3, last, 'aae', substr_3, 5),
|
||||
(5, 5, 'aae', substr_3, 5),
|
||||
(4, 5, 'aae', substr_3, 5),
|
||||
(3, 5, 'aae', substr_3, 5),
|
||||
(3, 3, 'aae', substr_3, None),
|
||||
(7, 7, 'aae', substr_3, 7),
|
||||
(8, 8, 'aae', substr_3, None),
|
||||
(8, last, 'aae', substr_3, None)):
|
||||
|
||||
start = gs(data, bot, top, target, bisect_key_func, match_key_func)
|
||||
|
||||
vmsg(fs.format(
|
||||
bot, top, target, match_key_func.__name__,
|
||||
'None' if start is None else start,
|
||||
'-' if start is None else f'{data[start]}'))
|
||||
|
||||
assert start == chk, f'{start=} != {chk=}'
|
||||
|
||||
vmsg('\nTesting error handling:')
|
||||
|
||||
bad1 = lambda: gs(data, 1, 0, 'aa', bisect_key_func, substr_3)
|
||||
bad2 = lambda: gs(data, 1, last+1, 'aa', bisect_key_func, substr_3)
|
||||
|
||||
bad_data = (
|
||||
('bot > top', 'AssertionError', '>', bad1),
|
||||
('top >= len(data)', 'AssertionError', '>=', bad2))
|
||||
|
||||
ut.process_bad_data(bad_data, pfx='')
|
||||
|
||||
return True
|
||||
Loading…
Add table
Add a link
Reference in a new issue