├── .gitignore ├── README.md ├── ex04_parseargs └── parseargs.py ├── ex05_cat ├── cat.py └── test.txt ├── ex06_find └── find.py ├── ex07_grep └── grep.py ├── ex08_cut ├── cut.py └── cut_fp.py ├── ex09_sed ├── TODO ├── sed.py ├── test_sed.py ├── testfiles │ ├── cat.py │ ├── cut.py │ ├── find.py │ ├── grep.py │ └── parseargs.py └── tools.py ├── ex10_sort └── sort.py ├── ex11_uniq └── uniq.py ├── ex13_sllist ├── sllist.py └── test_sllist.py ├── ex14_dllist ├── dllist.py ├── dllist_after.py ├── dllist_done.py └── test_dllist.py ├── ex15_queuestack ├── queue.py ├── queue_done.py ├── queue_wrapped.py ├── stack.py ├── test_queue.py └── test_stack.py ├── ex16_sorts ├── dllist.py ├── sorting.py ├── sorting_quicksolved.py └── test_sorting.py ├── ex17_dictionary ├── dictionary.py ├── dllist.py └── test_dictionary.py ├── ex18_measure ├── dllist.py ├── sorting.py ├── sorting_quicksolved.py └── test_sorting.py ├── ex19_tuning ├── dllist.py ├── sorting.py ├── sorting_quicksolved.py └── test_sorting.py ├── ex20_bstree ├── bstree.py ├── bstree_analyzed.py └── test_bstree.py ├── ex21_bsearch ├── bsearch.py ├── bstree.py ├── dllist.py └── test_bsearch.py ├── ex22_suffixarray ├── ex22_pycon.py ├── sarray.py └── test_sarray.py ├── ex23_ternarytree ├── test_tstree.py └── tstree.py ├── ex24_urlrouter ├── bstree.py ├── dllist.py ├── test_bstree.py ├── test_dllist.py ├── test_tstree.py ├── test_urlrouter.py ├── test_urlrouter_benchmark.py ├── tstree.py └── urlrouter.py ├── ex25_xargs ├── xargs.py └── xargs_video.py ├── ex26_hexdump ├── hexdump.py └── test_hexdump.py ├── ex27_tr ├── test_tr.py ├── tr.py └── tr_spike.py ├── ex28_sh ├── sh.py ├── sh_spike.py └── test_sh.py ├── ex29_diff_patch ├── diff.py ├── diff_spike.py ├── sample.py └── test_diff.py ├── ex30_fsm ├── fsm.py ├── socket_fsm.py └── test_fsm.py ├── ex31_regex ├── fsm.py ├── refsm.py └── test_refsm.py ├── ex32_scanners ├── ex32.py ├── punypy_scanner.py ├── scanner.py └── test_scanner.py ├── ex33_parsers ├── ex33.py ├── punypy │ ├── parser.py │ ├── productions.py │ ├── run.py │ └── scanner.py └── tests │ ├── test_parser.py │ └── test_scanner.py ├── ex34_analyzers ├── ex34a.py ├── punypy │ ├── analyzer.py │ ├── parser.py │ ├── productions.py │ ├── run.py │ └── scanner.py └── tests │ ├── test_parser.py │ └── test_scanner.py ├── ex35_punypy ├── punypy │ ├── analyzer.py │ ├── interpreter.py │ ├── parser.py │ ├── productions.py │ ├── run.py │ └── scanner.py ├── test1.ppy └── tests │ ├── test_parser.py │ └── test_scanner.py ├── ex36_calc ├── calc │ ├── analyzer.py │ ├── parser.py │ ├── productions.py │ ├── run.py │ └── scanner.py ├── test1.calc └── tests │ ├── test_parser.py │ └── test_scanner.py ├── ex37_basic ├── basic │ ├── analyzer.py │ ├── parser.py │ ├── productions.py │ ├── run.py │ └── scanner.py ├── test1.bs ├── test2.bs └── tests │ ├── test_parser.py │ └── test_scanner.py ├── ex39_sql_create ├── 01_create.sql ├── 02_create.sql ├── 03_insert.sql ├── 04_insert.sql └── test.py ├── ex40_sql_reading ├── 01_select.sql └── 02_select.sql ├── ex41_sql_updating ├── 01_update.sql ├── 02_update.sql └── 03_insert_replace.sql ├── ex42_sql_deleting ├── 01_delete.sql └── 02_delete.sql ├── ex43_sql_alter ├── 01_alter.sql ├── 02_alter.sh-session └── 02_alter.sql ├── ex45_orm ├── 02_create.sql ├── simple_orm.py └── test_orm.py ├── ex46_blog ├── bin │ └── blog ├── blog │ ├── __init__.py │ └── run.py ├── setup.py └── tests │ ├── __init__.py │ ├── output │ ├── day1.html │ └── index.html │ ├── sample │ ├── config.json │ ├── day1.md │ ├── index.md │ └── template.html │ └── test_blog.py ├── ex47_bc ├── calc │ ├── analyzer.py │ ├── parser.py │ ├── productions.py │ ├── run.py │ └── scanner.py ├── test1.calc ├── test2.calc └── tests │ ├── test_language.py │ ├── test_parser.py │ └── test_scanner.py ├── ex48_ed ├── ed │ ├── ed.py │ ├── parser.py │ └── scanner.py ├── notes.txt ├── run.py └── tests │ ├── test_basics.py │ └── test_buffer.py ├── ex49_sed ├── ed │ ├── ed.py │ ├── parser.py │ └── scanner.py ├── run.py ├── script.sed └── tests │ ├── out.txt │ ├── test_basics.py │ └── test_buffer.py ├── ex50_vi ├── README.md ├── curses_test.py ├── ed │ ├── ed.py │ ├── parser.py │ └── scanner.py ├── notes.txt ├── run.py ├── test.py └── tests │ ├── test_basics.py │ └── test_buffer.py └── stats.py /.gitignore: -------------------------------------------------------------------------------- 1 | .pyc 2 | __pycache__ 3 | .cache 4 | .*.sw* 5 | .morepystats 6 | .sw* 7 | .coverage 8 | *.egg-info 9 | build 10 | dist 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Learn More Python The Hard Way Solutions 2 | ==== 3 | 4 | These are the in-progress solutions from the videos for my book [Learn More Python The Hard Way](http://learncodethehardway.org/more-python-book/) where I teach beginners how to do better at coding in Python 3. These solutions are straight from the videos, so no cleanup or refinement has been done. After the recording is completed I will go back and do a refinement on the solutions so people can see how I'd do it. 5 | 6 | The code in these solutions is under an MIT license: 7 | 8 | Copyright (c) 2016 Zed A. Shaw. 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy 11 | of this software and associated documentation files (the "Software"), to deal 12 | in the Software without restriction, including without limitation the rights 13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the Software is 15 | furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in all 18 | copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | SOFTWARE. 27 | -------------------------------------------------------------------------------- /ex04_parseargs/parseargs.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | parser = argparse.ArgumentParser() 3 | parser.add_argument('integers', metavar='N', type=int, nargs='+') 4 | parser.add_argument('-f', '--foo', help='foo help') 5 | parser.add_argument('-b', '--bar', help='bar help') 6 | parser.add_argument('-z', '--baz', help='baz help') 7 | parser.add_argument('-t', '--turn-on', action='store_true') 8 | parser.add_argument('-x', '--exclude', action='store_false') 9 | parser.add_argument('-s', '--start', action='store_true') 10 | args = parser.parse_args() 11 | 12 | print(args) 13 | -------------------------------------------------------------------------------- /ex05_cat/cat.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | parser = argparse.ArgumentParser() 3 | 4 | parser.add_argument('files', metavar='F', type=str, nargs='+') 5 | parser.add_argument('-n', '--numbers', action='store_true', 6 | help='Print line numbers') 7 | 8 | args = parser.parse_args() 9 | 10 | print(">>> parsed args: ", args) 11 | 12 | line_number = 1 13 | for in_file_name in args.files: 14 | in_file = open(in_file_name) 15 | if args.numbers: 16 | for line in in_file.readlines(): 17 | print(f"\t{line_number}\t{line}", end="") 18 | line_number += 1 19 | else: 20 | print(in_file.read()) 21 | -------------------------------------------------------------------------------- /ex05_cat/test.txt: -------------------------------------------------------------------------------- 1 | this 2 | is 3 | some 4 | stuff 5 | -------------------------------------------------------------------------------- /ex06_find/find.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | import sys 3 | import argparse 4 | 5 | def name_find(start, args): 6 | for f in start.rglob(args.name): 7 | print(f) 8 | 9 | def type_find(start, args): 10 | if args.type not in ['d','f']: 11 | print(f"Unknown type: {args.type}") 12 | sys.exit(1) 13 | 14 | for f in start.rglob(args.name or "*"): 15 | if args.type == "d" and f.is_dir(): 16 | print(f) 17 | elif args.type == "f" and f.is_file(): 18 | print(f) 19 | 20 | 21 | def find_files(args): 22 | start_path = Path(args.start[0]) 23 | 24 | if args.name and not args.type: 25 | name_find(start_path, args) 26 | elif args.type: 27 | type_find(start_path, args) 28 | else: 29 | print("You need either --name or --type") 30 | sys.exit(1) 31 | 32 | def parse_args(): 33 | parser = argparse.ArgumentParser() 34 | 35 | parser.add_argument('start', type=str, nargs=1) 36 | parser.add_argument('--name', type=str) 37 | parser.add_argument('--type' , type=str) 38 | 39 | return parser.parse_args() 40 | 41 | find_files(parse_args()) 42 | 43 | -------------------------------------------------------------------------------- /ex07_grep/grep.py: -------------------------------------------------------------------------------- 1 | import re 2 | import sys 3 | import argparse 4 | from pathlib import Path 5 | 6 | def parse_args(): 7 | parser = argparse.ArgumentParser() 8 | 9 | parser.add_argument('pattern', type=str, nargs=1) 10 | parser.add_argument('start', type=str, nargs=1) 11 | parser.add_argument('-r', action='store_true') 12 | 13 | return parser.parse_args() 14 | 15 | def find_in_file(name, pattern): 16 | try: 17 | lines = open(name).readlines() 18 | except UnicodeDecodeError: 19 | print(f"Binary file {name} matches.") 20 | return 21 | 22 | expr = re.compile(pattern) 23 | 24 | for line in lines: 25 | if expr.search(line): 26 | print(line, end="") 27 | 28 | args = parse_args() 29 | if args.r: 30 | start_path = Path(args.start[0]) 31 | for f in start_path.rglob("*"): 32 | if f.is_file(): 33 | find_in_file(f, args.pattern[0]) 34 | else: 35 | find_in_file(args.start[0], args.pattern[0]) 36 | -------------------------------------------------------------------------------- /ex08_cut/cut.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | _, delim, fields = sys.argv 4 | split_at = [int(x) for x in fields.split(',')] 5 | 6 | while True: 7 | try: 8 | line = input() 9 | cuts = line.split(delim) 10 | for i in split_at: 11 | print(f"{cuts[i]} ", end="") 12 | print() 13 | 14 | except EOFError: 15 | sys.exit(0) 16 | except IndexError: 17 | pass 18 | 19 | 20 | -------------------------------------------------------------------------------- /ex08_cut/cut_fp.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | _, delim, fields = sys.argv 4 | split_at = [int(x) for x in fields.split(',')] 5 | 6 | def lines_of_input(): 7 | try: 8 | while True: 9 | yield input() 10 | except EOFError: 11 | raise StopIteration 12 | 13 | split_lines = (line.split(delim) for line in lines_of_input()) 14 | 15 | cuts = (line[split_at[0]] for line in split_lines) 16 | 17 | print("\n".join(cut for cut in cuts)) 18 | 19 | -------------------------------------------------------------------------------- /ex09_sed/TODO: -------------------------------------------------------------------------------- 1 | = Print the current line number. 2 | a \ text Append text, which has each embedded newline preceded by a backslash. 3 | i \ text Insert text, which has each embedded newline preceded by a backslash. 4 | q [exit-code] 5 | Immediately quit the sed script without processing any more input, except that if auto-print 6 | is not disabled the current pattern space will be printed. The exit code argument is a GNU 7 | extension. 8 | Q [exit-code] 9 | Immediately quit the sed script without processing any more input. This is a GNU extension. 10 | r filename 11 | Append text read from filename. 12 | R filename 13 | Append a line read from filename. Each invocation of the command reads a line from the file. 14 | This is a GNU extension. 15 | { Begin a block of commands (end with a }). 16 | b label 17 | Branch to label; if label is omitted, branch to end of script. 18 | c \ text Replace the selected lines with text, which has each embedded newline preceded by a backslash. 19 | d Delete pattern space. Start next cycle. 20 | D If pattern space contains no newline, start a normal new cycle as if the d command was issued. 21 | Otherwise, delete text in the pattern space up to the first newline, and restart cycle with 22 | the resultant pattern space, without reading a new line of input. 23 | h H Copy/append pattern space to hold space. 24 | g G Copy/append hold space to pattern space. 25 | l List out the current line in a ``visually unambiguous'' form. 26 | l width 27 | List out the current line in a ``visually unambiguous'' form, breaking it at width characters. 28 | This is a GNU extension. 29 | n N Read/append the next line of input into the pattern space. 30 | p Print the current pattern space. 31 | P Print up to the first embedded newline of the current pattern space. 32 | t label 33 | If a s/// has done a successful substitution since the last input line was read and since the 34 | last t or T command, then branch to label; if label is omitted, branch to end of script. 35 | T label 36 | If no s/// has done a successful substitution since the last input line was read and since the 37 | last t or T command, then branch to label; if label is omitted, branch to end of script. This 38 | is a GNU extension. 39 | w filename 40 | Write the current pattern space to filename. 41 | W filename 42 | Write the first line of the current pattern space to filename. This is a GNU extension. 43 | x Exchange the contents of the hold and pattern spaces. 44 | y/source/dest/ 45 | Transliterate the characters in the pattern space which appear in source to the corresponding 46 | character in dest. 47 | -------------------------------------------------------------------------------- /ex09_sed/sed.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import tools 3 | 4 | script = sys.argv[1] 5 | files = sys.argv[2:] 6 | 7 | tools.sed(script, files) 8 | -------------------------------------------------------------------------------- /ex09_sed/test_sed.py: -------------------------------------------------------------------------------- 1 | 2 | def test_parse_script(): 3 | pass 4 | 5 | 6 | def test_do_s(): 7 | pass 8 | 9 | def test_print_uniq_lines(): 10 | pass 11 | 12 | -------------------------------------------------------------------------------- /ex09_sed/testfiles/cat.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | parser = argparse.ArgumentParser() 3 | 4 | parser.add_argument('files', metavar='F', type=str, nargs='+') 5 | parser.add_argument('-n', '--numbers', action='store_true', 6 | help='Print line numbers') 7 | 8 | args = parser.parse_args() 9 | 10 | print(">>> parsed args: ", args) 11 | 12 | line_number = 1 13 | for in_file_name in args.files: 14 | in_file = open(in_file_name) 15 | if args.numbers: 16 | for line in in_file.readlines(): 17 | print(f"\t{line_number}\t{line}", end="") 18 | line_number += 1 19 | else: 20 | print(in_file.read()) 21 | -------------------------------------------------------------------------------- /ex09_sed/testfiles/cut.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | _, delim, fields = sys.argv 4 | split_at = [int(x) for x in fields.split(',')] 5 | 6 | while True: 7 | try: 8 | line = input() 9 | cuts = line.split(delim) 10 | for i in split_at: 11 | print(f"{cuts[i]} ", end="") 12 | print() 13 | 14 | except EOFError: 15 | sys.exit(0) 16 | except IndexError: 17 | pass 18 | 19 | 20 | -------------------------------------------------------------------------------- /ex09_sed/testfiles/find.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | import sys 3 | import argparse 4 | 5 | def name_find(start, args): 6 | for f in start.rglob(args.name): 7 | print(f) 8 | 9 | def type_find(start, args): 10 | if args.type not in ['d','f']: 11 | print(f"Unknown type: {args.type}") 12 | sys.exit(1) 13 | 14 | for f in start.rglob(args.name or "*"): 15 | if args.type == "d" and f.is_dir(): 16 | print(f) 17 | elif args.type == "f" and f.is_file(): 18 | print(f) 19 | 20 | 21 | def find_files(args): 22 | start_path = Path(args.start[0]) 23 | 24 | if args.name and not args.type: 25 | name_find(start_path, args) 26 | elif args.type: 27 | type_find(start_path, args) 28 | else: 29 | print("You need either --name or --type") 30 | sys.exit(1) 31 | 32 | def parse_args(): 33 | parser = argparse.ArgumentParser() 34 | 35 | parser.add_argument('start', type=str, nargs=1) 36 | parser.add_argument('--name', type=str) 37 | parser.add_argument('--type' , type=str) 38 | 39 | return parser.parse_args() 40 | 41 | find_files(parse_args()) 42 | 43 | -------------------------------------------------------------------------------- /ex09_sed/testfiles/grep.py: -------------------------------------------------------------------------------- 1 | import re 2 | import sys 3 | import argparse 4 | from pathlib import Path 5 | 6 | def parse_args(): 7 | parser = argparse.ArgumentParser() 8 | 9 | parser.add_argument('pattern', type=str, nargs=1) 10 | parser.add_argument('start', type=str, nargs=1) 11 | parser.add_argument('-r', action='store_true') 12 | 13 | return parser.parse_args() 14 | 15 | def find_in_file(name, pattern): 16 | try: 17 | lines = open(name).readlines() 18 | except UnicodeDecodeError: 19 | print(f"Binary file {name} matches.") 20 | return 21 | 22 | expr = re.compile(pattern) 23 | 24 | for line in lines: 25 | if expr.search(line): 26 | print(line, end="") 27 | 28 | args = parse_args() 29 | if args.r: 30 | start_path = Path(args.start[0]) 31 | for f in start_path.rglob("*"): 32 | if f.is_file(): 33 | find_in_file(f, args.pattern[0]) 34 | else: 35 | find_in_file(args.start[0], args.pattern[0]) 36 | -------------------------------------------------------------------------------- /ex09_sed/testfiles/parseargs.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | parser = argparse.ArgumentParser() 3 | parser.add_argument('integers', metavar='N', type=int, nargs='+') 4 | parser.add_argument('-f', '--foo', help='foo help') 5 | parser.add_argument('-b', '--bar', help='bar help') 6 | parser.add_argument('-z', '--baz', help='baz help') 7 | parser.add_argument('-t', '--turn-on', action='store_true') 8 | parser.add_argument('-x', '--exclude', action='store_false') 9 | parser.add_argument('-s', '--start', action='store_true') 10 | args = parser.parse_args() 11 | 12 | print(args) 13 | -------------------------------------------------------------------------------- /ex09_sed/tools.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | def parse_script(script): 4 | if script[0] == "s": 5 | return ('s', script.split('/')[1:3]) 6 | elif script[0] == "r": 7 | return ('r', script.split(' ')[1:]) 8 | else: 9 | print("Error, only s supported") 10 | sys.exit(1) 11 | 12 | def do_s(file_name, pattern, replace): 13 | with open(file_name) as f: 14 | for line in f.readlines(): 15 | fixed = re.sub(pattern, replace, line) 16 | print(fixed, end="") 17 | 18 | def do_r(file_name, in_file_name): 19 | contents = open(in_file_name).read() 20 | 21 | with open(file_name) as f: 22 | for line in f.readlines(): 23 | print(line, contents, end='') 24 | 25 | def apply_script(command, file_name, args): 26 | if command == "s": 27 | do_s(file_name, *args) 28 | elif command == "r": 29 | do_r(file_name, *args) 30 | else: 31 | print("Not supported.") 32 | sys.exit(1) 33 | 34 | def sed(script, files): 35 | command, args = parse_script(script) 36 | for file_name in files: 37 | apply_script(command, file_name, args) 38 | 39 | def print_uniq_lines(file_list): 40 | all_lines = set() 41 | 42 | for f in file_list: 43 | all_lines |= set(f.readlines()) 44 | 45 | print("".join(all_lines)) 46 | 47 | -------------------------------------------------------------------------------- /ex10_sort/sort.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | lines = [] 4 | 5 | if len(sys.argv) > 1: 6 | for file_name in sys.argv[1:]: 7 | lines += [line for line in open(file_name).readlines() if len(line) > 20] 8 | else: 9 | while True: 10 | try: 11 | line = input() + "\n" 12 | lines.append(line) 13 | except EOFError: 14 | break 15 | 16 | print("".join(sorted(lines)), end="") 17 | -------------------------------------------------------------------------------- /ex11_uniq/uniq.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3.6 2 | 3 | import sys 4 | 5 | def print_uniq_lines(file_list): 6 | all_lines = set() 7 | 8 | for f in file_list: 9 | all_lines |= set(f.readlines()) 10 | 11 | print("".join(all_lines)) 12 | 13 | if len(sys.argv) > 1: 14 | print_uniq_lines(open(f) for f in sys.argv[1:]) 15 | else: 16 | print_uniq_lines([sys.stdin]) 17 | 18 | -------------------------------------------------------------------------------- /ex13_sllist/sllist.py: -------------------------------------------------------------------------------- 1 | class SingleLinkedListNode(object): 2 | 3 | def __init__(self, value, nxt): 4 | self.value = value 5 | self.next = nxt 6 | 7 | def __repr__(self): 8 | nval = self.next and self.next.value or None 9 | return f"[{self.value}:{repr(nval)}]" 10 | 11 | class SingleLinkedList(object): 12 | 13 | def __init__(self): 14 | self.begin = None 15 | self.end = None 16 | 17 | def push(self, obj): 18 | """Appends a new value on the end of the list.""" 19 | node = SingleLinkedListNode(obj, None) 20 | if self.begin == None: 21 | # nothing net 22 | self.begin = node 23 | self.end = self.begin 24 | else: 25 | self.end.next = node 26 | self.end = node 27 | assert self.begin != self.end 28 | 29 | assert self.end.next == None 30 | 31 | def pop(self): 32 | """Removes the last item and returns it.""" 33 | if self.end == None: 34 | return None 35 | elif self.end == self.begin: 36 | node = self.begin 37 | self.end = self.begin = None 38 | return node.value 39 | else: 40 | node = self.begin 41 | while node.next != self.end: 42 | node = node.next 43 | assert self.end != node 44 | self.end = node 45 | return node.next.value 46 | 47 | def shift(self, obj): 48 | """Another name for push.""" 49 | 50 | def unshift(self): 51 | """Removes the first item and returns it.""" 52 | 53 | def remove(self, obj): 54 | """Finds a matching item and removes it from the list.""" 55 | 56 | def first(self): 57 | """Returns a *reference* to the first item, does not remove.""" 58 | 59 | def last(self): 60 | """Returns a reference to the last item, does not remove.""" 61 | 62 | def count(self): 63 | """Counts the number of elements in the list.""" 64 | node = self.begin 65 | count = 0 66 | while node: 67 | count += 1 68 | node = node.next 69 | return count 70 | 71 | def get(self, index): 72 | """Get the value at index.""" 73 | 74 | def dump(self, mark): 75 | """Debugging function that dumps the contents of the list.""" 76 | 77 | -------------------------------------------------------------------------------- /ex13_sllist/test_sllist.py: -------------------------------------------------------------------------------- 1 | from sllist import * 2 | 3 | def test_push(): 4 | colors = SingleLinkedList() 5 | colors.push("Pthalo Blue") 6 | assert colors.count() == 1 7 | colors.push("Ultramarine Blue") 8 | assert colors.count() == 2 9 | colors.push("Ultramarine Violet") 10 | assert colors.count() == 3 11 | 12 | def test_pop(): 13 | colors = SingleLinkedList() 14 | colors.push("Magenta") 15 | colors.push("Alizarin") 16 | assert colors.pop() == "Alizarin" 17 | assert colors.pop() == "Magenta" 18 | assert colors.pop() == None 19 | -------------------------------------------------------------------------------- /ex14_dllist/dllist_done.py: -------------------------------------------------------------------------------- 1 | class DoubleLinkedListNode(object): 2 | 3 | def __init__(self, value, nxt, prev): 4 | self.value = value 5 | self.next = nxt 6 | self.prev = prev 7 | 8 | def __repr__(self): 9 | nval = self.next and self.next.value or None 10 | pval = self.prev and self.prev.value or None 11 | return f"[{self.value}, {repr(nval)}, {repr(pval)}]" 12 | 13 | class DoubleLinkedList(object): 14 | 15 | def __init__(self): 16 | self.begin = None 17 | self.end = None 18 | 19 | def push(self, obj): 20 | if self.end: 21 | node = DoubleLinkedListNode(obj, None, self.end) 22 | self.end.next = node 23 | self.end = node 24 | else: 25 | self.begin = DoubleLinkedListNode(obj, None, None) 26 | self.end = self.begin 27 | 28 | def pop(self): 29 | if self.end: 30 | # get the last node 31 | node = self.end 32 | 33 | if self.end == self.begin: 34 | # last node, kill them both 35 | self.end = None 36 | self.begin = None 37 | else: 38 | # not last, detach and move end 39 | self.end = node.prev 40 | self.end.next = None 41 | 42 | if self.end == self.begin: 43 | # we have only one node left, make begin and end same 44 | self.begin.next = None 45 | 46 | 47 | return node.value 48 | else: 49 | return None 50 | 51 | def unshift(self): 52 | if self.begin: 53 | node = self.begin 54 | 55 | if self.end == self.begin: 56 | self.end = None 57 | self.begin = None 58 | else: 59 | self.begin = node.next 60 | self.begin.prev = None 61 | 62 | return node.value 63 | else: 64 | return None 65 | 66 | def shift(self, obj): 67 | self.push(obj) 68 | 69 | def detach_node(self, node): 70 | if node == self.end: 71 | # only node or last node 72 | self.pop() 73 | elif node == self.begin: 74 | # first node 75 | self.unshift() 76 | else: 77 | # in the middle 78 | prev = node.prev 79 | nxt = node.next 80 | prev.next = nxt 81 | nxt.prev = prev 82 | 83 | 84 | def remove(self, obj): 85 | node = self.begin 86 | count = 0 87 | 88 | while node: 89 | if node.value == obj: 90 | self.detach_node(node) 91 | return count 92 | else: 93 | count += 1 94 | node = node.next 95 | 96 | return -1 97 | 98 | 99 | def first(self): 100 | return self.begin and self.begin.value or None 101 | 102 | def last(self): 103 | return self.end and self.end.value or None 104 | 105 | def count(self): 106 | node = self.begin 107 | count = 0 108 | 109 | while node: 110 | node = node.next 111 | count += 1 112 | 113 | return count 114 | 115 | 116 | def get(self, index): 117 | node = self.begin 118 | i = 0 119 | while node: 120 | if i == index: 121 | return node.value 122 | else: 123 | i += 1 124 | node = node.next 125 | 126 | return None 127 | 128 | def dump(self, mark='----'): 129 | node = self.begin 130 | print(mark) 131 | while node: 132 | print(node, " ", end='') 133 | node = node.next 134 | print() 135 | 136 | -------------------------------------------------------------------------------- /ex14_dllist/test_dllist.py: -------------------------------------------------------------------------------- 1 | from dllist import * 2 | 3 | 4 | def test_push(): 5 | colors = DoubleLinkedList() 6 | colors.push("Pthalo Blue") 7 | colors._invariant() 8 | assert colors.count() == 1 9 | colors.push("Ultramarine Blue") 10 | assert colors.count() == 2 11 | colors._invariant() 12 | 13 | def test_pop(): 14 | colors = DoubleLinkedList() 15 | colors.push("Magenta") 16 | colors._invariant() 17 | colors.push("Alizarin") 18 | colors.push("Van Dyke") 19 | colors._invariant() 20 | assert colors.pop() == "Van Dyke" 21 | colors._invariant() 22 | assert colors.get(1) == "Alizarin" 23 | assert colors.pop() == "Alizarin" 24 | assert colors.pop() == "Magenta" 25 | colors._invariant() 26 | assert colors.pop() == None 27 | 28 | def test_unshift(): 29 | colors = DoubleLinkedList() 30 | colors.shift("Viridian") 31 | colors.shift("Sap Green") 32 | colors.shift("Van Dyke") 33 | assert colors.unshift() == "Viridian" 34 | assert colors.unshift() == "Sap Green" 35 | assert colors.unshift() == "Van Dyke" 36 | assert colors.unshift() == None 37 | 38 | def test_shift(): 39 | colors = DoubleLinkedList() 40 | colors.shift("Cadmium Orange") 41 | assert colors.count() == 1 42 | 43 | colors.shift("Carbazole Violet") 44 | assert colors.count() == 2 45 | 46 | assert colors.pop() == "Carbazole Violet" 47 | assert colors.count() == 1 48 | assert colors.pop() == "Cadmium Orange" 49 | assert colors.count() == 0 50 | 51 | def test_remove(): 52 | colors = DoubleLinkedList() 53 | colors.push("Cobalt") 54 | colors.push("Zinc White") 55 | colors.push("Nickle Yellow") 56 | colors.push("Perinone") 57 | assert colors.remove("Cobalt") == 0 58 | colors._invariant() 59 | colors.dump("before perinone") 60 | assert colors.remove("Perinone") == 2 61 | colors._invariant() 62 | colors.dump("after perinone") 63 | assert colors.remove("Nickle Yellow") == 1 64 | colors._invariant() 65 | assert colors.remove("Zinc White") == 0 66 | colors._invariant() 67 | 68 | def test_first(): 69 | colors = DoubleLinkedList() 70 | colors.push("Cadmium Red Light") 71 | assert colors.first() == "Cadmium Red Light" 72 | colors.push("Hansa Yellow") 73 | assert colors.first() == "Cadmium Red Light" 74 | colors.shift("Pthalo Green") 75 | assert colors.first() == "Cadmium Red Light" 76 | 77 | def test_last(): 78 | colors = DoubleLinkedList() 79 | colors.push("Cadmium Red Light") 80 | assert colors.last() == "Cadmium Red Light" 81 | colors.push("Hansa Yellow") 82 | assert colors.last() == "Hansa Yellow" 83 | colors.shift("Pthalo Green") 84 | assert colors.last() == "Pthalo Green" 85 | 86 | def test_get(): 87 | colors = DoubleLinkedList() 88 | colors.push("Vermillion") 89 | assert colors.get(0) == "Vermillion" 90 | colors.push("Sap Green") 91 | assert colors.get(0) == "Vermillion" 92 | assert colors.get(1) == "Sap Green" 93 | colors.push("Cadmium Yellow Light") 94 | assert colors.get(0) == "Vermillion" 95 | assert colors.get(1) == "Sap Green" 96 | assert colors.get(2) == "Cadmium Yellow Light" 97 | assert colors.pop() == "Cadmium Yellow Light" 98 | assert colors.get(0) == "Vermillion" 99 | assert colors.get(1) == "Sap Green" 100 | assert colors.get(2) == None 101 | colors.pop() 102 | assert colors.get(0) == "Vermillion" 103 | colors.pop() 104 | assert colors.get(0) == None 105 | 106 | 107 | -------------------------------------------------------------------------------- /ex15_queuestack/queue.py: -------------------------------------------------------------------------------- 1 | class QueueNode(object): 2 | 3 | def __init__(self, value, nxt, prv): 4 | self.value = value 5 | self.next = nxt 6 | self.prev = prv 7 | 8 | def __repr__(self): 9 | nval = self.next and self.next.value or None 10 | pval = self.prev and self.prev.value or None 11 | return f"[{self.value}:next={repr(nval)}:prev={repr(pval)}]" 12 | 13 | 14 | class Queue(object): 15 | 16 | def __init__(self): 17 | self.tail = None 18 | self.head = None 19 | 20 | def shift(self, obj): 21 | """Shifts a new element onto the back of the queue.""" 22 | assert obj != None, "Cannot add None to queue." 23 | 24 | if self.head: 25 | # make a new node.prev = tail 26 | node = QueueNode(obj, None, self.tail) 27 | # set tail.next to None 28 | self.tail.next = node 29 | # set tail to node 30 | self.tail = node 31 | else: 32 | self.head = QueueNode(obj, None, None) 33 | self.tail = self.head 34 | 35 | def unshift(self): 36 | """Removes the element that is first in the queue.""" 37 | if self.head: 38 | node = self.head 39 | if self.head == self.tail: 40 | self.head = self.tail = None 41 | else: 42 | self.head = node.next 43 | self.head.prev = None 44 | return node.value 45 | else: 46 | return None 47 | 48 | def drop(self): 49 | """Take the tail item and forget about it.""" 50 | if self.head: 51 | if self.head == self.tail: 52 | self.head = None 53 | self.tail = None 54 | else: 55 | self.tail = self.tail.prev 56 | self.tail.next = None 57 | 58 | def first(self): 59 | """Returns a *reference* to the first item, does not remove.""" 60 | return self.head != None and self.head.value or None 61 | 62 | def empty(self): 63 | """Indicates if the Queue is empty.""" 64 | return self.head == None 65 | 66 | def count(self): 67 | """Counts the number of elements in the queue.""" 68 | # same old count 69 | count = 0 70 | node = self.head 71 | while node: 72 | count += 1 73 | node = node.next 74 | return count 75 | 76 | 77 | def dump(self, mark='----'): 78 | """Debugging function that dumps the contents of the queue.""" 79 | # same old dump 80 | 81 | -------------------------------------------------------------------------------- /ex15_queuestack/queue_done.py: -------------------------------------------------------------------------------- 1 | from time import sleep 2 | 3 | class QueueNode(object): 4 | 5 | def __init__(self, value, nxt, prv): 6 | self.value = value 7 | self.next = nxt 8 | self.prev = prv 9 | 10 | def __repr__(self): 11 | nval = self.next and self.next.value or None 12 | pval = self.prev and self.prev.value or None 13 | return f"[{self.value}:next={repr(nval)}:prev={repr(pval)}]" 14 | 15 | 16 | class Queue(object): 17 | 18 | def __init__(self): 19 | self.tail = None 20 | self.head = None 21 | 22 | def shift(self, obj): 23 | """Shifts a new element onto the back of the queue.""" 24 | if self.tail: 25 | node = QueueNode(obj, None, self.tail) 26 | self.tail.next = node 27 | self.tail = node 28 | else: 29 | self.head = QueueNode(obj, None, None) 30 | self.tail = self.head 31 | 32 | def unshift(self): 33 | """Removes the element that is first in the queue.""" 34 | if self.head: 35 | node = self.head 36 | 37 | if self.head == self.tail: 38 | self.head = None 39 | self.tail = None 40 | else: 41 | self.head = node.next 42 | self.head.prev = None 43 | 44 | return node.value 45 | 46 | else: 47 | return None 48 | 49 | def first(self): 50 | """Returns a *reference* to the first item, does not remove.""" 51 | return self.head and self.head.value or None 52 | 53 | def empty(self): 54 | """Indicates if the Queue is empty.""" 55 | return self.head == None 56 | 57 | def count(self): 58 | """Counts the number of elements in the queue.""" 59 | node = self.head 60 | i = 0 61 | while node: 62 | i += 1 63 | node = node.next 64 | 65 | return i 66 | 67 | def dump(self, mark='----'): 68 | """Debugging function that dumps the contents of the queue.""" 69 | print(mark) 70 | node = self.head 71 | while node: 72 | print(node, " ", end='') 73 | node = node.next 74 | print() 75 | 76 | 77 | -------------------------------------------------------------------------------- /ex15_queuestack/queue_wrapped.py: -------------------------------------------------------------------------------- 1 | from dllist import DoubleLinkedList() 2 | 3 | class Queue(object): 4 | 5 | def __init__(self): 6 | self.list = DoubleLinkedList() 7 | 8 | def shift(self, obj): 9 | """Shifts a new element onto the back of the queue.""" 10 | self.list.shift() 11 | 12 | def unshift(self): 13 | """Removes the element that is first in the queue.""" 14 | self.list.unshift() 15 | 16 | -------------------------------------------------------------------------------- /ex15_queuestack/stack.py: -------------------------------------------------------------------------------- 1 | class StackNode(object): 2 | 3 | def __init__(self, value, nxt): 4 | self.value = value 5 | self.next = nxt 6 | 7 | def __repr__(self): 8 | nval = self.next and self.next.value or None 9 | return f"[{self.value}:{repr(nval)}]" 10 | 11 | class Stack(object): 12 | 13 | def __init__(self): 14 | self.top = None 15 | 16 | def push(self, obj): 17 | """Pushes a new value to the top of the stack.""" 18 | self.top = StackNode(obj, self.top) 19 | 20 | def pop(self): 21 | """Pops the value that is currently on the top of the stack.""" 22 | if self.top: 23 | node = self.top 24 | self.top = node.next 25 | return node.value 26 | else: 27 | return None 28 | 29 | def first(self): 30 | """Returns a *reference* to the first item, does not remove.""" 31 | return self.top != None and self.top.value or None 32 | 33 | def count(self): 34 | """Counts the number of elements in the stack.""" 35 | # same old count 36 | count = 0 37 | node = self.top 38 | while node: 39 | count += 1 40 | node = node.next 41 | return count 42 | 43 | def dump(self, mark="----"): 44 | """Debugging function that dumps the contents of the stack.""" 45 | # same old dump 46 | print(">>>>> ", end="") 47 | node = self.top 48 | while node: 49 | print(node) 50 | print() 51 | 52 | -------------------------------------------------------------------------------- /ex15_queuestack/test_queue.py: -------------------------------------------------------------------------------- 1 | from queue import * 2 | 3 | def test_shift(): 4 | colors = Queue() 5 | colors.shift("Pthalo Blue") 6 | assert colors.count() == 1 7 | colors.shift("Ultramarine Blue") 8 | assert colors.count() == 2 9 | 10 | def test_unshift(): 11 | colors = Queue() 12 | colors.shift("Magenta") 13 | colors.shift("Alizarin") 14 | assert colors.unshift() == "Magenta" 15 | assert colors.unshift() == "Alizarin" 16 | assert colors.unshift() == None 17 | 18 | def test_first(): 19 | colors = Queue() 20 | colors.shift("Cadmium Red Light") 21 | assert colors.first() == "Cadmium Red Light" 22 | colors.shift("Hansa Yellow") 23 | assert colors.first() == "Cadmium Red Light" 24 | colors.shift("Pthalo Green") 25 | assert colors.first() == "Cadmium Red Light" 26 | 27 | def test_drop(): 28 | colors = Queue() 29 | colors.shift("Cad Red") 30 | colors.shift("Hansa Yellow") 31 | assert colors.count() == 2 32 | colors.drop() 33 | assert colors.count() == 1 34 | assert colors.first() == "Cad Red" 35 | colors.drop() 36 | assert colors.first() == None 37 | -------------------------------------------------------------------------------- /ex15_queuestack/test_stack.py: -------------------------------------------------------------------------------- 1 | from stack import * 2 | 3 | def test_push(): 4 | colors = Stack() 5 | colors.push("Pthalo Blue") 6 | assert colors.count() == 1 7 | colors.push("Ultramarine Blue") 8 | assert colors.count() == 2 9 | 10 | def test_pop(): 11 | colors = Stack() 12 | colors.push("Magenta") 13 | colors.push("Alizarin") 14 | assert colors.pop() == "Alizarin" 15 | assert colors.pop() == "Magenta" 16 | assert colors.pop() == None 17 | 18 | def test_top(): 19 | colors = Stack() 20 | colors.push("Cadmium Red Light") 21 | assert colors.first() == "Cadmium Red Light" 22 | colors.push("Hansa Yellow") 23 | assert colors.first() == "Hansa Yellow" 24 | colors.push("Pthalo Green") 25 | assert colors.first() == "Pthalo Green" 26 | 27 | -------------------------------------------------------------------------------- /ex16_sorts/dllist.py: -------------------------------------------------------------------------------- 1 | class DoubleLinkedListNode(object): 2 | 3 | def __init__(self, value, nxt, prev): 4 | self.value = value 5 | self.next = nxt 6 | self.prev = prev 7 | 8 | def __repr__(self): 9 | nval = self.next and self.next.value or None 10 | pval = self.prev and self.prev.value or None 11 | return f"[{self.value}, {repr(nval)}, {repr(pval)}]" 12 | 13 | class DoubleLinkedList(object): 14 | 15 | def __init__(self): 16 | self.begin = None 17 | self.end = None 18 | 19 | def push(self, obj): 20 | if self.end: 21 | node = DoubleLinkedListNode(obj, None, self.end) 22 | self.end.next = node 23 | self.end = node 24 | else: 25 | self.begin = DoubleLinkedListNode(obj, None, None) 26 | self.end = self.begin 27 | 28 | def pop(self): 29 | if self.end: 30 | # get the last node 31 | node = self.end 32 | 33 | if self.end == self.begin: 34 | # last node, kill them both 35 | self.end = None 36 | self.begin = None 37 | else: 38 | # not last, detach and move end 39 | self.end = node.prev 40 | self.end.next = None 41 | 42 | if self.end == self.begin: 43 | # we have only one node left, make begin and end same 44 | self.begin.next = None 45 | 46 | 47 | return node.value 48 | else: 49 | return None 50 | 51 | def unshift(self): 52 | if self.begin: 53 | node = self.begin 54 | 55 | if self.end == self.begin: 56 | self.end = None 57 | self.begin = None 58 | else: 59 | self.begin = node.next 60 | self.begin.prev = None 61 | 62 | return node.value 63 | else: 64 | return None 65 | 66 | def shift(self, obj): 67 | self.push(obj) 68 | 69 | def detach_node(self, node): 70 | if node == self.end: 71 | # only node or last node 72 | self.pop() 73 | elif node == self.begin: 74 | # first node 75 | self.unshift() 76 | else: 77 | # in the middle 78 | prev = node.prev 79 | nxt = node.next 80 | prev.next = nxt 81 | nxt.prev = prev 82 | 83 | 84 | def remove(self, obj): 85 | node = self.begin 86 | count = 0 87 | 88 | while node: 89 | if node.value == obj: 90 | self.detach_node(node) 91 | return count 92 | else: 93 | count += 1 94 | node = node.next 95 | 96 | return -1 97 | 98 | 99 | def first(self): 100 | return self.begin and self.begin.value or None 101 | 102 | def last(self): 103 | return self.end and self.end.value or None 104 | 105 | def count(self, start=None): 106 | node = self.begin 107 | count = 0 108 | 109 | while node: 110 | node = node.next 111 | count += 1 112 | 113 | return count 114 | 115 | 116 | def get(self, index): 117 | node = self.begin 118 | i = 0 119 | while node: 120 | if i == index: 121 | return node.value 122 | else: 123 | i += 1 124 | node = node.next 125 | 126 | return None 127 | 128 | def dump(self, mark='----'): 129 | node = self.begin 130 | print(mark) 131 | while node: 132 | print(node, " ", end='') 133 | node = node.next 134 | print() 135 | 136 | -------------------------------------------------------------------------------- /ex16_sorts/sorting.py: -------------------------------------------------------------------------------- 1 | from dllist import DoubleLinkedList 2 | from queue import Queue 3 | from random import randint 4 | 5 | def bubble_sort(numbers): 6 | """Sorts a list of numbers using bubble sort.""" 7 | while True: 8 | # start off assuming it's sorted 9 | is_sorted = True 10 | # comparing 2 at a time, skipping ahead 11 | node = numbers.begin.next 12 | while node: 13 | # loop through comparing node to the next 14 | if node.prev.value > node.value: 15 | # if the next is greater, then we need to swap 16 | node.prev.value, node.value = node.value, node.prev.value 17 | # oops, looks like we have to scan again 18 | is_sorted = False 19 | node = node.next 20 | 21 | # this is reset at the top but if we never swapped then it's sorted 22 | if is_sorted: break 23 | 24 | 25 | def count(node): 26 | count = 0 27 | 28 | while node: 29 | node = node.next 30 | count += 1 31 | 32 | return count 33 | 34 | 35 | def merge_sort(numbers): 36 | numbers.begin = merge_node(numbers.begin) 37 | 38 | # horrible way to get the end 39 | node = numbers.begin 40 | while node.next: 41 | node = node.next 42 | numbers.end = node 43 | 44 | 45 | def merge_node(start): 46 | """Sorts a list of numbers using merge sort.""" 47 | if start.next == None: 48 | return start 49 | 50 | mid = count(start) // 2 51 | 52 | # scan to the middle 53 | scanner = start 54 | for i in range(0, mid-1): 55 | scanner = scanner.next 56 | 57 | # set mid node right after the scan point 58 | mid_node = scanner.next 59 | # break at the mid point 60 | scanner.next = None 61 | mid_node.prev = None 62 | 63 | merged_left = merge_node(start) 64 | merged_right = merge_node(mid_node) 65 | 66 | return merge(merged_left, merged_right) 67 | 68 | def merge(left, right): 69 | """Performs the merge of two lists.""" 70 | result = None 71 | 72 | if left == None: return right 73 | if right == None: return left 74 | 75 | if left.value > right.value: 76 | result = right 77 | result.next = merge(left, right.next) 78 | else: 79 | result = left 80 | result.next = merge(left.next, right) 81 | 82 | result.next.prev = result 83 | return result 84 | 85 | def node_at(numbers, i): 86 | count = 0 87 | node = numbers.begin 88 | while node and count < i: 89 | node = node.next 90 | count += 1 91 | return node 92 | 93 | def quick_sort(numbers, lo, hi): 94 | """Sorts a list of numbers using quick sort. 95 | You must implement this one based on the p-code 96 | at https://en.wikipedia.org/wiki/Quicksort or 97 | any other reference. 98 | """ 99 | if lo < hi: 100 | p = quick_sort_partition(numbers, lo, hi) 101 | quick_sort(numbers, lo, p - 1) 102 | quick_sort(numbers, p + 1, hi) 103 | 104 | def quick_sort_partition(numbers, lo, hi): 105 | pivot = node_at(numbers, hi) 106 | i = lo - 1 107 | 108 | for j in range(lo, hi): 109 | node_j = node_at(numbers, j) 110 | if node_j.value < pivot.value: 111 | i += 1 112 | if i != j: 113 | node_i = node_at(numbers, i) 114 | node_i.value, node_j.value = node_j.value, node_i.value 115 | 116 | node_hi = node_at(numbers, hi) 117 | node_i = node_at(numbers, i + 1) 118 | 119 | if node_hi.value < node_i.value: 120 | node_hi.value, node_i.value = node_i.value, node_hi.value 121 | 122 | return i + 1 123 | -------------------------------------------------------------------------------- /ex16_sorts/test_sorting.py: -------------------------------------------------------------------------------- 1 | import sorting 2 | from dllist import DoubleLinkedList 3 | from random import randint 4 | 5 | max_numbers = 30 6 | 7 | def random_list(count): 8 | numbers = DoubleLinkedList() 9 | for i in range(count, 0, -1): 10 | numbers.shift(randint(0, 10000)) 11 | return numbers 12 | 13 | 14 | def is_sorted(numbers): 15 | node = numbers.begin 16 | while node and node.next: 17 | if node.value > node.next.value: 18 | return False 19 | else: 20 | node = node.next 21 | 22 | return True 23 | 24 | 25 | def test_bubble_sort(): 26 | numbers = random_list(max_numbers) 27 | 28 | sorting.bubble_sort(numbers) 29 | 30 | assert is_sorted(numbers) 31 | 32 | 33 | def test_merge_sort(): 34 | numbers = random_list(max_numbers * 31) 35 | 36 | sorting.merge_sort(numbers) 37 | 38 | assert is_sorted(numbers) 39 | 40 | def test_quick_sort(): 41 | numbers = random_list(max_numbers) 42 | 43 | sorting.quick_sort(numbers, 0, numbers.count() - 1) 44 | 45 | assert is_sorted(numbers) 46 | -------------------------------------------------------------------------------- /ex17_dictionary/dictionary.py: -------------------------------------------------------------------------------- 1 | from dllist import DoubleLinkedList 2 | 3 | class Dictionary(object): 4 | def __init__(self, num_buckets=256): 5 | """Initializes a Map with the given number of buckets.""" 6 | self.map = DoubleLinkedList() 7 | for i in range(0, num_buckets): 8 | self.map.push(DoubleLinkedList()) 9 | 10 | def hash_key(self, key): 11 | """Given a key this will create a number and then convert it to 12 | an index for the aMap's buckets.""" 13 | return hash(key) % self.map.count() 14 | 15 | def get_bucket(self, key): 16 | """Given a key, find the bucket where it would go.""" 17 | bucket_id = self.hash_key(key) 18 | return self.map.get(bucket_id) 19 | 20 | def get_slot(self, key, default=None): 21 | """ 22 | Returns either the bucket and node for a slot, or None, None 23 | """ 24 | bucket = self.get_bucket(key) 25 | 26 | if bucket: 27 | node = bucket.begin 28 | i = 0 29 | 30 | while node: 31 | if key == node.value[0]: 32 | return bucket, node 33 | else: 34 | node = node.next 35 | i += 1 36 | 37 | # fall through for both if and while above 38 | return bucket, None 39 | 40 | def get(self, key, default=None): 41 | """Gets the value in a bucket for the given key, or the default.""" 42 | bucket, node = self.get_slot(key, default=default) 43 | return node and node.value[1] or node 44 | 45 | def set(self, key, value): 46 | """Sets the key to the value, replacing any existing value.""" 47 | bucket, slot = self.get_slot(key) 48 | 49 | if slot: 50 | # the key exists, replace it 51 | slot.value = (key, value) 52 | else: 53 | # the key does not, append to create it 54 | bucket.push((key, value)) 55 | 56 | def delete(self, key): 57 | """Deletes the given key from the Map.""" 58 | bucket = self.get_bucket(key) 59 | node = bucket.begin 60 | 61 | while node: 62 | k, v = node.value 63 | if key == k: 64 | bucket.detach_node(node) 65 | break 66 | 67 | def list(self): 68 | """Prints out what's in the Map.""" 69 | bucket_node = self.map.begin 70 | while bucket_node: 71 | slot_node = bucket_node.value.begin 72 | while slot_node: 73 | print(slot_node.value) 74 | slot_node = slot_node.next 75 | bucket_node = bucket_node.next 76 | 77 | 78 | -------------------------------------------------------------------------------- /ex17_dictionary/dllist.py: -------------------------------------------------------------------------------- 1 | class DoubleLinkedListNode(object): 2 | 3 | def __init__(self, value, nxt, prev): 4 | self.value = value 5 | self.next = nxt 6 | self.prev = prev 7 | 8 | def __repr__(self): 9 | nval = self.next and self.next.value or None 10 | pval = self.prev and self.prev.value or None 11 | return f"[{self.value}, {repr(nval)}, {repr(pval)}]" 12 | 13 | class DoubleLinkedList(object): 14 | 15 | def __init__(self): 16 | self.begin = None 17 | self.end = None 18 | 19 | def push(self, obj): 20 | if self.end: 21 | node = DoubleLinkedListNode(obj, None, self.end) 22 | self.end.next = node 23 | self.end = node 24 | else: 25 | self.begin = DoubleLinkedListNode(obj, None, None) 26 | self.end = self.begin 27 | 28 | def pop(self): 29 | if self.end: 30 | # get the last node 31 | node = self.end 32 | 33 | if self.end == self.begin: 34 | # last node, kill them both 35 | self.end = None 36 | self.begin = None 37 | else: 38 | # not last, detach and move end 39 | self.end = node.prev 40 | self.end.next = None 41 | 42 | if self.end == self.begin: 43 | # we have only one node left, make begin and end same 44 | self.begin.next = None 45 | 46 | 47 | return node.value 48 | else: 49 | return None 50 | 51 | def unshift(self): 52 | if self.begin: 53 | node = self.begin 54 | 55 | if self.end == self.begin: 56 | self.end = None 57 | self.begin = None 58 | else: 59 | self.begin = node.next 60 | self.begin.prev = None 61 | 62 | return node.value 63 | else: 64 | return None 65 | 66 | def shift(self, obj): 67 | self.push(obj) 68 | 69 | def detach_node(self, node): 70 | if node == self.end: 71 | # only node or last node 72 | self.pop() 73 | elif node == self.begin: 74 | # first node 75 | self.unshift() 76 | else: 77 | # in the middle 78 | prev = node.prev 79 | nxt = node.next 80 | prev.next = nxt 81 | nxt.prev = prev 82 | 83 | 84 | def remove(self, obj): 85 | node = self.begin 86 | count = 0 87 | 88 | while node: 89 | if node.value == obj: 90 | self.detach_node(node) 91 | return count 92 | else: 93 | count += 1 94 | node = node.next 95 | 96 | return -1 97 | 98 | 99 | def first(self): 100 | return self.begin and self.begin.value or None 101 | 102 | def last(self): 103 | return self.end and self.end.value or None 104 | 105 | def count(self): 106 | node = self.begin 107 | count = 0 108 | 109 | while node: 110 | node = node.next 111 | count += 1 112 | 113 | return count 114 | 115 | 116 | def get(self, index): 117 | node = self.begin 118 | i = 0 119 | while node: 120 | if i == index: 121 | return node.value 122 | else: 123 | i += 1 124 | node = node.next 125 | 126 | return None 127 | 128 | def dump(self, mark='----'): 129 | node = self.begin 130 | print(mark) 131 | while node: 132 | print(node, " ", end='') 133 | node = node.next 134 | print() 135 | 136 | -------------------------------------------------------------------------------- /ex17_dictionary/test_dictionary.py: -------------------------------------------------------------------------------- 1 | from dictionary import Dictionary 2 | 3 | # create a mapping of state to abbreviation 4 | states = Dictionary() 5 | states.set('Oregon', 'OR') 6 | states.set('Florida', 'FL') 7 | states.set('California', 'CA') 8 | states.set('New York', 'NY') 9 | states.set('Michigan', 'MI') 10 | 11 | # create a basic set of states and some cities in them 12 | cities = Dictionary() 13 | cities.set('CA', 'San Francisco') 14 | cities.set('MI', 'Detroit') 15 | cities.set('FL', 'Jacksonville') 16 | 17 | # add some more cities 18 | cities.set('NY', 'New York') 19 | cities.set('OR', 'Portland') 20 | 21 | 22 | # print(out some cities 23 | print('-' * 10) 24 | print("NY State has: %s" % cities.get('NY')) 25 | print("OR State has: %s" % cities.get('OR')) 26 | 27 | # print(some states 28 | print('-' * 10) 29 | print("Michigan's abbreviation is: %s" % states.get('Michigan')) 30 | print("Florida's abbreviation is: %s" % states.get('Florida')) 31 | 32 | # do it by using the state then cities dict 33 | print('-' * 10) 34 | print("Michigan has: %s" % cities.get(states.get('Michigan'))) 35 | print("Florida has: %s" % cities.get(states.get('Florida'))) 36 | 37 | # print(every state abbreviation 38 | print('-' * 10) 39 | states.list() 40 | 41 | # print(every city in state 42 | print('-' * 10) 43 | cities.list() 44 | 45 | print('-' * 10) 46 | state = states.get('Texas') 47 | 48 | if not state: 49 | print("Sorry, no Texas.") 50 | 51 | # default values using ||= with the nil result 52 | # can you do this on one line? 53 | city = cities.get('TX', 'Does Not Exist') 54 | print("The city for the state 'TX' is: %s" % city) 55 | 56 | -------------------------------------------------------------------------------- /ex18_measure/dllist.py: -------------------------------------------------------------------------------- 1 | class DoubleLinkedListNode(object): 2 | 3 | def __init__(self, value, nxt, prev): 4 | self.value = value 5 | self.next = nxt 6 | self.prev = prev 7 | 8 | def __repr__(self): 9 | nval = self.next and self.next.value or None 10 | pval = self.prev and self.prev.value or None 11 | return f"[{self.value}, {repr(nval)}, {repr(pval)}]" 12 | 13 | class DoubleLinkedList(object): 14 | 15 | def __init__(self): 16 | self.begin = None 17 | self.end = None 18 | 19 | def push(self, obj): 20 | if self.end: 21 | node = DoubleLinkedListNode(obj, None, self.end) 22 | self.end.next = node 23 | self.end = node 24 | else: 25 | self.begin = DoubleLinkedListNode(obj, None, None) 26 | self.end = self.begin 27 | 28 | def pop(self): 29 | if self.end: 30 | # get the last node 31 | node = self.end 32 | 33 | if self.end == self.begin: 34 | # last node, kill them both 35 | self.end = None 36 | self.begin = None 37 | else: 38 | # not last, detach and move end 39 | self.end = node.prev 40 | self.end.next = None 41 | 42 | if self.end == self.begin: 43 | # we have only one node left, make begin and end same 44 | self.begin.next = None 45 | 46 | 47 | return node.value 48 | else: 49 | return None 50 | 51 | def unshift(self): 52 | if self.begin: 53 | node = self.begin 54 | 55 | if self.end == self.begin: 56 | self.end = None 57 | self.begin = None 58 | else: 59 | self.begin = node.next 60 | self.begin.prev = None 61 | 62 | return node.value 63 | else: 64 | return None 65 | 66 | def shift(self, obj): 67 | self.push(obj) 68 | 69 | def detach_node(self, node): 70 | if node == self.end: 71 | # only node or last node 72 | self.pop() 73 | elif node == self.begin: 74 | # first node 75 | self.unshift() 76 | else: 77 | # in the middle 78 | prev = node.prev 79 | nxt = node.next 80 | prev.next = nxt 81 | nxt.prev = prev 82 | 83 | 84 | def remove(self, obj): 85 | node = self.begin 86 | count = 0 87 | 88 | while node: 89 | if node.value == obj: 90 | self.detach_node(node) 91 | return count 92 | else: 93 | count += 1 94 | node = node.next 95 | 96 | return -1 97 | 98 | 99 | def first(self): 100 | return self.begin and self.begin.value or None 101 | 102 | def last(self): 103 | return self.end and self.end.value or None 104 | 105 | def count(self, start=None): 106 | node = self.begin 107 | count = 0 108 | 109 | while node: 110 | node = node.next 111 | count += 1 112 | 113 | return count 114 | 115 | 116 | def get(self, index): 117 | node = self.begin 118 | i = 0 119 | while node: 120 | if i == index: 121 | return node.value 122 | else: 123 | i += 1 124 | node = node.next 125 | 126 | return None 127 | 128 | def dump(self, mark='----'): 129 | node = self.begin 130 | print(mark) 131 | while node: 132 | print(node, " ", end='') 133 | node = node.next 134 | print() 135 | 136 | -------------------------------------------------------------------------------- /ex18_measure/test_sorting.py: -------------------------------------------------------------------------------- 1 | import sorting 2 | from dllist import DoubleLinkedList 3 | from random import randint 4 | 5 | max_numbers = 950 6 | 7 | def random_list(count): 8 | numbers = DoubleLinkedList() 9 | for i in range(count, 0, -1): 10 | numbers.shift(randint(0, 10000)) 11 | return numbers 12 | 13 | 14 | def is_sorted(numbers): 15 | node = numbers.begin 16 | while node and node.next: 17 | if node.value > node.next.value: 18 | return False 19 | else: 20 | node = node.next 21 | 22 | return True 23 | 24 | 25 | def test_bubble_sort(): 26 | numbers = random_list(max_numbers) 27 | 28 | sorting.bubble_sort(numbers) 29 | 30 | assert is_sorted(numbers) 31 | 32 | 33 | def test_merge_sort(): 34 | numbers = random_list(max_numbers) 35 | 36 | sorting.merge_sort(numbers) 37 | 38 | assert is_sorted(numbers) 39 | 40 | def test_quick_sort(): 41 | numbers = random_list(max_numbers) 42 | 43 | sorting.quick_sort(numbers, 0, numbers.count() - 1) 44 | 45 | assert is_sorted(numbers) 46 | 47 | if __name__ == "__main__": 48 | for i in range(0, 10): 49 | test_bubble_sort() 50 | test_merge_sort() 51 | test_quick_sort() 52 | 53 | 54 | -------------------------------------------------------------------------------- /ex19_tuning/test_sorting.py: -------------------------------------------------------------------------------- 1 | import sorting 2 | from dllist import DoubleLinkedList 3 | from random import randint 4 | 5 | max_numbers = 950 6 | 7 | def random_list(count): 8 | numbers = DoubleLinkedList() 9 | for i in range(count, 0, -1): 10 | numbers.shift(randint(0, 10000)) 11 | return numbers 12 | 13 | 14 | def is_sorted(numbers): 15 | node = numbers.begin 16 | while node and node.next: 17 | if node.value > node.next.value: 18 | return False 19 | else: 20 | node = node.next 21 | 22 | return True 23 | 24 | 25 | def test_bubble_sort(): 26 | numbers = random_list(max_numbers) 27 | 28 | sorting.bubble_sort(numbers) 29 | 30 | assert is_sorted(numbers) 31 | 32 | 33 | def test_merge_sort(): 34 | numbers = random_list(max_numbers) 35 | 36 | sorting.merge_sort(numbers) 37 | 38 | assert is_sorted(numbers) 39 | 40 | def test_quick_sort(): 41 | numbers = random_list(max_numbers) 42 | 43 | sorting.quick_sort(numbers, 0, numbers.count() - 1) 44 | 45 | assert is_sorted(numbers) 46 | 47 | if __name__ == "__main__": 48 | for i in range(100): 49 | test_quick_sort() 50 | 51 | 52 | -------------------------------------------------------------------------------- /ex20_bstree/bstree_analyzed.py: -------------------------------------------------------------------------------- 1 | class BSTreeNode(object): 2 | 3 | def __init__(self, key, value, left=None, right=None, parent=None): 4 | self.key = key 5 | self.value = value 6 | self.left = left 7 | self.right = right 8 | self.parent = parent 9 | 10 | def __repr__(self): 11 | return f"{self.key}={self.value}:{self.left}:{self.right}:^{self.parent}" 12 | 13 | 14 | class BSTree(object): 15 | 16 | def __init__(self): 17 | self.root = None 18 | 19 | def get(self, key, default=None): 20 | """Get the value for the tree.""" 21 | # start at the root 22 | 23 | # if node.key = node.value 24 | # return node.value 25 | 26 | # if no left and right child: 27 | # return None 28 | # elif key < node key: 29 | # You go left if the given key is less-than the node's key. 30 | # node = node.left 31 | # else: 32 | # You go right if the key is greater-than the node's key. 33 | # node = node.right 34 | 35 | # return None 36 | 37 | def set(self, key, value): 38 | """Sets the key to the value, replacing any existing value.""" 39 | # start at the root 40 | 41 | # if node.key = node.value 42 | # set value to new value 43 | 44 | # if no left and right child: 45 | # add a new node to the left or right depending on equality 46 | # elif key < node key: 47 | # You go left if the given key is less-than the node's key. 48 | # node = node.left 49 | # else: 50 | # You go right if the key is greater-than the node's key. 51 | # node = node.right 52 | 53 | def delete(self, key): 54 | """Deletes the given key from the data structure.""" 55 | # The D node is a "leaf" node because it has no children (not left or right). 56 | # Just remove it from the parent. 57 | 58 | # The D node has only one child (either left or right but not both). In 59 | # that case you can simply move the value of this child to the D node, 60 | # then delete the child. That effectively replaces the D node with the 61 | # child (or, "moves the child up"). 62 | 63 | # The D node has both a left and right child, which means it's time to do 64 | # some major surgery. First, find the minimum child of the D.right node 65 | # called the successor. Set the D.key to the successor.key, and then do 66 | # the same delete on this successor's children using its key. 67 | 68 | def list(self): 69 | """List the elements in the tree.""" 70 | # start at the root 71 | # go left 72 | # when there is no left print value 73 | # go right 74 | # when there is no right print value 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /ex20_bstree/test_bstree.py: -------------------------------------------------------------------------------- 1 | from bstree import * 2 | 3 | 4 | def test_BSTree_set(): 5 | colors = BSTree() 6 | colors.set("Cad", "Cad Red") 7 | assert colors.root.value == "Cad Red" 8 | colors.set("Cad", "Cadmium Red") 9 | assert colors.root.key == "Cad" 10 | assert colors.root.value == "Cadmium Red" 11 | colors.set("Pthalo", "Pthalo Blue") 12 | assert colors.root.right.key == "Pthalo" 13 | colors.set("Aliz", "Alizarin Crimson") 14 | assert colors.root.left.key == "Aliz" 15 | return colors 16 | 17 | def test_BSTree_get(): 18 | colors = test_BSTree_set() 19 | assert colors.get("Cad") == "Cadmium Red" 20 | assert colors.get("Pthalo") == "Pthalo Blue" 21 | assert colors.get("Aliz") == "Alizarin Crimson" 22 | 23 | 24 | def test_list(): 25 | colors = test_BSTree_set() 26 | colors.list() 27 | 28 | def test_delete(): 29 | colors = test_BSTree_set() 30 | colors.set("Gamb", "New Gamboge") 31 | assert colors.get("Gamb") == "New Gamboge" 32 | colors.delete("Pthalo") 33 | assert colors.get("Pthalo") == None 34 | assert colors.get("Gamb") == "New Gamboge" 35 | colors.delete("Aliz") 36 | assert colors.get("Aliz") == None 37 | 38 | colors = test_BSTree_set() 39 | colors.set("Gamb", "New Gamboge") 40 | colors.set("Prus", "Prussian Blue") 41 | colors.delete("Pthalo") 42 | assert colors.get("Gamb") == "New Gamboge" 43 | assert colors.get("Prus") == "Prussian Blue" 44 | colors.delete("Gamb") 45 | assert colors.get("Gamb") == None 46 | assert colors.get("Prus") 47 | colors.delete("Prus") 48 | assert colors.get("Prus") == None 49 | colors.delete("Cad") 50 | assert colors.root.key == "Aliz" 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /ex21_bsearch/bsearch.py: -------------------------------------------------------------------------------- 1 | import dllist 2 | import bstree 3 | 4 | def _search_list(data, elem, low, high): 5 | if low >= high: 6 | return None, -1 7 | else: 8 | mid = (high - low) // 2 + low 9 | mid_val = data[mid] 10 | 11 | if mid_val == elem: 12 | return elem, mid 13 | elif mid_val > elem: 14 | return _search_list(data, elem, low, mid) 15 | elif mid_val < elem: 16 | return _search_list(data, elem, mid + 1, high) 17 | 18 | def search_list(data, elem): 19 | return _search_list(data, elem, 0, len(data)) 20 | 21 | def search_list_iter(data, elem): 22 | low = 0 23 | high = len(data) 24 | 25 | while low < high: 26 | mid = (high - low) // 2 + low 27 | mid_val = data[mid] 28 | 29 | if mid_val == elem: 30 | return elem, mid 31 | elif mid_val > elem: 32 | high = mid 33 | elif mid_val < elem: 34 | low = mid + 1 35 | 36 | return None, -1 37 | 38 | def search_dllist(data, elem): 39 | dl = dllist.DoubleLinkedList() 40 | for x in data: dl.push(x) 41 | 42 | low = 0 43 | high = dl.count() 44 | 45 | while low < high: 46 | mid = (high - low) // 2 + low 47 | mid_val = dl.get(mid) 48 | 49 | if mid_val == elem: 50 | return elem, mid 51 | elif mid_val > elem: 52 | high = mid 53 | elif mid_val < elem: 54 | low = mid + 1 55 | 56 | return None, -1 57 | 58 | 59 | def search_btree(data, elem): 60 | """Unlike the other functions this doesn't expect sorted data.""" 61 | tree = bstree.BSTree() 62 | for i, key in enumerate(data): 63 | # use value for index 64 | tree.set(key, i) 65 | 66 | index = tree.get(elem) 67 | return index != None and (elem, index) or (None, -1) 68 | 69 | -------------------------------------------------------------------------------- /ex21_bsearch/test_bsearch.py: -------------------------------------------------------------------------------- 1 | import bsearch 2 | 3 | 4 | def test_bsearch_list(): 5 | data = sorted([5,3,7,1,9,20]) 6 | assert bsearch.search_list(data, 5) == (5, 2) 7 | assert bsearch.search_list(data, 1) == (1, 0) 8 | assert bsearch.search_list(data, 20) == (20, len(data) - 1) 9 | assert bsearch.search_list(data, 9) == (9, 4) 10 | assert bsearch.search_list(data, 100) == (None, -1) 11 | assert bsearch.search_list(data, -1) == (None, -1) 12 | 13 | def test_bsearch_list_iter(): 14 | data = sorted([5,3,7,1,9,20]) 15 | assert bsearch.search_list_iter(data, 5) == (5, 2) 16 | assert bsearch.search_list_iter(data, 1) == (1, 0) 17 | assert bsearch.search_list_iter(data, 20) == (20, len(data) - 1) 18 | assert bsearch.search_list_iter(data, 9) == (9, 4) 19 | assert bsearch.search_list_iter(data, 100) == (None, -1) 20 | assert bsearch.search_list_iter(data, -1) == (None, -1) 21 | 22 | def test_bsearch_dllist(): 23 | data = sorted([5,3,7,1,9,20]) 24 | assert bsearch.search_dllist(data, 5) == (5, 2) 25 | assert bsearch.search_dllist(data, 1) == (1, 0) 26 | assert bsearch.search_dllist(data, 20) == (20, len(data) - 1) 27 | assert bsearch.search_dllist(data, 9) == (9, 4) 28 | assert bsearch.search_dllist(data, 100) == (None, -1) 29 | assert bsearch.search_dllist(data, -1) == (None, -1) 30 | 31 | def test_btree_search(): 32 | # for btree, adding sorted data just makes it a list 33 | data = [5,3,7,1,9,20] 34 | assert bsearch.search_btree(data, 5) == (5, 0) 35 | assert bsearch.search_btree(data, 1) == (1, 3) 36 | assert bsearch.search_btree(data, 20) == (20, len(data) - 1) 37 | assert bsearch.search_btree(data, 9) == (9, 4) 38 | assert bsearch.search_btree(data, 100) == (None, -1) 39 | assert bsearch.search_btree(data, -1) == (None, -1) 40 | 41 | 42 | -------------------------------------------------------------------------------- /ex22_suffixarray/ex22_pycon.py: -------------------------------------------------------------------------------- 1 | >>> magic = "abracadabra" 2 | >>> magic_sa = [] 3 | >>> for i in range(0, len(magic)): 4 | ... magic_sa.append(magic[i:]) 5 | ... 6 | >>> magic_sa 7 | ['abracadabra', 'bracadabra', 'racadabra', 'acadabra', 8 | 'cadabra', 'adabra', 'dabra', 'abra', 'bra', 'ra', 'a'] 9 | >>> magic_sa = sorted(magic_sa) 10 | >>> magic_sa 11 | ['a', 'abra', 'abracadabra', 'acadabra', 'adabra', 'bra', 12 | 'bracadabra', 'cadabra', 'dabra', 'ra', 'racadabra'] 13 | >>> 14 | -------------------------------------------------------------------------------- /ex22_suffixarray/sarray.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | class SuffixArray(object): 4 | 5 | def __init__(self, instr): 6 | self.instr = instr 7 | sfx = [] 8 | for i in range(0, len(instr)): 9 | sfx.append((self.instr[i:], i)) 10 | 11 | self.sarray = sorted(sfx) 12 | 13 | def search(self, what): 14 | """ 15 | Uses a binary search to find two indexes: 16 | first is where it is in self.sarray 17 | second is where is in self.instr 18 | """ 19 | 20 | low = 0 21 | high = len(self.sarray) 22 | 23 | while low < high: 24 | mid = (high - low) // 2 + low 25 | mid_val, starts_at = self.sarray[mid] 26 | 27 | if mid_val == what: 28 | return mid, starts_at 29 | elif mid_val > what: 30 | high = mid 31 | elif mid_val < what: 32 | low = mid + 1 33 | 34 | return -1, -1 35 | 36 | def find_shortest(self, match): 37 | """Easiest is assume exact match of suffix.""" 38 | sarray_i, instr_i = self.search(match) 39 | return instr_i 40 | 41 | def find_longest(self, match): 42 | sarray_i, instr_i = self.search(match) 43 | if sarray_i == -1: return -1, -1 44 | 45 | test, instr_i = self.sarray[sarray_i] 46 | longest, longest_i = test, instr_i 47 | 48 | while test.startswith(match): 49 | if len(test) > len(longest): 50 | longest, longest_i = test, instr_i 51 | 52 | sarray_i += 1 53 | 54 | try: 55 | test, instr_i = self.sarray[sarray_i] 56 | except IndexError: 57 | break 58 | 59 | return longest, longest_i 60 | 61 | def find_all(self, match): 62 | sarray_i, inst_i = self.search(match) 63 | if sarray_i == -1: return -1, -1 64 | 65 | test, instr_i = self.sarray[sarray_i] 66 | results = [] 67 | 68 | while test.startswith(match): 69 | results.append((test, instr_i)) 70 | sarray_i += 1 71 | 72 | try: 73 | test, instr_i = self.sarray[sarray_i] 74 | except IndexError: 75 | break 76 | 77 | return results 78 | 79 | -------------------------------------------------------------------------------- /ex22_suffixarray/test_sarray.py: -------------------------------------------------------------------------------- 1 | from sarray import SuffixArray 2 | 3 | def test_SuffixArray(): 4 | finder = SuffixArray("abracadabra") 5 | 6 | assert finder.search("ra") == (9,9) 7 | assert finder.search("abra") == (1,7) 8 | assert finder.search("cadabra") == (7,4) 9 | 10 | assert finder.find_shortest("abra") == 7 11 | assert finder.find_shortest("a") == 10 12 | 13 | assert finder.find_longest("a") == ("abracadabra", 0) 14 | assert finder.find_longest("bra") == ("bracadabra", 1) 15 | 16 | assert finder.find_all("bra") == [('bra', 8), ('bracadabra', 1)] 17 | assert finder.find_all("ra") == [('ra', 9), ('racadabra', 2)] 18 | 19 | 20 | -------------------------------------------------------------------------------- /ex23_ternarytree/test_tstree.py: -------------------------------------------------------------------------------- 1 | from tstree import * 2 | 3 | 4 | def test_TSTree_get_set(): 5 | urls = TSTree() 6 | urls.set("/apple/", "Apple") 7 | urls.set("/grape/", "Grape") 8 | urls.set("/kiwi/", "Kiwi") 9 | urls.set("/kumquat/", "Kumquat") 10 | urls.set("/pineapple/", "Pineapple") 11 | 12 | assert urls.get("/apple/") == "Apple" 13 | assert urls.get("/grape/") == "Grape" 14 | assert urls.get("/kiwi/") == "Kiwi" 15 | assert urls.get("/") == None 16 | 17 | return urls 18 | 19 | def test_TSTree_find_all(): 20 | urls = test_TSTree_get_set() 21 | results = [n.value for n in urls.find_all("/k")] 22 | assert results == ["Kiwi", "Kumquat"] 23 | 24 | def test_TSTree_find_shortest(): 25 | urls = test_TSTree_get_set() 26 | urls.set("/kiwikiwi/", "KiwiKiwi") 27 | assert urls.find_shortest("/k").value == "Kiwi" 28 | assert urls.find_shortest("/kiwiki").value == "KiwiKiwi" 29 | assert urls.find_shortest("/a").value == "Apple" 30 | assert urls.find_shortest("/").value == "Kiwi" 31 | 32 | 33 | def test_TSTree_find_longest(): 34 | urls = test_TSTree_get_set() 35 | assert urls.find_longest("/").value == "Pineapple" 36 | 37 | 38 | def test_TSTree_find_part(): 39 | urls = test_TSTree_get_set() 40 | assert urls.find_part("/kaler").value == "Kiwi" 41 | assert urls.find_part("/application").value == "Apple" 42 | assert urls.find_part("/papal").value == "Pineapple" 43 | assert urls.find_part("/apple/120/1000/").value == "Apple" 44 | assert urls.find_part("/kiwi/user/zedshaw/has/stuff").value == "Kiwi" 45 | assert urls.find_part("XTREEME") == None 46 | 47 | 48 | -------------------------------------------------------------------------------- /ex24_urlrouter/test_bstree.py: -------------------------------------------------------------------------------- 1 | from bstree import * 2 | 3 | 4 | def test_BSTree_set(): 5 | colors = BSTree() 6 | colors.set("Cad", "Cad Red") 7 | assert colors.root.value == "Cad Red" 8 | colors.set("Cad", "Cadmium Red") 9 | assert colors.root.key == "Cad" 10 | assert colors.root.value == "Cadmium Red" 11 | colors.set("Pthalo", "Pthalo Blue") 12 | assert colors.root.right.key == "Pthalo" 13 | colors.set("Aliz", "Alizarin Crimson") 14 | assert colors.root.left.key == "Aliz" 15 | return colors 16 | 17 | def test_BSTree_get(): 18 | colors = test_BSTree_set() 19 | assert colors.get("Cad") == "Cadmium Red" 20 | assert colors.get("Pthalo") == "Pthalo Blue" 21 | assert colors.get("Aliz") == "Alizarin Crimson" 22 | 23 | 24 | def test_list(): 25 | colors = test_BSTree_set() 26 | colors.list() 27 | 28 | def test_delete(): 29 | colors = test_BSTree_set() 30 | colors.set("Gamb", "New Gamboge") 31 | assert colors.get("Gamb") == "New Gamboge" 32 | colors.delete("Pthalo") 33 | assert colors.get("Pthalo") == None 34 | assert colors.get("Gamb") == "New Gamboge" 35 | colors.delete("Aliz") 36 | assert colors.get("Aliz") == None 37 | 38 | colors = test_BSTree_set() 39 | colors.set("Gamb", "New Gamboge") 40 | colors.set("Prus", "Prussian Blue") 41 | colors.delete("Pthalo") 42 | assert colors.get("Gamb") == "New Gamboge" 43 | assert colors.get("Prus") == "Prussian Blue" 44 | colors.delete("Gamb") 45 | assert colors.get("Gamb") == None 46 | assert colors.get("Prus") 47 | colors.delete("Prus") 48 | assert colors.get("Prus") == None 49 | colors.delete("Cad") 50 | assert colors.root.key == "Aliz" 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /ex24_urlrouter/test_dllist.py: -------------------------------------------------------------------------------- 1 | from dllist import * 2 | 3 | 4 | def test_push(): 5 | colors = DoubleLinkedList() 6 | colors.push("Pthalo Blue") 7 | colors._invariant() 8 | assert colors.count() == 1 9 | colors.push("Ultramarine Blue") 10 | assert colors.count() == 2 11 | colors._invariant() 12 | 13 | def test_pop(): 14 | colors = DoubleLinkedList() 15 | colors.push("Magenta") 16 | colors._invariant() 17 | colors.push("Alizarin") 18 | colors.push("Van Dyke") 19 | colors._invariant() 20 | assert colors.pop() == "Van Dyke" 21 | colors._invariant() 22 | assert colors.get(1) == "Alizarin" 23 | assert colors.pop() == "Alizarin" 24 | assert colors.pop() == "Magenta" 25 | colors._invariant() 26 | assert colors.pop() == None 27 | 28 | def test_unshift(): 29 | colors = DoubleLinkedList() 30 | colors.shift("Viridian") 31 | colors.shift("Sap Green") 32 | colors.shift("Van Dyke") 33 | assert colors.unshift() == "Viridian" 34 | assert colors.unshift() == "Sap Green" 35 | assert colors.unshift() == "Van Dyke" 36 | assert colors.unshift() == None 37 | 38 | def test_shift(): 39 | colors = DoubleLinkedList() 40 | colors.shift("Cadmium Orange") 41 | assert colors.count() == 1 42 | 43 | colors.shift("Carbazole Violet") 44 | assert colors.count() == 2 45 | 46 | assert colors.pop() == "Carbazole Violet" 47 | assert colors.count() == 1 48 | assert colors.pop() == "Cadmium Orange" 49 | assert colors.count() == 0 50 | 51 | def test_remove(): 52 | colors = DoubleLinkedList() 53 | colors.push("Cobalt") 54 | colors.push("Zinc White") 55 | colors.push("Nickle Yellow") 56 | colors.push("Perinone") 57 | assert colors.remove("Cobalt") == 0 58 | colors._invariant() 59 | colors.dump("before perinone") 60 | assert colors.remove("Perinone") == 2 61 | colors._invariant() 62 | colors.dump("after perinone") 63 | assert colors.remove("Nickle Yellow") == 1 64 | colors._invariant() 65 | assert colors.remove("Zinc White") == 0 66 | colors._invariant() 67 | 68 | def test_first(): 69 | colors = DoubleLinkedList() 70 | colors.push("Cadmium Red Light") 71 | assert colors.first() == "Cadmium Red Light" 72 | colors.push("Hansa Yellow") 73 | assert colors.first() == "Cadmium Red Light" 74 | colors.shift("Pthalo Green") 75 | assert colors.first() == "Cadmium Red Light" 76 | 77 | def test_last(): 78 | colors = DoubleLinkedList() 79 | colors.push("Cadmium Red Light") 80 | assert colors.last() == "Cadmium Red Light" 81 | colors.push("Hansa Yellow") 82 | assert colors.last() == "Hansa Yellow" 83 | colors.shift("Pthalo Green") 84 | assert colors.last() == "Pthalo Green" 85 | 86 | def test_get(): 87 | colors = DoubleLinkedList() 88 | colors.push("Vermillion") 89 | assert colors.get(0) == "Vermillion" 90 | colors.push("Sap Green") 91 | assert colors.get(0) == "Vermillion" 92 | assert colors.get(1) == "Sap Green" 93 | colors.push("Cadmium Yellow Light") 94 | assert colors.get(0) == "Vermillion" 95 | assert colors.get(1) == "Sap Green" 96 | assert colors.get(2) == "Cadmium Yellow Light" 97 | assert colors.pop() == "Cadmium Yellow Light" 98 | assert colors.get(0) == "Vermillion" 99 | assert colors.get(1) == "Sap Green" 100 | assert colors.get(2) == None 101 | colors.pop() 102 | assert colors.get(0) == "Vermillion" 103 | colors.pop() 104 | assert colors.get(0) == None 105 | 106 | 107 | -------------------------------------------------------------------------------- /ex24_urlrouter/test_tstree.py: -------------------------------------------------------------------------------- 1 | from tstree import * 2 | 3 | 4 | def test_TSTree_get_set(): 5 | urls = TSTree() 6 | urls.set("/apple/", "Apple") 7 | urls.set("/grape/", "Grape") 8 | urls.set("/kiwi/", "Kiwi") 9 | urls.set("/kumquat/", "Kumquat") 10 | urls.set("/pineapple/", "Pineapple") 11 | 12 | assert urls.get("/apple/") == "Apple" 13 | assert urls.get("/grape/") == "Grape" 14 | assert urls.get("/kiwi/") == "Kiwi" 15 | assert urls.get("/") == None 16 | 17 | return urls 18 | 19 | def test_TSTree_find_all(): 20 | urls = test_TSTree_get_set() 21 | results = [n.value for n in urls.find_all("/k")] 22 | assert results == ["Kiwi", "Kumquat"] 23 | 24 | def test_TSTree_find_shortest(): 25 | urls = test_TSTree_get_set() 26 | urls.set("/kiwikiwi/", "KiwiKiwi") 27 | assert urls.find_shortest("/k").value == "Kiwi" 28 | assert urls.find_shortest("/kiwiki").value == "KiwiKiwi" 29 | assert urls.find_shortest("/a").value == "Apple" 30 | assert urls.find_shortest("/").value == "Kiwi" 31 | 32 | 33 | def test_TSTree_find_longest(): 34 | urls = test_TSTree_get_set() 35 | assert urls.find_longest("/").value == "Pineapple" 36 | 37 | 38 | def test_TSTree_find_part(): 39 | urls = test_TSTree_get_set() 40 | assert urls.find_part("/kaler").value == "Kiwi" 41 | assert urls.find_part("/application").value == "Apple" 42 | assert urls.find_part("/papal").value == "Pineapple" 43 | assert urls.find_part("/apple/120/1000/").value == "Apple" 44 | assert urls.find_part("/kiwi/user/zedshaw/has/stuff").value == "Kiwi" 45 | assert urls.find_part("XTREEME") == None 46 | 47 | 48 | -------------------------------------------------------------------------------- /ex24_urlrouter/test_urlrouter.py: -------------------------------------------------------------------------------- 1 | from urlrouter import * 2 | 3 | 4 | 5 | def add_test(urls): 6 | urls.add("/kiwi/", "Kiwi") 7 | urls.add("/apple/", "Apple") 8 | return urls 9 | 10 | def exact_match_test(urls): 11 | assert urls.exact_match("/kiwi/") == "Kiwi" 12 | assert urls.exact_match("/apple/") == "Apple" 13 | assert urls.exact_match("/stuff/") == None 14 | 15 | def best_match_test(urls): 16 | assert urls.best_match("/kiwi/user/1234/").value == "Kiwi" 17 | assert urls.best_match("/apple/user/2344/").value == "Apple" 18 | assert urls.best_match("NOTHING") == None 19 | 20 | def match_all_test(urls): 21 | urls.add("/kiwikiwi/", "KiwiKiwi") 22 | all_matches = [n.value for n in urls.match_all("/kiwi")] 23 | assert all_matches == ["Kiwi", "KiwiKiwi"] 24 | 25 | def match_shortest_test(urls): 26 | urls.add("/kiwikiwi/", "KiwiKiwi") 27 | assert urls.match_shortest("/k").value == "Kiwi" 28 | assert urls.match_shortest("/a").value == "Apple" 29 | 30 | def match_longest_test(urls): 31 | urls.add("/kiwikiwi/", "KiwiKiwi") 32 | assert urls.match_longest("/k").value == "KiwiKiwi" 33 | assert urls.match_longest("/a").value == "Apple" 34 | 35 | def all_tests(urls): 36 | add_test(urls) 37 | exact_match_test(urls) 38 | best_match_test(urls) 39 | match_all_test(urls) 40 | match_shortest_test(urls) 41 | match_longest_test(urls) 42 | 43 | def test_TSTRouter(): 44 | urls = TSTRouter() 45 | all_tests(urls) 46 | 47 | def test_DictRouter(): 48 | urls = DictRouter() 49 | all_tests(urls) 50 | 51 | def test_DListRouter(): 52 | urls = DListRouter() 53 | all_tests(urls) 54 | 55 | def test_BSTreeRouter(): 56 | urls = BSTreeRouter() 57 | all_tests(urls) 58 | -------------------------------------------------------------------------------- /ex24_urlrouter/test_urlrouter_benchmark.py: -------------------------------------------------------------------------------- 1 | from urlrouter import * 2 | 3 | 4 | 5 | def add_test(urls): 6 | urls.add("/kiwi/", "Kiwi") 7 | urls.add("/apple/", "Apple") 8 | return urls 9 | 10 | def exact_match_test(urls): 11 | assert urls.exact_match("/kiwi/") == "Kiwi" 12 | assert urls.exact_match("/apple/") == "Apple" 13 | assert urls.exact_match("/stuff/") == None 14 | 15 | def best_match_test(urls): 16 | assert urls.best_match("/kiwi/user/1234/").value == "Kiwi" 17 | assert urls.best_match("/apple/user/2344/").value == "Apple" 18 | assert urls.best_match("NOTHING") == None 19 | 20 | def match_all_test(urls): 21 | urls.add("/kiwikiwi/", "KiwiKiwi") 22 | all_matches = [n.value for n in urls.match_all("/kiwi")] 23 | assert all_matches == ["Kiwi", "KiwiKiwi"] 24 | 25 | def match_shortest_test(urls): 26 | urls.add("/kiwikiwi/", "KiwiKiwi") 27 | assert urls.match_shortest("/k").value == "Kiwi" 28 | assert urls.match_shortest("/a").value == "Apple" 29 | 30 | def match_longest_test(urls): 31 | urls.add("/kiwikiwi/", "KiwiKiwi") 32 | assert urls.match_longest("/k").value == "KiwiKiwi" 33 | assert urls.match_longest("/a").value == "Apple" 34 | 35 | def all_tests(urls): 36 | add_test(urls) 37 | exact_match_test(urls) 38 | best_match_test(urls) 39 | match_all_test(urls) 40 | match_shortest_test(urls) 41 | match_longest_test(urls) 42 | 43 | def test_TSTRouter(): 44 | urls = TSTRouter() 45 | all_tests(urls) 46 | 47 | def test_DictRouter(): 48 | urls = DictRouter() 49 | all_tests(urls) 50 | 51 | def test_DListRouter(): 52 | urls = DListRouter() 53 | all_tests(urls) 54 | 55 | def test_BSTreeRouter(): 56 | urls = BSTreeRouter() 57 | all_tests(urls) 58 | 59 | def test_bm_TSTRouter(benchmark): 60 | result = benchmark(test_TSTRouter) 61 | 62 | def test_bm_DictRouter(benchmark): 63 | result = benchmark(test_DictRouter) 64 | 65 | def test_bm_DListRouter(benchmark): 66 | result = benchmark(test_DListRouter) 67 | 68 | def test_bm_BSTreeRouter(benchmark): 69 | result = benchmark(test_BSTreeRouter) 70 | 71 | -------------------------------------------------------------------------------- /ex25_xargs/xargs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import subprocess 4 | import sys 5 | 6 | command = sys.argv[1:] 7 | 8 | for line in sys.stdin.readlines(): 9 | exec = command + [line.strip()] 10 | status = subprocess.run(exec) 11 | -------------------------------------------------------------------------------- /ex25_xargs/xargs_video.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import subprocess 4 | import sys 5 | 6 | command = sys.argv[1:] 7 | if command[0] == '-I': 8 | _ = command.pop(0) 9 | replace = command.pop(0) 10 | print("Command: ", command) 11 | else: 12 | replace = None 13 | 14 | for line in sys.stdin.readlines(): 15 | if replace: 16 | l = line.strip() 17 | exec = [c.replace(replace, l) for c in command] 18 | else: 19 | exec = command + [line.strip()] 20 | 21 | status = subprocess.run(exec) 22 | 23 | -------------------------------------------------------------------------------- /ex26_hexdump/hexdump.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | def reshape(data, n): 4 | results = [] 5 | while data: 6 | results.append(data[:n]) 7 | del data[:n] 8 | 9 | return results 10 | 11 | def format_bytes(inbytes): 12 | byte_codes = inbytes.encode("utf-8") 13 | shaped = reshape([b for b in byte_codes], 16) 14 | return shaped 15 | 16 | def format(infile): 17 | data = open(infile).read() 18 | formatted = format_bytes(data) 19 | count = 0 20 | for row in formatted: 21 | print(f"{count:010x} ", end="") 22 | print(" ".join(f"{x:02x}" for x in row)) 23 | count += len(row) 24 | 25 | if __name__ == "__main__": 26 | import sys 27 | format(sys.argv[1]) 28 | -------------------------------------------------------------------------------- /ex26_hexdump/test_hexdump.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import hexdump 3 | 4 | 5 | 6 | def test_compare_output(): 7 | out = subprocess.run(["hexdump","test_hexdump.py"], check=True, stdout=subprocess.PIPE) 8 | 9 | def test_reshape(): 10 | even = [1,2,3,4,5,6,7,8] 11 | expect = [[1, 2], [3, 4], [5, 6], [7, 8]] 12 | assert hexdump.reshape(even, 2) == expect 13 | 14 | even = [1,2,3,4,5,6,7,8] 15 | expect = [[1,2,3], [4,5,6], [7,8]] 16 | assert hexdump.reshape(even, 3) == expect 17 | 18 | odd = [1,2,3,4,5,6,7,8,9] 19 | expect = [[1,2], [3,4], [5, 6], [7,8], [9]] 20 | assert hexdump.reshape(odd, 2) == expect 21 | 22 | odd = [1,2,3,4,5,6,7,8,9] 23 | expect = [[1,2,3], [4,5,6], [7,8,9]] 24 | assert hexdump.reshape(odd, 3) == expect 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /ex27_tr/test_tr.py: -------------------------------------------------------------------------------- 1 | import tr 2 | 3 | def test_translate(): 4 | test_input = "ttyyhhxxaa" 5 | results = tr.translate('t', 'X', test_input) 6 | assert results == "XXyyhhxxaa" 7 | 8 | def test_tr_basic(): 9 | results = tr.main(['t', 'X'], 'ttyyhhxxaa') 10 | assert results == "XXyyhhxxaa" 11 | 12 | results = tr.main(['XX', 'tt'], results) 13 | assert results == 'ttyyhhxxaa' 14 | 15 | results = tr.main(['hh', 'XX'], results) 16 | assert results == 'ttyyXXxxaa' 17 | 18 | results = tr.main(['hh', 'XX'], '') 19 | assert results == '' 20 | 21 | def test_tr_truncate(): 22 | results = tr.main(['-t', 'httttt', 'Xx'], 'httttttaaaaa') 23 | assert results == 'Xxtttttaaaaa' 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /ex27_tr/tr.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import sys 3 | 4 | def translate(from_c, to_c, in_string): 5 | return in_string.replace(from_c, to_c) 6 | 7 | def main(args, in_string): 8 | print(">>>> args", args) 9 | if args[0] == '-t': 10 | # need to truncate first to len of last 11 | to_c = args[2] 12 | from_c = args[1][:len(to_c)] 13 | else: 14 | from_c = args[0] 15 | to_c = args[1] 16 | print("from_c", from_c, "to_c", to_c) 17 | return translate(from_c, to_c, in_string) 18 | 19 | if __name__ == "__main__": 20 | print(main(sys.argv[1:], sys.stdin.read())) 21 | 22 | -------------------------------------------------------------------------------- /ex27_tr/tr_spike.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import argparse 3 | 4 | # parser = argparse.ArgumentParser() 5 | # parser.add_argument('-f', '--foo', help='foo help') 6 | # args = parser.parse_args() 7 | 8 | _, tr_from, tr_to = sys.argv 9 | 10 | 11 | for line in sys.stdin.readlines(): 12 | print(line.replace(tr_from, tr_to), end="") 13 | 14 | -------------------------------------------------------------------------------- /ex28_sh/sh.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import sys 3 | import os 4 | 5 | def parse(line): 6 | return [x.strip() for x in line.split(' ') if x] 7 | 8 | def interpret(exec): 9 | if exec[0] == 'exit': 10 | sys.exit(0) 11 | elif exec[0] == 'cd': 12 | if len(exec) == 2: 13 | os.chdir(exec[1]) 14 | else: 15 | print("Error: cd DIR") 16 | else: 17 | subprocess.run(exec) 18 | 19 | def run(line): 20 | exec = parse(line) 21 | 22 | if exec: 23 | return interpret(exec) 24 | else: 25 | return None 26 | 27 | def main(): 28 | while True: 29 | try: 30 | line = input('> ') 31 | # need to find out if we should process this 32 | status = run(line) 33 | except KeyboardInterrupt: 34 | sys.exit(0) 35 | 36 | if __name__ == "__main__": 37 | main() 38 | -------------------------------------------------------------------------------- /ex28_sh/sh_spike.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import sys 3 | import os 4 | 5 | 6 | while True: 7 | line = input('> ') 8 | exec = line.strip().split(' ') 9 | status = subprocess.run(exec) 10 | 11 | -------------------------------------------------------------------------------- /ex28_sh/test_sh.py: -------------------------------------------------------------------------------- 1 | from unittest.mock import patch 2 | import sh 3 | 4 | 5 | @patch('subprocess.run') 6 | def test_basic(sub_run): 7 | result = sh.run('ls -l') 8 | assert sub_run.called 9 | 10 | def test_parse(): 11 | result = sh.parse('ls -l') 12 | assert result == ['ls', '-l'] 13 | 14 | result = sh.parse('') 15 | assert result == [] 16 | 17 | result = sh.parse(' ') 18 | assert result == [] 19 | 20 | @patch('sys.exit') 21 | @patch('os.chdir') 22 | @patch('subprocess.run') 23 | def test_interpret(sub_run, chdir, sys_exit): 24 | result = sh.interpret(['exit']) 25 | assert sys_exit.called 26 | 27 | result = sh.interpret(['cd', '..']) 28 | assert chdir.called 29 | 30 | result = sh.interpret(['ls', '-l']) 31 | assert sub_run.called 32 | 33 | 34 | @patch('subprocess.run') 35 | def test_empty_input(sub_run): 36 | result = sh.run('') 37 | assert not sub_run.called 38 | -------------------------------------------------------------------------------- /ex29_diff_patch/diff.py: -------------------------------------------------------------------------------- 1 | import difflib 2 | import sys 3 | 4 | def create_diff(style, left_lines, right_lines, fromfile="", tofile=""): 5 | if style == "n": 6 | return difflib.ndiff(left_lines, right_lines) 7 | elif style == "c": 8 | return difflib.context_diff(left_lines, right_lines, 9 | fromfile=fromfile, tofile=tofile) 10 | elif style == "u": 11 | # default to unified diff 12 | return difflib.unified_diff(left_lines, right_lines, 13 | fromfile=fromfile, tofile=tofile) 14 | else: 15 | assert False, "Invalid diff style." 16 | 17 | def main(style, left, right, outfile): 18 | left_lines = open(left).readlines() 19 | right_lines = open(right).readlines() 20 | diff = create_diff(style, left_lines, right_lines, fromfile=left, tofile=right) 21 | outfile.writelines(diff) 22 | 23 | 24 | if __name__ == "__main__": 25 | if len(sys.argv) == 4: 26 | assert sys.argv[1][0] == '-', "Argument error." 27 | _, diff_style, left, right = sys.argv 28 | main(diff_style[1], left, right, sys.stdout) 29 | else: 30 | _, left, right = sys.argv 31 | main('u', left, right, sys.stdout) 32 | 33 | -------------------------------------------------------------------------------- /ex29_diff_patch/diff_spike.py: -------------------------------------------------------------------------------- 1 | import difflib 2 | import sys 3 | 4 | 5 | def main(left, right, outfile): 6 | left_lines = open(left).readlines() 7 | right_lines = open(right).readlines() 8 | 9 | diff = difflib.context_diff(left_lines, right_lines, 10 | fromfile=left, 11 | tofile=right) 12 | 13 | outfile.writelines(diff) 14 | 15 | 16 | if __name__ == "__main__": 17 | _, left, right = sys.argv 18 | main(left, right, sys.stdout) 19 | -------------------------------------------------------------------------------- /ex29_diff_patch/sample.py: -------------------------------------------------------------------------------- 1 | 2 | # This is just for testing 3 | 4 | import difflib 5 | import sys 6 | 7 | 8 | def main(left, right, outfile): 9 | left_lines = open(XXX).readlines() 10 | right_lines = open(right).readlines() 11 | 12 | diff = difflib.context_diff(left_lines, right_lines, 13 | XXX=left, 14 | tofile=right) 15 | 16 | outfile.writelines(diff) 17 | 18 | 19 | if __name__ == "__main__": 20 | _, left, right = sys.argv 21 | main(left, right, sys.stdout) 22 | -------------------------------------------------------------------------------- /ex29_diff_patch/test_diff.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import diff 3 | import io 4 | 5 | def run_diff(style): 6 | out = io.StringIO() 7 | diff.main(style, 'diff.py', 'sample.py', out) 8 | # should probably do a better test 9 | return out.getvalue() 10 | 11 | 12 | def test_basic(): 13 | result = run_diff('u') 14 | assert result 15 | 16 | result = run_diff('c') 17 | assert result 18 | 19 | result = run_diff('n') 20 | assert result 21 | 22 | -------------------------------------------------------------------------------- /ex30_fsm/fsm.py: -------------------------------------------------------------------------------- 1 | from inspect import getmembers 2 | import re 3 | 4 | STATE_NAME = re.compile('^[A-Z0-9]+$') 5 | 6 | class FSM(object): 7 | 8 | def __init__(self): 9 | self.state_name = "START" 10 | members = getmembers(self) 11 | self.possible_states = {} 12 | 13 | for k,v in members: 14 | # TODO: restrict this to all caps only 15 | if STATE_NAME.match(k): 16 | self.possible_states[k] = v 17 | 18 | def handle(self, event): 19 | state_handler = self.lookup_state() 20 | self.state_name = state_handler(event) 21 | 22 | def lookup_state(self): 23 | return self.possible_states.get(self.state_name) 24 | 25 | 26 | -------------------------------------------------------------------------------- /ex30_fsm/socket_fsm.py: -------------------------------------------------------------------------------- 1 | from fsm import FSM 2 | 3 | class SocketFSM(FSM): 4 | 5 | def START(self, event): 6 | return "LISTENING" 7 | 8 | def LISTENING(self, event): 9 | if event == "connect": 10 | return "CONNECTED" 11 | elif event == "error": 12 | return "LISTENING" 13 | else: 14 | return "ERROR" 15 | 16 | def CONNECTED(self, event): 17 | if event == "accept": 18 | return "ACCEPTED" 19 | elif event == "close": 20 | return "CLOSED" 21 | else: 22 | return "ERROR" 23 | 24 | def ACCEPTED(self, event): 25 | if event == "close": 26 | return "CLOSED" 27 | elif event == "read": 28 | return "READING"(event) 29 | elif event == "write": 30 | return "WRITING"(event) 31 | else: 32 | return "ERROR" 33 | 34 | def READING(self, event): 35 | if event == "read": 36 | return "READING" 37 | elif event == "write": 38 | return "WRITING"(event) 39 | elif event == "close": 40 | return "CLOSED" 41 | else: 42 | return "ERROR" 43 | 44 | def WRITING(self, event): 45 | if event == "read": 46 | return "READING"(event) 47 | elif event == "write": 48 | return "WRITING" 49 | elif event == "close": 50 | return "CLOSED" 51 | else: 52 | return "ERROR" 53 | 54 | def CLOSED(self, event): 55 | return "LISTENING"(event) 56 | 57 | def ERROR(self, event): 58 | return "ERROR" 59 | 60 | 61 | -------------------------------------------------------------------------------- /ex30_fsm/test_fsm.py: -------------------------------------------------------------------------------- 1 | from socket_fsm import SocketFSM 2 | 3 | 4 | def test_basic_connection(): 5 | fsm = SocketFSM() 6 | script = ["connect", "accept", "read", "read", "write", "close", "connect"] 7 | 8 | for event in script: 9 | print(event, ">>>", fsm) 10 | fsm.handle(event) 11 | 12 | -------------------------------------------------------------------------------- /ex31_regex/fsm.py: -------------------------------------------------------------------------------- 1 | from inspect import getmembers 2 | import re 3 | 4 | STATE_NAME = re.compile('^[A-Z0-9]+$') 5 | 6 | class FSM(object): 7 | 8 | def __init__(self): 9 | self.state_name = "START" 10 | members = getmembers(self) 11 | self.possible_states = {} 12 | 13 | for k,v in members: 14 | # TODO: restrict this to all caps only 15 | if STATE_NAME.match(k): 16 | self.possible_states[k] = v 17 | 18 | def handle(self, event): 19 | state_handler = self.lookup_state() 20 | self.state_name = state_handler(event) 21 | 22 | def lookup_state(self): 23 | return self.possible_states.get(self.state_name) 24 | 25 | 26 | -------------------------------------------------------------------------------- /ex31_regex/refsm.py: -------------------------------------------------------------------------------- 1 | from fsm import FSM 2 | 3 | 4 | def match(what): 5 | """Will use an FSM to match [A-Za-z][0-9]+""" 6 | 7 | fsm = ReFSM() 8 | 9 | for c in what: 10 | fsm.handle(c) 11 | # exit early on first bad match 12 | 13 | if fsm.state_name == "INVALID": 14 | return False 15 | 16 | 17 | return fsm.matched 18 | 19 | 20 | class ReFSM(FSM): 21 | 22 | def START(self, event): 23 | 24 | if event.isalpha(): 25 | self.matched = False 26 | return "DIGITS" 27 | else: 28 | self.matched = False 29 | return "INVALID" 30 | 31 | def DIGITS(self, event): 32 | if event.isdigit(): 33 | self.matched = True 34 | return "DIGITS" 35 | else: 36 | self.matched = False 37 | return "INVALID" 38 | 39 | def INVALID(self, event): 40 | self.matched = False 41 | return "INVALID" 42 | 43 | 44 | -------------------------------------------------------------------------------- /ex31_regex/test_refsm.py: -------------------------------------------------------------------------------- 1 | import refsm 2 | 3 | 4 | def test_match(): 5 | assert refsm.match('A990099') 6 | assert refsm.match('d9848') 7 | assert refsm.match('Z99%#$') == False 8 | assert refsm.match('%6666') == False 9 | assert refsm.match('A#$#') == False 10 | assert refsm.match('AZZZZ') == False 11 | 12 | def test_ReFSM(): 13 | 14 | fsm = refsm.ReFSM() 15 | fsm.handle('%') 16 | fsm.handle('%') 17 | assert fsm.state_name == 'INVALID' 18 | -------------------------------------------------------------------------------- /ex32_scanners/ex32.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | code = [ 4 | "def hello(x, y):", 5 | " print(x + y)", 6 | "hello(10, 20)", 7 | ] 8 | 9 | TOKENS = [ 10 | (re.compile(r"^def"), "DEF"), 11 | (re.compile(r"^[a-zA-Z_][a-zA-Z0-9_]*"), "NAME"), 12 | (re.compile(r"^[0-9]+"), "INTEGER"), 13 | (re.compile(r"^\("), "LPAREN"), 14 | (re.compile(r"^\)"), "RPAREN"), 15 | (re.compile(r"^\+"), "PLUS"), 16 | (re.compile(r"^:"), "COLON"), 17 | (re.compile(r"^,"), "COMMA"), 18 | (re.compile(r"^\s+"), "INDENT"), 19 | ] 20 | 21 | def match(i, line): 22 | start = line[i:] 23 | for regex, token in TOKENS: 24 | match = regex.match(start) 25 | if match: 26 | begin, end = match.span() 27 | return token, start[:end], end 28 | return None, start, None 29 | 30 | script = [] 31 | 32 | for line in code: 33 | i = 0 34 | while i < len(line): 35 | token, string, end = match(i, line) 36 | assert token, "Failed to match line %s" % string 37 | if token: 38 | i += end 39 | script.append((token, string, i, end)) 40 | 41 | print(script) 42 | -------------------------------------------------------------------------------- /ex32_scanners/punypy_scanner.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | class Scanner(object): 4 | 5 | def __init__(self, regex, code): 6 | self.regex_list = regex 7 | self.code = code 8 | self.tokens = self.scan(code) 9 | 10 | 11 | def match_regex(self, i, line): 12 | start = line[i:] 13 | for regex, token in self.regex_list: 14 | tok = regex.match(start) 15 | if tok: 16 | begin, end = tok.span() 17 | return token, start[:end], end 18 | return None, start, None 19 | 20 | 21 | def scan(self, code): 22 | self.script = [] 23 | 24 | for line in code: 25 | i = 0 26 | while i < len(line): 27 | token, string, end = self.match_regex(i, line) 28 | assert token, "Failed to match line %s" % string 29 | if token: 30 | i += end 31 | self.script.append((token, string, i, end)) 32 | 33 | return self.script 34 | 35 | def done(self): 36 | return len(self.tokens) == 0 37 | -------------------------------------------------------------------------------- /ex32_scanners/scanner.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | code = [ 4 | "def hello(x, y):", 5 | " print(x + y)", 6 | "hello(10, 20)", 7 | ] 8 | 9 | TOKENS = [ 10 | (re.compile(r"^def"), "DEF"), 11 | (re.compile(r"^[a-zA-Z_][a-zA-Z0-9_]*"), "NAME"), 12 | (re.compile(r"^[0-9]+"), "INTEGER"), 13 | (re.compile(r"^\("), "LPAREN"), 14 | (re.compile(r"^\)"), "RPAREN"), 15 | (re.compile(r"^\+"), "PLUS"), 16 | (re.compile(r"^:"), "COLON"), 17 | (re.compile(r"^,"), "COMMA"), 18 | (re.compile(r"^\s+"), "INDENT"), 19 | ] 20 | 21 | 22 | def match_regex(i, line): 23 | start = line[i:] 24 | for regex, token in TOKENS: 25 | tok = regex.match(start) 26 | if tok: 27 | begin, end = tok.span() 28 | return token, start[:end], end 29 | return None, start, None 30 | 31 | 32 | def scan(code): 33 | script = [] 34 | 35 | for line in code: 36 | i = 0 37 | while i < len(line): 38 | token, string, end = match_regex(i, line) 39 | assert token, "Failed to match line %s" % string 40 | if token: 41 | i += end 42 | script.append((token, string, i, end)) 43 | 44 | return script 45 | -------------------------------------------------------------------------------- /ex32_scanners/test_scanner.py: -------------------------------------------------------------------------------- 1 | from punypy_scanner import Scanner 2 | import re 3 | 4 | code = [ 5 | "def hello(x, y):", 6 | " print(x + y)", 7 | "hello(10, 20)", 8 | ] 9 | 10 | TOKENS = [ 11 | (re.compile(r"^def"), "DEF"), 12 | (re.compile(r"^[a-zA-Z_][a-zA-Z0-9_]*"), "NAME"), 13 | (re.compile(r"^[0-9]+"), "INTEGER"), 14 | (re.compile(r"^\("), "LPAREN"), 15 | (re.compile(r"^\)"), "RPAREN"), 16 | (re.compile(r"^\+"), "PLUS"), 17 | (re.compile(r"^:"), "COLON"), 18 | (re.compile(r"^,"), "COMMA"), 19 | (re.compile(r"^\s+"), "INDENT"), 20 | ] 21 | 22 | 23 | def test_Scanner(): 24 | s = Scanner(TOKENS, code) 25 | assert s.tokens[0][0] == 'DEF' 26 | assert s.tokens[1][0] == 'INDENT' 27 | assert s.tokens[2][0] == 'NAME' 28 | assert s.tokens[2][1] == 'hello' 29 | 30 | -------------------------------------------------------------------------------- /ex33_parsers/ex33.py: -------------------------------------------------------------------------------- 1 | from scanner import * 2 | from pprint import pprint 3 | 4 | def root(tokens): 5 | """root = *(funccal / funcdef)""" 6 | first = peek(tokens) 7 | 8 | if first == 'DEF': 9 | return function_definition(tokens) 10 | elif first == 'NAME': 11 | name = match(tokens, 'NAME') 12 | second = peek(tokens) 13 | 14 | if second == 'LPAREN': 15 | return function_call(tokens, name) 16 | else: 17 | assert False, "Not a FUNCDEF or FUNCCALL" 18 | 19 | def function_definition(tokens): 20 | """ 21 | funcdef = DEF name LPAREN params RPAREN COLON body 22 | I ignore body for this example 'cause that's hard. 23 | I mean, so you can learn how to do it. 24 | """ 25 | skip(tokens) # discard def 26 | name = match(tokens, 'NAME') 27 | match(tokens, 'LPAREN') 28 | params = parameters(tokens) 29 | match(tokens, 'RPAREN') 30 | match(tokens, 'COLON') 31 | return {'type': 'FUNCDEF', 'name': name, 'params': params} 32 | 33 | def parameters(tokens): 34 | """params = expression *(COMMA expression)""" 35 | params = [] 36 | start = peek(tokens) 37 | while start != 'RPAREN': 38 | params.append(expression(tokens)) 39 | start = peek(tokens) 40 | if start != 'RPAREN': 41 | assert match(tokens, 'COMMA') 42 | return params 43 | 44 | def function_call(tokens, name): 45 | """funccall = name LPAREN params RPAREN""" 46 | match(tokens, 'LPAREN') 47 | params = parameters(tokens) 48 | match(tokens, 'RPAREN') 49 | return {'type': 'FUNCCALL', 'name': name, 'params': params} 50 | 51 | def expression(tokens): 52 | """expression = name / plus / integer""" 53 | start = peek(tokens) 54 | 55 | if start == 'NAME': 56 | name = match(tokens, 'NAME') 57 | if peek(tokens) == 'PLUS': 58 | return plus(tokens, name) 59 | else: 60 | return name 61 | elif start == 'INTEGER': 62 | number = match(tokens, 'INTEGER') 63 | if peek(tokens) == 'PLUS': 64 | return plus(tokens, number) 65 | else: 66 | return number 67 | else: 68 | assert False, "Syntax error %r" % start 69 | 70 | def plus(tokens, left): 71 | """plus = expression PLUS expression""" 72 | match(tokens, 'PLUS') 73 | right = expression(tokens) 74 | return {'type': 'PLUS', 'left': left, 'right': right} 75 | 76 | 77 | def main(tokens): 78 | results = [] 79 | while tokens: 80 | results.append(root(tokens)) 81 | return results 82 | 83 | parsed = main(scan(code)) 84 | pprint(parsed) 85 | -------------------------------------------------------------------------------- /ex33_parsers/punypy/parser.py: -------------------------------------------------------------------------------- 1 | from pprint import pprint 2 | 3 | 4 | class Parser(object): 5 | 6 | def __init__(self, scanner): 7 | self.scanner = scanner 8 | 9 | def match(self, token_id): 10 | return self.scanner.match(token_id) 11 | 12 | def peek(self): 13 | return self.scanner.peek() 14 | 15 | def skip(self, *what): 16 | return self.scanner.skip(*what) 17 | 18 | def root(self): 19 | pass 20 | 21 | def parse(self): 22 | results = [] 23 | while not self.scanner.done(): 24 | results.append(self.root()) 25 | return results 26 | 27 | 28 | -------------------------------------------------------------------------------- /ex33_parsers/punypy/productions.py: -------------------------------------------------------------------------------- 1 | class Production(object): 2 | def analyze(self, world): 3 | """Implement your analyzer here.""" 4 | 5 | class FuncCall(Production): 6 | 7 | def __init__(self, token, params): 8 | self.name = token[1] 9 | self.params = params 10 | self.token = token 11 | 12 | def __repr__(self): 13 | return f"FuncCall({self.name}: {self.params})" 14 | 15 | class Parameters(Production): 16 | 17 | def __init__(self, expressions): 18 | self.expressions = expressions 19 | 20 | def __repr__(self): 21 | return f"Parameters({self.expressions})" 22 | 23 | class Expr(Production): pass 24 | 25 | class NameExpr(Expr): 26 | def __init__(self, token): 27 | self.name = token[1] 28 | self.token = token 29 | 30 | def __repr__(self): 31 | return f"NameExpr({self.name})" 32 | 33 | class IntExpr(Expr): 34 | def __init__(self, token): 35 | self.integer = token[1] 36 | self.token = token 37 | 38 | def __repr__(self): 39 | return f"IntExpr({self.integer})" 40 | 41 | class AddExpr(Expr): 42 | def __init__(self, left, right): 43 | self.left = left 44 | self.right = right 45 | 46 | def __repr__(self): 47 | return f"AddExpr({self.left}, {self.right})" 48 | 49 | class FuncDef(Production): 50 | 51 | def __init__(self, token, params, body): 52 | self.name = token[1] 53 | self.params = params 54 | self.body = body 55 | self.token = token 56 | 57 | def __repr__(self): 58 | return f"FuncDef({self.name}({self.params}): {self.body}" 59 | 60 | 61 | -------------------------------------------------------------------------------- /ex33_parsers/punypy/run.py: -------------------------------------------------------------------------------- 1 | import re 2 | from punypy.parser import Parser 3 | from punypy import productions as prod 4 | 5 | TOKENS = [ 6 | (re.compile(r"^def"), "DEF"), 7 | (re.compile(r"^[a-zA-Z_][a-zA-Z0-9_]*"), "NAME"), 8 | (re.compile(r"^[0-9]+"), "INTEGER"), 9 | (re.compile(r"^\("), "LPAREN"), 10 | (re.compile(r"^\)"), "RPAREN"), 11 | (re.compile(r"^\+"), "PLUS"), 12 | (re.compile(r"^:"), "COLON"), 13 | (re.compile(r"^,"), "COMMA"), 14 | (re.compile(r"^\s+"), "INDENT"), 15 | ] 16 | 17 | 18 | class PunyPyParser(Parser): 19 | 20 | def root(self): 21 | """root = *(funccal / funcdef)""" 22 | first = self.peek() 23 | 24 | if first == 'DEF': 25 | return self.function_definition() 26 | elif first == 'NAME': 27 | name = self.match('NAME') 28 | second = self.peek() 29 | 30 | if second == 'LPAREN': 31 | return self.function_call(name) 32 | else: 33 | assert False, "Not a FUNCDEF or FUNCCALL" 34 | 35 | def function_definition(self): 36 | """ 37 | funcdef = DEF name LPAREN params RPAREN COLON body 38 | I ignore body for this example 'cause that's hard. 39 | I mean, so you can learn how to do it. 40 | """ 41 | self.skip('DEF') 42 | name = self.match('NAME') 43 | self.match('LPAREN') 44 | params = self.parameters() 45 | self.skip('RPAREN', 'COLON') 46 | body = self.function_body() 47 | return prod.FuncDef(name, params, body) 48 | 49 | def function_body(self): 50 | body = [] 51 | while self.skip("INDENT"): 52 | body.append(self.expression()) 53 | return body 54 | 55 | def parameters(self): 56 | """params = expression *(COMMA expression)""" 57 | params = [] 58 | start = self.peek() 59 | while start != 'RPAREN': 60 | params.append(self.expression()) 61 | start = self.peek() 62 | if start != 'RPAREN': 63 | assert self.skip('COMMA') 64 | return prod.Parameters(params) 65 | 66 | def function_call(self, name): 67 | """funccall = name LPAREN params RPAREN""" 68 | self.match('LPAREN') 69 | params = self.parameters() 70 | self.match('RPAREN') 71 | return prod.FuncCall(name, params) 72 | 73 | def expression(self): 74 | """expression = name / funccall / plus / integer""" 75 | start = self.peek() 76 | 77 | if start == 'NAME': 78 | name = self.match('NAME') 79 | nameexpr = prod.NameExpr(name) 80 | 81 | expr = self.peek() 82 | 83 | if expr == 'PLUS': 84 | return self.plus(nameexpr) 85 | elif expr == 'LPAREN': 86 | return self.function_call(name) 87 | else: 88 | return nameexpr 89 | elif start == 'INTEGER': 90 | number = self.match('INTEGER') 91 | numexpr = prod.IntExpr(number) 92 | if self.peek() == 'PLUS': 93 | return self.plus(numexpr) 94 | else: 95 | return numexpr 96 | else: 97 | assert False, "Syntax error %r" % start 98 | 99 | def plus(self, left): 100 | """plus = expression PLUS expression""" 101 | self.match('PLUS') 102 | right = self.expression() 103 | return prod.AddExpr(left, right) 104 | 105 | class PunyPyWorld(object): 106 | 107 | def __init__(self, variables): 108 | self.variables = variables 109 | self.functions = {} 110 | 111 | 112 | -------------------------------------------------------------------------------- /ex33_parsers/punypy/scanner.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | class Scanner(object): 4 | 5 | def __init__(self, regex, code): 6 | self.regex_list = regex 7 | self.code = code 8 | self.tokens = self.scan(code) 9 | 10 | def ignore_ws(self): 11 | while self.tokens[0][0] == 'INDENT': 12 | self.tokens.pop(0) 13 | 14 | def match(self, token_id): 15 | if token_id != 'INDENT': 16 | self.ignore_ws() 17 | 18 | if self.tokens[0][0] == token_id: 19 | return self.tokens.pop(0) 20 | else: 21 | return ['ERROR', 'error'] 22 | 23 | def peek(self): 24 | self.ignore_ws() 25 | return self.tokens[0][0] 26 | 27 | def skip(self, *what): 28 | for x in what: 29 | if x != 'INDENT': self.ignore_ws() 30 | 31 | tok = self.tokens[0] 32 | if tok[0] != x: 33 | return False 34 | else: 35 | self.tokens.pop(0) 36 | 37 | return True 38 | 39 | 40 | def match_regex(self, i, line): 41 | start = line[i:] 42 | for regex, token in self.regex_list: 43 | tok = regex.match(start) 44 | if tok: 45 | begin, end = tok.span() 46 | return token, start[:end], end 47 | return None, start, None 48 | 49 | 50 | def scan(self, code): 51 | self.script = [] 52 | 53 | for line in code: 54 | i = 0 55 | while i < len(line): 56 | token, string, end = self.match_regex(i, line) 57 | assert token, "Failed to match line %s" % string 58 | if token: 59 | i += end 60 | self.script.append((token, string, i, end)) 61 | 62 | return self.script 63 | 64 | def done(self): 65 | return len(self.tokens) == 0 66 | -------------------------------------------------------------------------------- /ex33_parsers/tests/test_parser.py: -------------------------------------------------------------------------------- 1 | from punypy.run import TOKENS, PunyPyParser, PunyPyWorld 2 | from punypy.scanner import Scanner 3 | from tests.test_scanner import code 4 | 5 | def test_Parser(): 6 | scanner = Scanner(TOKENS, code) 7 | parser = PunyPyParser(scanner) 8 | return parser.parse() 9 | 10 | -------------------------------------------------------------------------------- /ex33_parsers/tests/test_scanner.py: -------------------------------------------------------------------------------- 1 | from punypy.run import TOKENS 2 | from punypy.scanner import Scanner 3 | 4 | code = [ 5 | "def hello(x, y):", 6 | " print(x + y)", 7 | "hello(10, 20)", 8 | ] 9 | 10 | 11 | def test_Scanner(): 12 | s = Scanner(TOKENS, code) 13 | assert s.skip("DEF") 14 | assert s.match("NAME")[1] == "hello" 15 | assert s.skip("LPAREN") 16 | assert s.peek() == "NAME" 17 | assert s.match("NAME")[1] == "x" 18 | assert s.skip("COMMA") 19 | assert s.match("NAME")[1] == "y" 20 | assert s.skip("RPAREN", "COLON") 21 | assert s.match("INDENT")[1] == " " 22 | assert s.match("NAME")[1] == "print" 23 | assert s.skip("LPAREN") 24 | assert s.match("NAME")[1] == "x" 25 | assert s.match("PLUS")[0] == "PLUS" 26 | assert s.match("NAME")[1] == "y" 27 | assert s.skip("RPAREN") 28 | assert s.peek() != "INDENT" 29 | assert s.match("NAME")[1] == "hello" 30 | assert s.skip("LPAREN") 31 | assert s.match("INTEGER")[1] == "10" 32 | assert s.skip("COMMA") 33 | assert s.match("INTEGER")[1] == "20" 34 | assert s.skip("RPAREN") 35 | 36 | 37 | -------------------------------------------------------------------------------- /ex34_analyzers/ex34a.py: -------------------------------------------------------------------------------- 1 | class Production(object): 2 | def analyze(self, world): 3 | """Implement your analyzer here.""" 4 | 5 | 6 | class FuncCall(Production): 7 | 8 | def __init__(self, name, params): 9 | self.name = name 10 | self.params = params 11 | 12 | def analyze(self, world): 13 | print("> FuncCall: ", self.name) 14 | self.params.analyze(world) 15 | 16 | class Parameters(Production): 17 | 18 | def __init__(self, expressions): 19 | self.expressions = expressions 20 | 21 | def analyze(self, world): 22 | print(">> Parameters: ") 23 | for expr in self.expressions: 24 | expr.analyze(world) 25 | 26 | class Expr(Production): pass 27 | 28 | class IntExpr(Expr): 29 | def __init__(self, integer): 30 | self.integer = integer 31 | 32 | def analyze(self, world): 33 | print(">>>> IntExpr: ", self.integer) 34 | 35 | class AddExpr(Expr): 36 | def __init__(self, left, right): 37 | self.left = left 38 | self.right = right 39 | 40 | def analyze(self, world): 41 | print(">>> AddExpr: ") 42 | self.left.analyze(world) 43 | self.right.analyze(world) 44 | 45 | class PunyPyWorld(object): 46 | 47 | def __init__(self, variables): 48 | self.variables = variables 49 | self.functions = {} 50 | 51 | class PunyPyAnalyzer(object): 52 | def __init__(self, parse_tree, world): 53 | self.parse_tree = parse_tree 54 | self.world = world 55 | 56 | def analyze(self): 57 | for node in self.parse_tree: 58 | node.analyze(self.world) 59 | 60 | variables = {} 61 | world = PunyPyWorld(variables) 62 | # simulate hello(10 + 20) 63 | script = [FuncCall("hello", 64 | Parameters( 65 | [AddExpr(IntExpr(10), IntExpr(20))]) 66 | )] 67 | analyzer = PunyPyAnalyzer(script, world) 68 | analyzer.analyze() 69 | -------------------------------------------------------------------------------- /ex34_analyzers/punypy/analyzer.py: -------------------------------------------------------------------------------- 1 | 2 | class PunyPyAnalyzer(object): 3 | def __init__(self, parse_tree, world): 4 | self.parse_tree = parse_tree 5 | self.world = world 6 | 7 | def analyze(self): 8 | for node in self.parse_tree: 9 | node.analyze(self.world) 10 | 11 | return self.parse_tree 12 | -------------------------------------------------------------------------------- /ex34_analyzers/punypy/parser.py: -------------------------------------------------------------------------------- 1 | from pprint import pprint 2 | 3 | 4 | class Parser(object): 5 | 6 | def __init__(self, scanner): 7 | self.scanner = scanner 8 | 9 | def match(self, token_id): 10 | return self.scanner.match(token_id) 11 | 12 | def peek(self): 13 | return self.scanner.peek() 14 | 15 | def skip(self, *what): 16 | return self.scanner.skip(*what) 17 | 18 | def root(self): 19 | pass 20 | 21 | def parse(self): 22 | results = [] 23 | while not self.scanner.done(): 24 | results.append(self.root()) 25 | return results 26 | 27 | 28 | -------------------------------------------------------------------------------- /ex34_analyzers/punypy/productions.py: -------------------------------------------------------------------------------- 1 | class Production(object): 2 | def analyze(self, world): 3 | """Implement your analyzer here.""" 4 | 5 | class FuncCall(Production): 6 | 7 | def __init__(self, token, params): 8 | self.name = token[1] 9 | self.params = params 10 | self.token = token 11 | 12 | def analyze(self, world): 13 | self.params.analyze(world) 14 | 15 | def __repr__(self): 16 | return f"FuncCall({self.name}: {self.params})" 17 | 18 | class Parameters(Production): 19 | 20 | def __init__(self, expressions): 21 | self.expressions = expressions 22 | 23 | def analyze(self, world): 24 | for expr in self.expressions: 25 | expr.analyze(world) 26 | 27 | def __repr__(self): 28 | return f"Parameters({self.expressions})" 29 | 30 | class Expr(Production): pass 31 | 32 | class NameExpr(Expr): 33 | def __init__(self, token): 34 | self.name = token[1] 35 | self.token = token 36 | 37 | def __repr__(self): 38 | return f"NameExpr({self.name})" 39 | 40 | class IntExpr(Expr): 41 | def __init__(self, token): 42 | self.integer = token[1] 43 | self.token = token 44 | 45 | def __repr__(self): 46 | return f"IntExpr({self.integer})" 47 | 48 | class AddExpr(Expr): 49 | def __init__(self, left, right): 50 | self.left = left 51 | self.right = right 52 | 53 | def analyze(self, world): 54 | self.left.analyze(world) 55 | self.right.analyze(world) 56 | 57 | def __repr__(self): 58 | return f"AddExpr({self.left}, {self.right})" 59 | 60 | class FuncDef(Production): 61 | 62 | def __init__(self, token, params, body): 63 | self.name = token[1] 64 | self.params = params 65 | self.body = body 66 | self.token = token 67 | 68 | def analyze(self, world): 69 | world.functions[self.name] = self 70 | 71 | def __repr__(self): 72 | return f"FuncDef({self.name}({self.params}): {self.body}" 73 | 74 | 75 | -------------------------------------------------------------------------------- /ex34_analyzers/punypy/run.py: -------------------------------------------------------------------------------- 1 | import re 2 | from punypy.parser import Parser 3 | from punypy import productions as prod 4 | 5 | TOKENS = [ 6 | (re.compile(r"^def"), "DEF"), 7 | (re.compile(r"^[a-zA-Z_][a-zA-Z0-9_]*"), "NAME"), 8 | (re.compile(r"^[0-9]+"), "INTEGER"), 9 | (re.compile(r"^\("), "LPAREN"), 10 | (re.compile(r"^\)"), "RPAREN"), 11 | (re.compile(r"^\+"), "PLUS"), 12 | (re.compile(r"^:"), "COLON"), 13 | (re.compile(r"^,"), "COMMA"), 14 | (re.compile(r"^\s+"), "INDENT"), 15 | ] 16 | 17 | 18 | class PunyPyParser(Parser): 19 | 20 | def root(self): 21 | """root = *(funccal / funcdef)""" 22 | first = self.peek() 23 | 24 | if first == 'DEF': 25 | return self.function_definition() 26 | elif first == 'NAME': 27 | name = self.match('NAME') 28 | second = self.peek() 29 | 30 | if second == 'LPAREN': 31 | return self.function_call(name) 32 | else: 33 | assert False, "Not a FUNCDEF or FUNCCALL" 34 | 35 | def function_definition(self): 36 | """ 37 | funcdef = DEF name LPAREN params RPAREN COLON body 38 | I ignore body for this example 'cause that's hard. 39 | I mean, so you can learn how to do it. 40 | """ 41 | self.skip('DEF') 42 | name = self.match('NAME') 43 | self.match('LPAREN') 44 | params = self.parameters() 45 | self.skip('RPAREN', 'COLON') 46 | body = self.function_body() 47 | return prod.FuncDef(name, params, body) 48 | 49 | def function_body(self): 50 | body = [] 51 | while self.skip("INDENT"): 52 | body.append(self.expression()) 53 | return body 54 | 55 | def parameters(self): 56 | """params = expression *(COMMA expression)""" 57 | params = [] 58 | start = self.peek() 59 | while start != 'RPAREN': 60 | params.append(self.expression()) 61 | start = self.peek() 62 | if start != 'RPAREN': 63 | assert self.skip('COMMA') 64 | return prod.Parameters(params) 65 | 66 | def function_call(self, name): 67 | """funccall = name LPAREN params RPAREN""" 68 | self.match('LPAREN') 69 | params = self.parameters() 70 | self.match('RPAREN') 71 | return prod.FuncCall(name, params) 72 | 73 | def expression(self): 74 | """expression = name / funccall / plus / integer""" 75 | start = self.peek() 76 | 77 | if start == 'NAME': 78 | name = self.match('NAME') 79 | nameexpr = prod.NameExpr(name) 80 | 81 | expr = self.peek() 82 | 83 | if expr == 'PLUS': 84 | return self.plus(nameexpr) 85 | elif expr == 'LPAREN': 86 | return self.function_call(name) 87 | else: 88 | return nameexpr 89 | elif start == 'INTEGER': 90 | number = self.match('INTEGER') 91 | numexpr = prod.IntExpr(number) 92 | if self.peek() == 'PLUS': 93 | return self.plus(numexpr) 94 | else: 95 | return numexpr 96 | else: 97 | assert False, "Syntax error %r" % start 98 | 99 | def plus(self, left): 100 | """plus = expression PLUS expression""" 101 | self.match('PLUS') 102 | right = self.expression() 103 | return prod.AddExpr(left, right) 104 | 105 | class PunyPyWorld(object): 106 | 107 | def __init__(self, variables): 108 | self.variables = variables 109 | self.functions = {} 110 | 111 | 112 | -------------------------------------------------------------------------------- /ex34_analyzers/punypy/scanner.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | class Scanner(object): 4 | 5 | def __init__(self, regex, code): 6 | self.regex_list = regex 7 | self.code = code 8 | self.tokens = self.scan(code) 9 | 10 | def ignore_ws(self): 11 | while self.tokens[0][0] == 'INDENT': 12 | self.tokens.pop(0) 13 | 14 | def match(self, token_id): 15 | if token_id != 'INDENT': 16 | self.ignore_ws() 17 | 18 | if self.tokens[0][0] == token_id: 19 | return self.tokens.pop(0) 20 | else: 21 | return ['ERROR', 'error'] 22 | 23 | def peek(self): 24 | self.ignore_ws() 25 | return self.tokens[0][0] 26 | 27 | def skip(self, *what): 28 | for x in what: 29 | if x != 'INDENT': self.ignore_ws() 30 | 31 | tok = self.tokens[0] 32 | if tok[0] != x: 33 | return False 34 | else: 35 | self.tokens.pop(0) 36 | 37 | return True 38 | 39 | 40 | def match_regex(self, i, line): 41 | start = line[i:] 42 | for regex, token in self.regex_list: 43 | tok = regex.match(start) 44 | if tok: 45 | begin, end = tok.span() 46 | return token, start[:end], end 47 | return None, start, None 48 | 49 | 50 | def scan(self, code): 51 | self.script = [] 52 | 53 | for line in code: 54 | i = 0 55 | while i < len(line): 56 | token, string, end = self.match_regex(i, line) 57 | assert token, "Failed to match line %s" % string 58 | if token: 59 | i += end 60 | self.script.append((token, string, i, end)) 61 | 62 | return self.script 63 | 64 | def done(self): 65 | return len(self.tokens) == 0 66 | -------------------------------------------------------------------------------- /ex34_analyzers/tests/test_parser.py: -------------------------------------------------------------------------------- 1 | from punypy.run import TOKENS, PunyPyParser, PunyPyWorld 2 | from punypy.scanner import Scanner 3 | from punypy.analyzer import PunyPyAnalyzer 4 | from tests.test_scanner import code 5 | 6 | def test_Parser(): 7 | scanner = Scanner(TOKENS, code) 8 | parser = PunyPyParser(scanner) 9 | return parser.parse() 10 | 11 | 12 | def test_Analyzer(): 13 | variables = {} 14 | world = PunyPyWorld(variables) 15 | # simulate hello(10 + 20) 16 | script = test_Parser() 17 | print(script) 18 | analyzer = PunyPyAnalyzer(script, world) 19 | return analyzer.analyze(), world 20 | 21 | -------------------------------------------------------------------------------- /ex34_analyzers/tests/test_scanner.py: -------------------------------------------------------------------------------- 1 | from punypy.run import TOKENS 2 | from punypy.scanner import Scanner 3 | 4 | code = [ 5 | "def hello(x, y):", 6 | " print(x + y)", 7 | "hello(10, 20)", 8 | ] 9 | 10 | 11 | def test_Scanner(): 12 | s = Scanner(TOKENS, code) 13 | assert s.skip("DEF") 14 | assert s.match("NAME")[1] == "hello" 15 | assert s.skip("LPAREN") 16 | assert s.peek() == "NAME" 17 | assert s.match("NAME")[1] == "x" 18 | assert s.skip("COMMA") 19 | assert s.match("NAME")[1] == "y" 20 | assert s.skip("RPAREN", "COLON") 21 | assert s.match("INDENT")[1] == " " 22 | assert s.match("NAME")[1] == "print" 23 | assert s.skip("LPAREN") 24 | assert s.match("NAME")[1] == "x" 25 | assert s.match("PLUS")[0] == "PLUS" 26 | assert s.match("NAME")[1] == "y" 27 | assert s.skip("RPAREN") 28 | assert s.peek() != "INDENT" 29 | assert s.match("NAME")[1] == "hello" 30 | assert s.skip("LPAREN") 31 | assert s.match("INTEGER")[1] == "10" 32 | assert s.skip("COMMA") 33 | assert s.match("INTEGER")[1] == "20" 34 | assert s.skip("RPAREN") 35 | 36 | 37 | -------------------------------------------------------------------------------- /ex35_punypy/punypy/analyzer.py: -------------------------------------------------------------------------------- 1 | 2 | class PunyPyAnalyzer(object): 3 | def __init__(self, parse_tree, world): 4 | self.parse_tree = parse_tree 5 | self.world = world 6 | 7 | def analyze(self): 8 | for node in self.parse_tree: 9 | node.analyze(self.world) 10 | 11 | return self.parse_tree 12 | -------------------------------------------------------------------------------- /ex35_punypy/punypy/interpreter.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /ex35_punypy/punypy/parser.py: -------------------------------------------------------------------------------- 1 | from pprint import pprint 2 | 3 | 4 | class Parser(object): 5 | 6 | def __init__(self, scanner): 7 | self.scanner = scanner 8 | 9 | def match(self, token_id): 10 | return self.scanner.match(token_id) 11 | 12 | def peek(self): 13 | return self.scanner.peek() 14 | 15 | def skip(self, *what): 16 | return self.scanner.skip(*what) 17 | 18 | def root(self): 19 | pass 20 | 21 | def parse(self): 22 | results = [] 23 | while not self.scanner.done(): 24 | results.append(self.root()) 25 | return results 26 | 27 | 28 | -------------------------------------------------------------------------------- /ex35_punypy/punypy/productions.py: -------------------------------------------------------------------------------- 1 | class Production(object): 2 | def analyze(self, world): 3 | """Implement your analyzer here.""" 4 | 5 | def interpret(self, world): 6 | """Implement your interpreter here.""" 7 | 8 | class FuncCall(Production): 9 | 10 | def __init__(self, token, params): 11 | self.name = token[1] 12 | self.params = params 13 | self.token = token 14 | 15 | def analyze(self, world): 16 | self.params.analyze(world) 17 | 18 | def interpret(self, world): 19 | funcdef = world.functions[self.name] 20 | funcdef.call(world, self.params) 21 | 22 | def __repr__(self): 23 | return f"FuncCall({self.name}: {self.params})" 24 | 25 | class Parameters(Production): 26 | 27 | def __init__(self, expressions): 28 | self.expressions = expressions 29 | 30 | def analyze(self, world): 31 | for expr in self.expressions: 32 | expr.analyze(world) 33 | 34 | def interpret(self, world): 35 | return [x.interpret(world) for x in self.expressions] 36 | 37 | def __repr__(self): 38 | return f"Parameters({self.expressions})" 39 | 40 | class Expr(Production): pass 41 | 42 | class NameExpr(Expr): 43 | def __init__(self, token): 44 | self.name = token[1] 45 | self.token = token 46 | 47 | def interpret(self, world): 48 | # This should point at an IntExpr for now 49 | ref = world.variables.get(self.name) 50 | return ref.interpret(world) 51 | 52 | def __repr__(self): 53 | return f"NameExpr({self.name})" 54 | 55 | class IntExpr(Expr): 56 | def __init__(self, token): 57 | self.integer = int(token[1]) 58 | self.token = token 59 | 60 | def __repr__(self): 61 | return f"IntExpr({self.integer})" 62 | 63 | def interpret(self, world): 64 | return self.integer 65 | 66 | 67 | class AddExpr(Expr): 68 | def __init__(self, left, right): 69 | self.left = left 70 | self.right = right 71 | 72 | def analyze(self, world): 73 | self.left.analyze(world) 74 | self.right.analyze(world) 75 | 76 | def interpret(self, world): 77 | return self.left.interpret(world) + self.right.interpret(world) 78 | 79 | 80 | def __repr__(self): 81 | return f"AddExpr({self.left}, {self.right})" 82 | 83 | class FuncDef(Production): 84 | 85 | def __init__(self, token, params, body): 86 | self.name = token[1] 87 | self.params = params 88 | self.body = body 89 | self.token = token 90 | 91 | def analyze(self, world): 92 | world.functions[self.name] = self 93 | 94 | def __repr__(self): 95 | return f"FuncDef({self.name}({self.params}): {self.body}" 96 | 97 | def call(self, world, params): 98 | params = params or Parameters() 99 | 100 | scope = world.clone() 101 | for i, p in enumerate(self.params.expressions): 102 | scope.variables[p.name] = params.expressions[i] 103 | 104 | for line in self.body: 105 | line.interpret(scope) 106 | 107 | class PrintFuncDef(Production): 108 | 109 | def call(self, world, params): 110 | print(*params.interpret(world)) 111 | 112 | 113 | -------------------------------------------------------------------------------- /ex35_punypy/punypy/scanner.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | class Scanner(object): 4 | 5 | def __init__(self, regex, code): 6 | self.regex_list = regex 7 | self.code = code 8 | self.tokens = self.scan(code) 9 | 10 | def ignore_ws(self): 11 | while self.tokens and self.tokens[0][0] == 'INDENT': 12 | self.tokens.pop(0) 13 | 14 | def match(self, token_id): 15 | if token_id != 'INDENT': 16 | self.ignore_ws() 17 | 18 | if self.tokens[0][0] == token_id: 19 | return self.tokens.pop(0) 20 | else: 21 | return ['ERROR', 'error'] 22 | 23 | def peek(self): 24 | self.ignore_ws() 25 | return self.tokens[0][0] 26 | 27 | def skip(self, *what): 28 | for x in what: 29 | if x != 'INDENT': self.ignore_ws() 30 | 31 | tok = self.tokens[0] 32 | if tok[0] != x: 33 | return False 34 | else: 35 | self.tokens.pop(0) 36 | 37 | return True 38 | 39 | 40 | def match_regex(self, i, line): 41 | start = line[i:] 42 | for regex, token in self.regex_list: 43 | tok = regex.match(start) 44 | if tok: 45 | begin, end = tok.span() 46 | return token, start[:end], end 47 | return None, start, None 48 | 49 | 50 | def scan(self, code): 51 | self.script = [] 52 | 53 | for line in code: 54 | i = 0 55 | line = line.rstrip() 56 | while i < len(line): 57 | token, string, end = self.match_regex(i, line) 58 | assert token, "Failed to match line %s" % string 59 | if token: 60 | i += end 61 | self.script.append((token, string, i, end)) 62 | 63 | return self.script 64 | 65 | def done(self): 66 | return len(self.tokens) == 0 67 | -------------------------------------------------------------------------------- /ex35_punypy/test1.ppy: -------------------------------------------------------------------------------- 1 | def hello(x, y): 2 | print(x + y) 3 | 4 | hello(40, 100) 5 | hello(10, 50) 6 | 7 | print(1,2,3,4,5) 8 | 9 | hello(1000000 + 3000, 1) 10 | -------------------------------------------------------------------------------- /ex35_punypy/tests/test_parser.py: -------------------------------------------------------------------------------- 1 | from punypy.run import TOKENS, PunyPyParser, PunyPyWorld 2 | from punypy.scanner import Scanner 3 | from punypy.analyzer import PunyPyAnalyzer 4 | from tests.test_scanner import code 5 | 6 | def test_Parser(): 7 | scanner = Scanner(TOKENS, code) 8 | parser = PunyPyParser(scanner) 9 | return parser.parse() 10 | 11 | 12 | def test_Analyzer(): 13 | variables = {} 14 | world = PunyPyWorld(variables) 15 | 16 | script = test_Parser() 17 | print(script) 18 | analyzer = PunyPyAnalyzer(script, world) 19 | return analyzer.analyze(), world 20 | 21 | def test_Interpreter(): 22 | prods, world = test_Analyzer() 23 | for prod in prods: 24 | prod.interpret(world) 25 | 26 | -------------------------------------------------------------------------------- /ex35_punypy/tests/test_scanner.py: -------------------------------------------------------------------------------- 1 | from punypy.run import TOKENS 2 | from punypy.scanner import Scanner 3 | 4 | code = [ 5 | "def hello(x, y):\n", 6 | " print(x + y)\n", 7 | "hello(10, 20)\n", 8 | ] 9 | 10 | 11 | def test_Scanner(): 12 | s = Scanner(TOKENS, code) 13 | assert s.skip("DEF") 14 | assert s.match("NAME")[1] == "hello" 15 | assert s.skip("LPAREN") 16 | assert s.peek() == "NAME" 17 | assert s.match("NAME")[1] == "x" 18 | assert s.skip("COMMA") 19 | assert s.match("NAME")[1] == "y" 20 | assert s.skip("RPAREN", "COLON") 21 | assert s.match("INDENT")[1] == " " 22 | assert s.match("NAME")[1] == "print" 23 | assert s.skip("LPAREN") 24 | assert s.match("NAME")[1] == "x" 25 | assert s.match("PLUS")[0] == "PLUS" 26 | assert s.match("NAME")[1] == "y" 27 | assert s.skip("RPAREN") 28 | assert s.peek() != "INDENT" 29 | assert s.match("NAME")[1] == "hello" 30 | assert s.skip("LPAREN") 31 | assert s.match("INTEGER")[1] == "10" 32 | assert s.skip("COMMA") 33 | assert s.match("INTEGER")[1] == "20" 34 | assert s.skip("RPAREN") 35 | 36 | 37 | -------------------------------------------------------------------------------- /ex36_calc/calc/analyzer.py: -------------------------------------------------------------------------------- 1 | 2 | class CalcAnalyzer(object): 3 | def __init__(self, parse_tree, world): 4 | self.parse_tree = parse_tree 5 | self.world = world 6 | 7 | def analyze(self): 8 | for node in self.parse_tree: 9 | node.analyze(self.world) 10 | 11 | return self.parse_tree 12 | -------------------------------------------------------------------------------- /ex36_calc/calc/parser.py: -------------------------------------------------------------------------------- 1 | from pprint import pprint 2 | 3 | 4 | class Parser(object): 5 | 6 | def __init__(self, scanner): 7 | self.scanner = scanner 8 | 9 | def match(self, token_id): 10 | return self.scanner.match(token_id) 11 | 12 | def peek(self): 13 | return self.scanner.peek() 14 | 15 | def skip(self, *what): 16 | return self.scanner.skip(*what) 17 | 18 | def root(self): 19 | pass 20 | 21 | def parse(self): 22 | results = [] 23 | while not self.scanner.done(): 24 | results.append(self.root()) 25 | return results 26 | 27 | 28 | -------------------------------------------------------------------------------- /ex36_calc/calc/productions.py: -------------------------------------------------------------------------------- 1 | class Production(object): 2 | def analyze(self, world): 3 | """Implement your analyzer here.""" 4 | 5 | def interpret(self, world): 6 | """Implement your interpreter here.""" 7 | 8 | 9 | class Expr(Production): pass 10 | 11 | class NameExpr(Expr): 12 | def __init__(self, token): 13 | self.name = token[1] 14 | self.token = token 15 | 16 | def interpret(self, world): 17 | # This should point at an IntExpr for now 18 | ref = world.variables.get(self.name) 19 | return ref.interpret(world) 20 | 21 | def __repr__(self): 22 | return f"NameExpr({self.name})" 23 | 24 | class AssignExpr(Expr): 25 | 26 | def __init__(self, name, expr): 27 | self.name = name 28 | self.expr = expr 29 | 30 | def interpret(self, world): 31 | world.variables[self.name.name] = self.expr 32 | return self.expr.interpret(world) 33 | 34 | def __repr__(self): 35 | return f"{self.name} = {self.expr}" 36 | 37 | 38 | class IntExpr(Expr): 39 | def __init__(self, token): 40 | self.integer = int(token[1]) 41 | self.token = token 42 | 43 | def __repr__(self): 44 | return f"IntExpr({self.integer})" 45 | 46 | def interpret(self, world): 47 | return self.integer 48 | 49 | 50 | class InfixExpr(Expr): 51 | def __init__(self, left, op, right): 52 | self.left = left 53 | self.op = op 54 | self.right = right 55 | 56 | def analyze(self, world): 57 | self.left.analyze(world) 58 | self.right.analyze(world) 59 | 60 | def interpret(self, world): 61 | left_n = self.left.interpret(world) 62 | right_n = self.right.interpret(world) 63 | if self.op == 'PLUS': 64 | return left_n + right_n 65 | elif self.op == 'MINUS': 66 | return left_n + right_n 67 | elif self.op == 'DIV': 68 | return left_n / right_n 69 | elif self.op == 'MULT': 70 | return left_n * right_n 71 | else: 72 | assert False, "Nope." 73 | 74 | def __repr__(self): 75 | return f"AddExpr({self.left}, {self.right})" 76 | 77 | -------------------------------------------------------------------------------- /ex36_calc/calc/run.py: -------------------------------------------------------------------------------- 1 | import re 2 | from calc.scanner import Scanner 3 | from calc.parser import Parser 4 | from calc.analyzer import CalcAnalyzer 5 | from calc import productions as prod 6 | import sys 7 | 8 | TOKENS = [ 9 | (re.compile(r"^[a-zA-Z_][a-zA-Z0-9_]*"), "NAME"), 10 | (re.compile(r"^[0-9]+"), "INTEGER"), 11 | (re.compile(r"^\="), "EQUAL"), 12 | (re.compile(r"^\+"), "PLUS"), 13 | (re.compile(r"^\-"), "MINUS"), 14 | (re.compile(r"^\*"), "MULT"), 15 | (re.compile(r"^/"), "DIV"), 16 | (re.compile(r"^\s+"), "SPACE"), 17 | ] 18 | 19 | class CalcParser(Parser): 20 | 21 | def root(self): 22 | """root = *(expression)""" 23 | return self.expression() 24 | 25 | def expression(self): 26 | """expression = name / assign / infix / integer""" 27 | start = self.peek() 28 | 29 | if start == 'NAME': 30 | name = self.match('NAME') 31 | nameexpr = prod.NameExpr(name) 32 | 33 | op = self.peek() 34 | 35 | if op in ['PLUS', 'MINUS', 'DIV', 'MULT']: 36 | return self.infix(nameexpr, op) 37 | elif op == 'EQUAL': 38 | return self.assign(nameexpr) 39 | else: 40 | return nameexpr 41 | elif start == 'INTEGER': 42 | number = self.match('INTEGER') 43 | numexpr = prod.IntExpr(number) 44 | op = self.peek() 45 | 46 | if op in ['PLUS', 'MINUS', 'DIV', 'MULT']: 47 | return self.infix(numexpr, op) 48 | else: 49 | return numexpr 50 | else: 51 | assert False, "Syntax error %r" % start 52 | 53 | def assign(self, name): 54 | self.skip('EQUAL') 55 | right = self.expression() 56 | return prod.AssignExpr(name, right) 57 | 58 | def infix(self, left, op): 59 | """plus = expression PLUS expression""" 60 | self.match(op) 61 | right = self.expression() 62 | return prod.InfixExpr(left, op, right) 63 | 64 | class CalcWorld(object): 65 | 66 | def __init__(self, variables): 67 | self.variables = variables 68 | self.functions = {} 69 | 70 | def clone(self): 71 | """Sort of a lame way to implement scope.""" 72 | temp = CalcWorld(self.variables.copy()) 73 | temp.functions = self.functions 74 | return temp 75 | 76 | 77 | def run(script): 78 | scanner = Scanner(TOKENS, script) 79 | parser = CalcParser(scanner) 80 | parse_tree = parser.parse() 81 | world = CalcWorld({}) 82 | analyzer = CalcAnalyzer(parse_tree, world) 83 | prods = analyzer.analyze() 84 | for prod in prods: 85 | print(prod.interpret(world)) 86 | 87 | if __name__ == "__main__": 88 | _, script = sys.argv 89 | 90 | run(open(script).readlines()) 91 | -------------------------------------------------------------------------------- /ex36_calc/calc/scanner.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | class Scanner(object): 4 | 5 | def __init__(self, regex, code): 6 | self.regex_list = regex 7 | self.code = code 8 | self.tokens = self.scan(code) 9 | 10 | def ignore_ws(self): 11 | while self.tokens and self.tokens[0][0] == 'SPACE': 12 | self.tokens.pop(0) 13 | 14 | def match(self, token_id): 15 | if token_id != 'SPACE': 16 | self.ignore_ws() 17 | 18 | if self.tokens[0][0] == token_id: 19 | return self.tokens.pop(0) 20 | else: 21 | return ['ERROR', 'error'] 22 | 23 | def peek(self): 24 | self.ignore_ws() 25 | if self.tokens: 26 | return self.tokens[0][0] 27 | else: 28 | return ['END', 'end'] 29 | 30 | def skip(self, *what): 31 | for x in what: 32 | if x != 'SPACE': self.ignore_ws() 33 | 34 | tok = self.tokens[0] 35 | if tok[0] != x: 36 | return False 37 | else: 38 | self.tokens.pop(0) 39 | 40 | return True 41 | 42 | 43 | def match_regex(self, i, line): 44 | start = line[i:] 45 | for regex, token in self.regex_list: 46 | tok = regex.match(start) 47 | if tok: 48 | begin, end = tok.span() 49 | return token, start[:end], end 50 | return None, start, None 51 | 52 | 53 | def scan(self, code): 54 | self.script = [] 55 | 56 | for line in code: 57 | i = 0 58 | line = line.rstrip() 59 | while i < len(line): 60 | token, string, end = self.match_regex(i, line) 61 | assert token, "Failed to match line %s" % string 62 | if token: 63 | i += end 64 | self.script.append((token, string, i, end)) 65 | 66 | return self.script 67 | 68 | def done(self): 69 | return len(self.tokens) == 0 70 | -------------------------------------------------------------------------------- /ex36_calc/test1.calc: -------------------------------------------------------------------------------- 1 | x = 10 2 | y = 100 3 | z = 23 4 | 5 | j = x * y + 100 / x - z 6 | 7 | 13 / 4 + 7 * 14 8 | 42 * 300 / 1000000 9 | -------------------------------------------------------------------------------- /ex36_calc/tests/test_parser.py: -------------------------------------------------------------------------------- 1 | from calc.run import TOKENS, CalcParser, CalcWorld 2 | from calc.scanner import Scanner 3 | from calc.analyzer import CalcAnalyzer 4 | from tests.test_scanner import code 5 | 6 | def test_Parser(): 7 | scanner = Scanner(TOKENS, code) 8 | parser = CalcParser(scanner) 9 | return parser.parse() 10 | 11 | 12 | def test_Analyzer(): 13 | variables = {} 14 | world = CalcWorld(variables) 15 | 16 | script = test_Parser() 17 | print(script) 18 | analyzer = CalcAnalyzer(script, world) 19 | return analyzer.analyze(), world 20 | 21 | def test_Interpreter(): 22 | prods, world = test_Analyzer() 23 | for prod in prods: 24 | prod.interpret(world) 25 | 26 | -------------------------------------------------------------------------------- /ex36_calc/tests/test_scanner.py: -------------------------------------------------------------------------------- 1 | from calc.run import TOKENS 2 | from calc.scanner import Scanner 3 | 4 | code = open("test1.calc").readlines() 5 | 6 | def test_Scanner(): 7 | s = Scanner(TOKENS, code) 8 | 9 | -------------------------------------------------------------------------------- /ex37_basic/basic/analyzer.py: -------------------------------------------------------------------------------- 1 | 2 | class BasicAnalyzer(object): 3 | def __init__(self, parse_tree, world): 4 | self.parse_tree = parse_tree 5 | self.world = world 6 | 7 | def analyze(self): 8 | for line_no, node in self.parse_tree: 9 | node.analyze(self.world) 10 | 11 | return self.parse_tree 12 | -------------------------------------------------------------------------------- /ex37_basic/basic/parser.py: -------------------------------------------------------------------------------- 1 | from pprint import pprint 2 | 3 | 4 | class Parser(object): 5 | 6 | def __init__(self, scanner): 7 | self.scanner = scanner 8 | 9 | def match(self, token_id): 10 | return self.scanner.match(token_id) 11 | 12 | def peek(self): 13 | return self.scanner.peek() 14 | 15 | def skip(self, *what): 16 | return self.scanner.skip(*what) 17 | 18 | def root(self): 19 | pass 20 | 21 | def parse(self): 22 | results = [] 23 | while not self.scanner.done(): 24 | results.append(self.root()) 25 | return results 26 | 27 | 28 | -------------------------------------------------------------------------------- /ex37_basic/basic/productions.py: -------------------------------------------------------------------------------- 1 | class Production(object): 2 | def analyze(self, world): 3 | """Implement your analyzer here.""" 4 | 5 | def interpret(self, world): 6 | """Implement your interpreter here.""" 7 | 8 | 9 | class Expr(Production): pass 10 | 11 | class NameExpr(Expr): 12 | def __init__(self, token): 13 | self.name = token[1] 14 | self.token = token 15 | 16 | def interpret(self, world): 17 | # This should point at an IntExpr for now 18 | ref = world.variables.get(self.name) 19 | return ref.interpret(world) 20 | 21 | def __repr__(self): 22 | return f"NameExpr({self.name})" 23 | 24 | class AssignExpr(Expr): 25 | 26 | def __init__(self, name, expr): 27 | self.name = name 28 | self.expr = expr 29 | 30 | def interpret(self, world): 31 | world.variables[self.name.name] = self.expr 32 | return self.expr.interpret(world) 33 | 34 | def __repr__(self): 35 | return f"{self.name} = {self.expr}" 36 | 37 | 38 | class IntExpr(Expr): 39 | def __init__(self, token): 40 | self.integer = int(token[1]) 41 | self.token = token 42 | 43 | def __repr__(self): 44 | return f"IntExpr({self.integer})" 45 | 46 | def interpret(self, world): 47 | return self.integer 48 | 49 | 50 | class InfixExpr(Expr): 51 | def __init__(self, left, op, right): 52 | self.left = left 53 | self.op = op 54 | self.right = right 55 | 56 | def analyze(self, world): 57 | self.left.analyze(world) 58 | self.right.analyze(world) 59 | 60 | def interpret(self, world): 61 | left_n = self.left.interpret(world) 62 | right_n = self.right.interpret(world) 63 | if self.op == 'PLUS': 64 | return left_n + right_n 65 | elif self.op == 'MINUS': 66 | return left_n + right_n 67 | elif self.op == 'DIV': 68 | return left_n / right_n 69 | elif self.op == 'MULT': 70 | return left_n * right_n 71 | else: 72 | assert False, "Nope." 73 | 74 | def __repr__(self): 75 | return f"AddExpr({self.left}, {self.right})" 76 | 77 | class PrintExpr(Expr): 78 | 79 | def __init__(self, expr): 80 | self.expr = expr 81 | 82 | def interpret(self, world): 83 | print(self.expr.interpret(world)) 84 | 85 | class GotoExpr(Expr): 86 | def __init__(self, expr): 87 | self.expr = expr 88 | 89 | def interpret(self, world): 90 | world.goto(self.expr.interpret(world)) 91 | 92 | -------------------------------------------------------------------------------- /ex37_basic/basic/scanner.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | class Scanner(object): 4 | 5 | def __init__(self, regex, code): 6 | self.regex_list = regex 7 | self.code = code 8 | self.tokens = self.scan(code) 9 | 10 | def ignore_ws(self): 11 | while self.tokens and self.tokens[0][0] == 'SPACE': 12 | self.tokens.pop(0) 13 | 14 | def match(self, token_id): 15 | if token_id != 'SPACE': 16 | self.ignore_ws() 17 | 18 | if self.tokens[0][0] == token_id: 19 | return self.tokens.pop(0) 20 | else: 21 | return ['ERROR', 'error'] 22 | 23 | def peek(self): 24 | self.ignore_ws() 25 | if self.tokens: 26 | return self.tokens[0][0] 27 | else: 28 | return ['END', 'end'] 29 | 30 | def error(self): 31 | token, string, line_no, i, end = self.tokens[0] 32 | 33 | return f"ERROR: {token}{string} at line {line_no}@{i}" 34 | 35 | def skip(self, *what): 36 | for x in what: 37 | if x != 'SPACE': self.ignore_ws() 38 | 39 | tok = self.tokens[0] 40 | if tok[0] != x: 41 | return False 42 | else: 43 | self.tokens.pop(0) 44 | 45 | return True 46 | 47 | 48 | def match_regex(self, i, line): 49 | start = line[i:] 50 | for regex, token in self.regex_list: 51 | tok = regex.match(start) 52 | if tok: 53 | begin, end = tok.span() 54 | return token, start[:end], end 55 | return None, start, None 56 | 57 | 58 | def scan(self, code): 59 | self.script = [] 60 | line_no = 0 61 | 62 | for line in code: 63 | i = 0 64 | line = line.rstrip() 65 | line_no += 1 66 | while i < len(line): 67 | token, string, end = self.match_regex(i, line) 68 | assert token, "Failed to match line %s" % string 69 | if token: 70 | i += end 71 | self.script.append((token, string, line_no, i, end)) 72 | 73 | return self.script 74 | 75 | def done(self): 76 | return len(self.tokens) == 0 77 | -------------------------------------------------------------------------------- /ex37_basic/test1.bs: -------------------------------------------------------------------------------- 1 | 10 LET X = 10 2 | 20 LET Y = 100 3 | 30 LET Z = 23 4 | 40 PRINT X 5 | 50 LET J = X * Y + 100 / X - Z 6 | 60 PRINT J 7 | 70 PRINT 13 / 4 + 7 * 14 8 | 80 PRINT 42 * 300 / 1000000 9 | -------------------------------------------------------------------------------- /ex37_basic/test2.bs: -------------------------------------------------------------------------------- 1 | 80 PRINT 42 * 300 / 1000000 2 | 100 GOTO 80 3 | -------------------------------------------------------------------------------- /ex37_basic/tests/test_parser.py: -------------------------------------------------------------------------------- 1 | from basic.run import TOKENS, BasicParser, BasicWorld 2 | from basic.scanner import Scanner 3 | from basic.analyzer import BasicAnalyzer 4 | from tests.test_scanner import code 5 | 6 | def test_Parser(): 7 | scanner = Scanner(TOKENS, code) 8 | parser = BasicParser(scanner) 9 | return parser.parse() 10 | 11 | 12 | def test_Analyzer(): 13 | variables = {} 14 | world = BasicWorld(variables) 15 | 16 | script = test_Parser() 17 | print(script) 18 | analyzer = BasicAnalyzer(script, world) 19 | return analyzer.analyze(), world 20 | 21 | def test_Interpreter(): 22 | prods, world = test_Analyzer() 23 | for line_no, prod in prods: 24 | prod.interpret(world) 25 | 26 | -------------------------------------------------------------------------------- /ex37_basic/tests/test_scanner.py: -------------------------------------------------------------------------------- 1 | from basic.run import TOKENS 2 | from basic.scanner import Scanner 3 | 4 | code = open("test1.bs").readlines() 5 | 6 | def test_Scanner(): 7 | s = Scanner(TOKENS, code) 8 | 9 | -------------------------------------------------------------------------------- /ex39_sql_create/01_create.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE person ( 2 | id INTEGER PRIMARY KEY, 3 | first_name TEXT, 4 | last_name TEXT, 5 | age INTEGER 6 | ); 7 | -------------------------------------------------------------------------------- /ex39_sql_create/02_create.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE person ( 2 | id INTEGER PRIMARY KEY, 3 | first_name TEXT, 4 | last_name TEXT, 5 | age INTEGER 6 | ); 7 | 8 | CREATE TABLE pet ( 9 | id INTEGER PRIMARY KEY, 10 | name TEXT, 11 | breed TEXT, 12 | age INTEGER, 13 | dead INTEGER 14 | ); 15 | 16 | CREATE TABLE person_pet ( 17 | person_id INTEGER, 18 | pet_id INTEGER 19 | ); 20 | 21 | -------------------------------------------------------------------------------- /ex39_sql_create/03_insert.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO person (id, first_name, last_name, age) 2 | VALUES (0, "Zed", "Shaw", 37); 3 | 4 | INSERT INTO pet (id, name, breed, age, dead) 5 | VALUES (0, "Fluffy", "Unicorn", 1000, 0); 6 | 7 | INSERT INTO pet VALUES (1, "Gigantor", "Robot", 1, 1); 8 | -------------------------------------------------------------------------------- /ex39_sql_create/04_insert.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO person_pet (person_id, pet_id) VALUES (0, 0); 2 | INSERT INTO person_pet VALUES (0, 1); 3 | -------------------------------------------------------------------------------- /ex39_sql_create/test.py: -------------------------------------------------------------------------------- 1 | class Person(object): 2 | 3 | def __init__(self, first_name, 4 | last_name, age, pets): 5 | self.first_name = first_name 6 | self.last_name = last_name 7 | self.age = age 8 | self.pets = pets 9 | 10 | class Pet(object): 11 | def __init__(self, name, breed, 12 | age, dead): 13 | self.name = name 14 | self.breed = breed 15 | self.age = age 16 | self.dead = dead 17 | self.owners = [] 18 | 19 | # simulate insert 20 | fluffy = Pet('Fluffy', 'Unicorn', 12, False) 21 | gigantor = Pet('Gigantor', 'Robot', 2, False) 22 | pete = Person("Zed", "Shaw", 43, [fluffy, gigantor]) 23 | fluffy.owners.append(pete) 24 | gigantor.owners.append(pete) 25 | 26 | DB = { 27 | 'person': [ pete ], 28 | 'pet': [fluffy, gigantor], 29 | } 30 | 31 | dead_pets = [pet for pet in DB['pet'] if pet.dead == False] 32 | print(dead_pets) 33 | 34 | 35 | -------------------------------------------------------------------------------- /ex40_sql_reading/01_select.sql: -------------------------------------------------------------------------------- 1 | SELECT * FROM person; 2 | 3 | SELECT name, age FROM pet; 4 | 5 | SELECT name, age FROM pet WHERE dead = 0; 6 | 7 | SELECT * FROM person WHERE first_name != "Zed"; 8 | -------------------------------------------------------------------------------- /ex40_sql_reading/02_select.sql: -------------------------------------------------------------------------------- 1 | SELECT pet.id, pet.name, pet.age, pet.dead 2 | FROM pet, person_pet, person 3 | WHERE 4 | pet.id = person_pet.pet_id AND 5 | person_pet.person_id = person.id AND 6 | person.first_name = "Zed"; 7 | -------------------------------------------------------------------------------- /ex41_sql_updating/01_update.sql: -------------------------------------------------------------------------------- 1 | UPDATE person SET first_name = "Hilarious Guy" 2 | WHERE first_name = "Zed"; 3 | 4 | UPDATE pet SET name = "Fancy Pants" 5 | WHERE id=0; 6 | 7 | SELECT * FROM person; 8 | SELECT * FROM pet; 9 | -------------------------------------------------------------------------------- /ex41_sql_updating/02_update.sql: -------------------------------------------------------------------------------- 1 | SELECT * FROM pet; 2 | 3 | UPDATE pet SET name = "Zed's Pet" WHERE id IN ( 4 | SELECT pet.id 5 | FROM pet, person_pet, person 6 | WHERE 7 | person.id = person_pet.person_id AND 8 | pet.id = person_pet.pet_id AND 9 | person.first_name = "Zed" 10 | ); 11 | 12 | SELECT * FROM pet; 13 | -------------------------------------------------------------------------------- /ex41_sql_updating/03_insert_replace.sql: -------------------------------------------------------------------------------- 1 | /* This should fail because 0 is already taken. */ 2 | INSERT INTO person (id, first_name, last_name, age) 3 | VALUES (0, 'Frank', 'Smith', 100); 4 | 5 | /* We can force it by doing an INSERT OR REPLACE. */ 6 | INSERT OR REPLACE INTO person (id, first_name, last_name, age) 7 | VALUES (0, 'Frank', 'Smith', 100); 8 | 9 | SELECT * FROM person; 10 | 11 | /* And shorthand for that is just REPLACE. */ 12 | REPLACE INTO person (id, first_name, last_name, age) 13 | VALUES (0, 'Zed', 'Shaw', 37); 14 | 15 | /* Now you can see I'm back. */ 16 | SELECT * FROM person; 17 | 18 | -------------------------------------------------------------------------------- /ex42_sql_deleting/01_delete.sql: -------------------------------------------------------------------------------- 1 | /* make sure there's dead pets */ 2 | SELECT name, age FROM pet WHERE dead = 1; 3 | 4 | /* aww poor robot */ 5 | DELETE FROM pet WHERE dead = 1; 6 | 7 | /* make sure the robot is gone */ 8 | SELECT * FROM pet; 9 | 10 | /* let's resurrect the robot */ 11 | INSERT INTO pet VALUES (1, "Gigantor", "Robot", 1, 0); 12 | 13 | /* the robot LIVES! */ 14 | SELECT * FROM pet; 15 | -------------------------------------------------------------------------------- /ex42_sql_deleting/02_delete.sql: -------------------------------------------------------------------------------- 1 | DELETE FROM pet WHERE id IN ( 2 | SELECT pet.id 3 | FROM pet, person_pet, person 4 | WHERE 5 | person.id = person_pet.person_id AND 6 | pet.id = person_pet.pet_id AND 7 | person.first_name = "Zed" 8 | ); 9 | 10 | SELECT * FROM pet; 11 | SELECT * FROM person_pet; 12 | 13 | DELETE FROM person_pet 14 | WHERE pet_id NOT IN ( 15 | SELECT id FROM pet 16 | ); 17 | 18 | SELECT * FROM person_pet; 19 | 20 | -------------------------------------------------------------------------------- /ex43_sql_alter/01_alter.sql: -------------------------------------------------------------------------------- 1 | /* Only drop table if it exists. */ 2 | DROP TABLE IF EXISTS person; 3 | 4 | /* Create again to work with it. */ 5 | CREATE TABLE person ( 6 | id INTEGER PRIMARY KEY, 7 | first_name TEXT, 8 | last_name TEXT, 9 | age INTEGER 10 | ); 11 | 12 | /* Rename the table to peoples. */ 13 | ALTER TABLE person RENAME TO peoples; 14 | 15 | /* Add a hatred column to peoples. */ 16 | ALTER TABLE peoples ADD COLUMN hatred INTEGER; 17 | 18 | /* Rename peoples back to person. */ 19 | ALTER TABLE peoples RENAME TO person; 20 | 21 | .schema person 22 | 23 | /* We don't need that. */ 24 | DROP TABLE person; 25 | -------------------------------------------------------------------------------- /ex43_sql_alter/02_alter.sh-session: -------------------------------------------------------------------------------- 1 | $ sqlite3 ex13.db < code.sql 2 | $ sqlite3 ex13.db .schema 3 | CREATE TABLE person ( 4 | id INTEGER PRIMARY KEY, 5 | first_name TEXT, 6 | last_name TEXT, 7 | age INTEGER 8 | ); 9 | CREATE TABLE person_pet ( 10 | person_id INTEGER, 11 | pet_id INTEGER 12 | ); 13 | CREATE TABLE pet ( 14 | id INTEGER PRIMARY KEY, 15 | name TEXT, 16 | breed TEXT, 17 | age INTEGER, 18 | dead INTEGER, 19 | dob DATETIME 20 | ); 21 | 22 | -------------------------------------------------------------------------------- /ex43_sql_alter/02_alter.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE person ADD COLUMN dead INTEGER; 2 | ALTER TABLE person ADD COLUMN phone_number TEXT; 3 | ALTER TABLE person ADD COLUMN salary FLOAT; 4 | ALTER TABLE person ADD COLUMN dob DATETIME; 5 | ALTER TABLE person_pet ADD COLUMN purchased_on DATETIME; 6 | ALTER TABLE pet ADD COLUMN parent INTEGER; 7 | 8 | 9 | -------------------------------------------------------------------------------- /ex45_orm/02_create.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE person ( 2 | id INTEGER PRIMARY KEY, 3 | first_name TEXT, 4 | last_name TEXT, 5 | age INTEGER 6 | ); 7 | 8 | CREATE TABLE pet ( 9 | id INTEGER PRIMARY KEY, 10 | name TEXT, 11 | breed TEXT, 12 | age INTEGER, 13 | dead INTEGER 14 | ); 15 | 16 | CREATE TABLE person_pet ( 17 | person_id INTEGER, 18 | pet_id INTEGER 19 | ); 20 | 21 | -------------------------------------------------------------------------------- /ex45_orm/simple_orm.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | 3 | CONN = None 4 | 5 | def connect(file_name): 6 | global CONN 7 | CONN = sqlite3.connect(file_name) 8 | 9 | def load_schema(schema_file): 10 | cur = CONN.cursor() 11 | script = open(schema_file).read() 12 | cur.executescript(script) 13 | CONN.commit() 14 | 15 | class Person: 16 | 17 | def __init__(self, first_name, last_name, age): 18 | self.pk = None 19 | self.first_name = first_name 20 | self.last_name = last_name 21 | self.age = age 22 | self.pets = [] 23 | 24 | def create(self): 25 | cur = CONN.cursor() 26 | cur.execute('insert into person(first_name, last_name, age) values (?,?,?)', 27 | (self.first_name, self.last_name, self.age)) 28 | self.pk = cur.lastrowid 29 | CONN.commit() 30 | 31 | def update(self): 32 | cur = CONN.cursor() 33 | cur.execute('update person set first_name=?, last_name=?, age=? where id=?', 34 | (self.first_name, self.last_name, self.age, self.pk)) 35 | CONN.commit() 36 | 37 | def delete(self): 38 | cur = CONN.cursor() 39 | cur.execute('delete from person where id=?', (self.pk,)) 40 | CONN.commit() 41 | 42 | @classmethod 43 | def read(self, pk): 44 | cur = CONN.cursor() 45 | cur.execute('select id, first_name, last_name, age from person where id=?', (pk,)) 46 | row = cur.fetchone() 47 | obj = Person(*row[1:]) 48 | obj.pk = row[0] 49 | return obj 50 | 51 | 52 | 53 | class Pet: 54 | 55 | def __init__(self, name, breed, age, dead): 56 | self.pk = None 57 | self.name = name 58 | self.breed = breed 59 | self.age = age 60 | self.dead = dead 61 | self.owners = [] 62 | 63 | def create(self): 64 | pass 65 | 66 | def update(self): 67 | pass 68 | 69 | def delete(self): 70 | pass 71 | 72 | def read(self, pk): 73 | pass 74 | -------------------------------------------------------------------------------- /ex45_orm/test_orm.py: -------------------------------------------------------------------------------- 1 | from simple_orm import * 2 | import os 3 | 4 | if os.path.exists('data.db'): os.remove('data.db') 5 | connect(':memory:') 6 | load_schema('02_create.sql') 7 | 8 | def test_Person(): 9 | joe = Person('Joe', 'Franks', 35) 10 | mary = Person('Mary', 'Abeline', 25) 11 | joe.create() 12 | mary.create() 13 | joe.first_name = 'Alex' 14 | joe.update() 15 | joe2 = Person.read(joe.pk) 16 | assert joe2.first_name == 'Alex' 17 | joe2.delete() 18 | 19 | 20 | -------------------------------------------------------------------------------- /ex46_blog/bin/blog: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from blog.run import main 4 | import sys 5 | import os 6 | 7 | _, input_dir, output_dir = sys.argv 8 | assert os.path.exists(input_dir) 9 | assert os.path.exists(output_dir) 10 | 11 | main(input_dir, output_dir) 12 | 13 | -------------------------------------------------------------------------------- /ex46_blog/blog/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zedshaw/learn-more-python-the-hard-way-solutions/7886c860f69d69739a41d6490b8dc3fa777f227b/ex46_blog/blog/__init__.py -------------------------------------------------------------------------------- /ex46_blog/blog/run.py: -------------------------------------------------------------------------------- 1 | from mako.template import Template 2 | from markdown import markdown 3 | from glob import glob 4 | import json 5 | from os import path 6 | 7 | def load_template(input_dir): 8 | template_path = path.join(input_dir, "template.html") 9 | assert path.exists(template_path) 10 | return Template(open(template_path).read()) 11 | 12 | def load_config(input_dir): 13 | config_path = path.join(input_dir, "config.json") 14 | assert path.exists(config_path) 15 | return json.load(open(config_path)) 16 | 17 | def load_input_files(input_dir): 18 | assert path.exists(input_dir) 19 | return glob(path.join(input_dir, "*.md")) 20 | 21 | def render_page(md_name, config, template): 22 | # convert it to html 23 | contents = markdown(open(md_name).read()) 24 | 25 | # add it to the config variables 26 | config['contents'] = contents 27 | # process the template 28 | 29 | return template.render(**config) 30 | 31 | def save_result(output_dir, md_name, html): 32 | fname, ext = path.splitext(path.basename(md_name)) 33 | out_name = path.join(output_dir, fname) + ".html" 34 | 35 | with open(out_name, 'w') as f: 36 | f.write(html) 37 | 38 | def main(input_dir, output_dir): 39 | """ 40 | Takes an input directory and output directory 41 | to produce the blog. The input directory needs to have 42 | a template.html, a config.json, and *.md files to process. 43 | """ 44 | 45 | # load the template file 46 | template = load_template(input_dir) 47 | 48 | # load the config.json as a dict 49 | config = load_config(input_dir) 50 | 51 | # list all the .md files 52 | md_files = load_input_files(input_dir) 53 | 54 | # go through each .md file and 55 | for md_name in md_files: 56 | # render it 57 | html = render_page(md_name, config, template) 58 | # save it in the proper location 59 | save_result(output_dir, md_name, html) 60 | 61 | -------------------------------------------------------------------------------- /ex46_blog/setup.py: -------------------------------------------------------------------------------- 1 | try: 2 | from setuptools import setup 3 | except ImportError: 4 | from distutils.core import setup 5 | 6 | config = { 7 | 'description': 'MorePy Blog', 8 | 'author': 'Zed A. Shaw', 9 | 'url': '', 10 | 'download_url': '', 11 | 'author_email': 'help@learncodethehardway.org', 12 | 'version': '0.1', 13 | 'install_requires': ['pytest'], 14 | 'packages': ['blog'], 15 | 'scripts': ['bin/blog'], 16 | 'name': 'blog' 17 | } 18 | 19 | setup(**config) 20 | 21 | -------------------------------------------------------------------------------- /ex46_blog/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zedshaw/learn-more-python-the-hard-way-solutions/7886c860f69d69739a41d6490b8dc3fa777f227b/ex46_blog/tests/__init__.py -------------------------------------------------------------------------------- /ex46_blog/tests/output/day1.html: -------------------------------------------------------------------------------- 1 | 2 |
This is the first day that I am writing on this blog tool I've got here.
7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /ex46_blog/tests/output/index.html: -------------------------------------------------------------------------------- 1 | 2 |This is a first sample index page for my blog.
7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /ex46_blog/tests/sample/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Zed's Blog" 3 | } 4 | -------------------------------------------------------------------------------- /ex46_blog/tests/sample/day1.md: -------------------------------------------------------------------------------- 1 | Day 1 2 | ==== 3 | 4 | This is the first day that I am writing on this blog tool I've got here. 5 | 6 | -------------------------------------------------------------------------------- /ex46_blog/tests/sample/index.md: -------------------------------------------------------------------------------- 1 | 2 | Hello There! 3 | ==== 4 | 5 | This is a first sample index page for my blog. 6 | -------------------------------------------------------------------------------- /ex46_blog/tests/sample/template.html: -------------------------------------------------------------------------------- 1 | 2 |