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

    "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:
            line = input(">> ").rstrip()
        except EOFError:

        tokens = line.split()

        if not tokens:

        cmd = tokens[0]

        if cmd not in COMMANDS:
            print(f"Error: unknown command: {cmd}.")

        if cmd in {"print", "exit", "plot"}:
            if len(tokens) != 1:
                print("Error: extra argument.")
        elif len(tokens) != 2:
            print("Error: expected 2 arguments.")
        COMMANDS[cmd](rbt, tokens[1])

def main() -> None:
    prompt = (
        "Welcome to RB-Tree REPL.\n"
        "Commands available: insert, delete, search, print, plot, exit.\n"


if __name__ == "__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 and delete can be non-bound references to RedBlackTree.insert and RedBlackTree.delete respectively
  • search should just be a function
  • print should be a non-bound reference to RedBlackTree.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\$ Apr 10 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 have exit. You're supposed to use sys.exit instead.) \$\endgroup\$ Apr 10 at 1:17



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.


    "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\$ Apr 17 at 21:53
  • 1
    \$\begingroup\$ @SUTerliakov, Agreed, I am usually quite pleased to see namedtuple incorporated into someone’s design. \$\endgroup\$
    – J_H
    Apr 17 at 22:03

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.