The REPL offers functionality for inserting, deleting, and searching within a red-black tree, along with the capability to generate an ASCII or graphical representation of the tree structure.
#!/usr/bin/env python3
import sys
from plot import plot_tree
from rbtree import RedBlackTree
COMMANDS = {
"insert": lambda rbt, x: rbt.insert(x),
"delete": lambda rbt, x: rbt.delete(x),
"search": lambda rbt, x: print(rbt.search(x).get_key()),
"print": lambda rbt: rbt.print_tree(),
"plot": lambda rbt: plot_tree(rbt.get_root()),
"exit": lambda rbt: exit(""),
}
def repl_loop() -> None:
rbt = RedBlackTree()
while True:
try:
line = input(">> ").rstrip()
except EOFError:
exit("")
tokens = line.split()
if not tokens:
continue
cmd = tokens[0]
if cmd not in COMMANDS:
print(f"Error: unknown command: {cmd}.")
continue
if cmd in {"print", "exit", "plot"}:
if len(tokens) != 1:
print("Error: extra argument.")
continue
COMMANDS[cmd](rbt)
continue
elif len(tokens) != 2:
print("Error: expected 2 arguments.")
continue
COMMANDS[cmd](rbt, tokens[1])
def main() -> None:
prompt = (
"Welcome to RB-Tree REPL.\n"
"Commands available: insert, delete, search, print, plot, exit.\n"
)
print(prompt)
repl_loop()
if __name__ == "__main__":
main()
The code has been formatted with black and isort.
Review Request:
Anything. Everything.
2 Answers 2
It doesn't make sense to pass a string to exit. That's supposed to be an integer returned to the operating system and accessible by a shell as the return code. If you want to indicate success, use 0 instead.
Don't use lambdas if you don't have to -
insert
anddelete
can be non-bound references toRedBlackTree.insert
andRedBlackTree.delete
respectivelysearch
should just be a functionprint
should be a non-bound reference toRedBlackTree.print_tree
plot
should just be a function
Really, you don't need to do length checking at all. Just splat-pass your *tokens
to the method, and if it throws, catch and print a message.
You should probably case-fold your command.
Don't hard-code your Commands available list; form that from a join()
on your dictionary keys.
-
\$\begingroup\$
exit
takes strings, but passing it a string is inappropriate here. If you pass it a string, it's treated as an error message. The exit code is 1 (indicating an error) and the string gets printed before exiting. \$\endgroup\$user2357112– user23571122024年04月10日 01:16:38 +00:00Commented Apr 10, 2024 at 1:16 -
1\$\begingroup\$ (Also
exit
is only supposed to be used in interactive mode, not in programs. Some Python execution settings don't actually haveexit
. You're supposed to usesys.exit
instead.) \$\endgroup\$user2357112– user23571122024年04月10日 01:17:46 +00:00Commented Apr 10, 2024 at 1:17
LGTM.
lambdas
Clearly the lambdas work, as-is.
Consider using def
instead,
in a dedicated module so we can iterate over them.
This would open up new possibilities,
such as tacking on a number-of-args decorator.
Consider adding type annotations.
DRY
COMMANDS = {
"insert": lambda rbt, x: rbt.insert(x),
...
def repl_loop() -> None:
...
if cmd in {"print", "exit", "plot"}:
if len(tokens) != 1:
This feels a bit redundant and hackish.
Consider putting num_args
next to the function definition:
"insert": (2, lambda rbt, x: rbt.insert(x)),
Consider using try
to detect when a "call with wrong
number of args" failed,
so you can print an informative diagnostic.
Consider using the inspect module to learn how many args a lambda requires.
-
\$\begingroup\$ Re DRY: this should be a
namedtuple
, probably. \$\endgroup\$STerliakov– STerliakov2024年04月17日 21:53:09 +00:00Commented Apr 17, 2024 at 21:53 -
1\$\begingroup\$ @SUTerliakov, Agreed, I am usually quite pleased to see namedtuple incorporated into someone’s design. \$\endgroup\$J_H– J_H2024年04月17日 22:03:07 +00:00Commented Apr 17, 2024 at 22:03