def ask(message, restrict_to=None):
"""prints message and asks user for input.
empty input is not allowed. if restrict_to is
given, we only accept entries from this list"""
if restrict_to is not None:
assert isinstance(restrict_to, list)
while True:
answer = input(message).strip()
if restrict_to is not None:
if answer not in restrict_to:
print("invalid input, try again")
else:
return answer
elif not answer:
print("invalid input, try again")
else:
return answer
def main_menu(entries):
print("""
PHONEBOOK v1.0
==============
Already {num_entries} entries in the phone book
Please choose:
1: add a new entry
2: lookup a number
3: show all entries
0: exit
""".format(num_entries=len(entries)))
return int(ask("what to do ? ", restrict_to = ["1", "2", "3", "0"]))
main_menu([])
The simplest data structure would be a dictionary mapping names to phone numbers. To get a more "falt tolerant" lookup we simplify a name by creating a spelling tolerant fingerprint.
Then we map this fingerprint to a list of tuples. Every tuple is a pair of name and phone number
def fingerprint(word):
""" inspired by https://www.wikiwand.com/en/Soundex """
word = word.lower()
head, tail = word[0], word[1:]
for c in "aeioyuhvwlr.-":
tail = tail.replace(c, "")
for c in "fpv":
tail = tail.replace(c, "b")
for c in "gjkqsxz":
tail = tail.replace(c, "c")
return head + tail
def create_lookup_key(name):
words = name.split(" ")
result = ""
for word in words:
result += fingerprint(word)
return result
print(create_lookup_key("Uwe Schmitt"))
print(create_lookup_key("A.K. Louis"))
print(create_lookup_key("Jenny Smith-Weber"))
from collections import defaultdict
def add_entry(name, phone_number, entries):
key = create_lookup_key(name)
entries[key].append((name, phone_number))
def lookup(name, entries):
key = create_lookup_key(name)
matches = entries.get(key, [])
return matches
def exists(name, entries):
for name_0, phone_number in lookup(name, entries):
if name_0 == name:
return True
return False
def list_all(entries):
for sub_entries in entries.values():
for (name, phone_number) in sub_entries:
yield name, phone_number
entries = defaultdict(list)
add_entry("Uwe Schmitt", "0041-076-123456", entries)
add_entry("A.K. Louis", "0049-0681-98765", entries)
add_entry("Janny Smith-Weber", "0033-0099-1234", entries)
print(entries)
print(lookup("uwe Schmatt", entries))
We choose the json
file format for storing our entries. This is a human readable text format which looks like dictionary with a few restrictions (e.g. keys must be strings):
import os
import json
# it is good style to write constant values in upper cases:
DEFAULT_FILE = "./phonebook.json"
def load_data(path=DEFAULT_FILE):
if os.path.exists(path):
with open(path, "r") as fh:
return json.load(fh)
else:
return defaultdict(list)
def store_data(phone_book_entries, path=DEFAULT_FILE):
with open(path, "w") as fh:
json.dump(phone_book_entries, fh, indent=4)
d = load_data("test.json")
add_entry("Uwe Schmitt", "0041-076-123456", d)
add_entry("A.K. Louis", "0049-0681-98765", d)
add_entry("Janny Smith-Weber", "0033-0099-1234", d)
store_data(d)
print(open(DEFAULT_FILE, "r").read())
def submenu_add_entry(phone_book_entries):
name = ask("name [. to abort] : ")
if name == ".":
print("aborted")
return
phone_number = ask("phone_number [. to abort]: ")
if name == ".":
print("aborted")
return
if exists(name, phone_book_entries):
print("entry for name {} exists: {}".format(name, phone_book_entries[name]))
overwrite = ask("do you want to ovewrite this [y/n] ?", ["y", "n"])
if overwrite == "n":
return
add_entry(name, phone_number, phone_book_entries)
def submenu_list_all(phone_book_entries):
names = sorted(phone_book_entries.keys())
if not names:
print("phone book is still empty")
else:
print("entries:")
for name, number in list_all(phone_book_entries):
print(" {:20s}: {}".format(name, number))
submenu_list_all(entries)
def submenu_lookup_entry(phone_book_entries):
name = ask("name: ")
count = 0
for name, number in lookup(name, phone_book_entries):
print("{name} has phone number {number}".format(name=name,
number=number))
count += 1
if count == 0:
print("no entry for {}".format(name))
We can avoid if
, elif
, else
for the chosen action by using a dictionary which maps the menu point number to the according function. This only works here if the functions for the sub menus have the same signature (same arguments):
def main():
routines = {1: submenu_add_entry,
2: submenu_lookup_entry,
3: submenu_list_all}
phone_book_entries = load_data()
while True:
todo = main_menu(phone_book_entries)
print()
if todo == 0:
store_data(phone_book_entries)
print("good bye")
return
# lookup and call function for chosen menu point
routines[todo](phone_book_entries)
main()