From 4526c6162fe943b98707f137b28b96e174ed2a4b Mon Sep 17 00:00:00 2001
From: liushengpeng <328232234@qq.com>
Date: Thu, 19 Apr 2018 14:14:18 +0800
Subject: [PATCH 1/8] save
---
.gitignore | 64 +++
README.md | 8 -
ch01/__init__.py | 3 +-
ch01/age1.py | 35 +-
ch01/age2.py | 41 +-
ch01/contains.py | 29 +-
ch01/count.py | 31 +-
ch01/factors1.py | 33 +-
ch01/factors2.py | 29 +-
ch01/factors3.py | 39 +-
ch01/fibonacci1.py | 35 +-
ch01/fibonacci2.py | 29 +-
ch01/gpa1.py | 58 +--
ch01/gpa2.py | 52 +-
ch01/heartrate.py | 23 +-
ch01/range.py | 29 +-
ch01/scale.py | 25 +-
ch01/sum1.py | 38 +-
ch01/sum2.py | 29 +-
ch02/credit_card.py | 149 +++---
ch02/predatory_credit_card.py | 79 ++-
ch02/progressions.py | 187 +++-----
ch02/range.py | 82 ++--
ch02/sequence_abc.py | 74 ++-
ch02/sequence_iterator.py | 51 +-
ch02/vector.py | 145 +++---
ch03/disjoint.py | 52 +-
ch03/exercises.py | 105 ++--
ch03/find.py | 37 +-
ch03/find_max.py | 33 +-
ch03/prefix_averages.py | 69 +--
ch03/unique.py | 46 +-
ch04/binary_search.py | 50 +-
ch04/binary_search_iterative.py | 45 +-
ch04/binary_sum.py | 37 +-
ch04/disk_usage.py | 39 +-
ch04/factorial.py | 29 +-
ch04/fibonacci.py | 44 +-
ch04/linear_sum.py | 31 +-
ch04/power_fast.py | 39 +-
ch04/power_slow.py | 31 +-
ch04/reverse.py | 29 +-
ch04/reverse_iterative.py | 31 +-
ch04/ruler.py | 63 +--
ch04/unique_bad.py | 36 +-
ch05/caesar.py | 82 ++--
ch05/dynamic_array.py | 128 +++--
ch05/experiment_list_append.py | 43 +-
ch05/experiment_list_size.py | 33 +-
ch05/high_scores.py | 127 ++---
ch05/insertion_sort.py | 37 +-
ch05/tic_tac_toe.py | 130 +++--
ch06/array_queue.py | 132 +++--
ch06/array_stack.py | 112 ++---
ch06/match_delimiters.py | 47 +-
ch06/match_html.py | 55 +--
ch06/reverse_file.py | 44 +-
ch07/circular_queue.py | 137 +++---
ch07/doubly_linked_base.py | 124 ++---
ch07/favorites_list.py | 152 +++---
ch07/favorites_list_mtf.py | 87 ++--
ch07/insertion_sort_positional.py | 49 +-
ch07/linked_deque.py | 108 ++---
ch07/linked_queue.py | 136 +++---
ch07/linked_stack.py | 120 ++---
ch07/positional_list.py | 244 +++++-----
ch08/binary_tree.py | 132 +++--
ch08/euler_tour.py | 226 ++++-----
ch08/expression_tree.py | 211 ++++----
ch08/linked_binary_tree.py | 371 +++++++--------
ch08/traversal_examples.py | 88 ++--
ch08/tree.py | 278 +++++------
ch09/adaptable_heap_priority_queue.py | 128 +++--
ch09/heap_priority_queue.py | 152 +++---
ch09/priority_queue_base.py | 106 ++---
ch09/sorted_priority_queue.py | 104 ++--
ch09/unsorted_priority_queue.py | 110 ++---
ch10/__init__.py | 3 +-
ch10/chain_hash_map.py | 68 +--
ch10/cost_performance.py | 65 +--
ch10/hash_map_base.py | 108 ++---
ch10/map_base.py | 50 +-
ch10/multi_map.py | 127 ++---
ch10/probe_hash_map.py | 119 ++---
ch10/sorted_table_map.py | 293 ++++++------
ch10/unsorted_table_map.py | 98 ++--
ch10/word_frequency.py | 40 +-
ch11/avl_tree.py | 141 +++---
ch11/binary_search_tree.py | 661 ++++++++++++--------------
ch11/red_black_tree.py | 195 ++++----
ch11/splay_tree.py | 72 +--
ch12/__init__.py | 3 +-
ch12/decorated_merge_sort.py | 53 +--
ch12/merge_array.py | 66 +--
ch12/merge_nonrecur.py | 75 ++-
ch12/merge_queue.py | 75 ++-
ch12/quick_inplace.py | 62 +--
ch12/quick_queue.py | 74 ++-
ch12/quick_select.py | 50 +-
ch13/find_boyer_moore.py | 63 +--
ch13/find_brute.py | 39 +-
ch13/find_kmp.py | 86 ++--
ch13/lcs.py | 68 +--
ch13/matrix_chain.py | 48 +-
ch14/__init__.py | 3 +-
ch14/bfs.py | 72 +--
ch14/dfs.py | 96 ++--
ch14/graph.py | 300 ++++++------
ch14/graph_examples.py | 197 ++++----
ch14/mst.py | 139 +++---
ch14/partition.py | 107 ++---
ch14/shortest_paths.py | 113 ++---
ch14/topological_sort.py | 72 +--
ch14/transitive_closure.py | 66 +--
114 files changed, 4015 insertions(+), 6228 deletions(-)
create mode 100644 .gitignore
delete mode 100644 README.md
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..e0e9321
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,64 @@
+
+__pycache__/
+*.py[cod]
+*$py.class
+*.so
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+*.manifest
+*.spec
+pip-log.txt
+pip-delete-this-directory.txt
+htmlcov/
+.tox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+.hypothesis/
+*.mo
+*.pot
+*.log
+.static_storage/
+.media/
+local_settings.py
+instance/
+.webassets-cache
+.scrapy
+docs/_build/
+target/
+.ipynb_checkpoints
+.python-version
+celerybeat-schedule
+*.sage.py
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+.spyderproject
+.spyproject
+.ropeproject
+/site
+.mypy_cache/
+.gitignore
+.idea/
diff --git a/README.md b/README.md
deleted file mode 100644
index 0552804..0000000
--- a/README.md
+++ /dev/null
@@ -1,8 +0,0 @@
-data-structures-and-algorithms-in-python
-========================================
-
-"Data Structures and Algorithms in Python" is designed to provide an introduction to data structures and algorithms, including their design, analysis, and implementation. The authors take advantage of the beauty and simplicity of Python to present executable source code that is clear and concise. Furthermore, a consistent object-oriented viewpoint is retained throughout the book, including the use of inheritance, both to maximize code reuse and to draw attention to the clear similarities and differences of various abstract data types and algorithmic approaches.
-
-1. pdf version can be found by IT-ebook http://it-ebooks.info/book/2467/
-2. Hardcopy can be found on http://www.amazon.com/dp/1118290275
-3. Offical website for the book http://as.wiley.com/WileyCDA/WileyTitle/productCd-EHEP002510.html
diff --git a/ch01/__init__.py b/ch01/__init__.py
index b5bd37b..483e88b 100644
--- a/ch01/__init__.py
+++ b/ch01/__init__.py
@@ -1 +1,2 @@
-__all__ = ['contains', 'count', 'factors1', 'factors2', 'factors3', 'fibonacci1', 'fibonacci2', 'gpa2', 'scale', 'sum1', 'sum2']
+__all__ = ['contains', 'count', 'factors1', 'factors2', 'factors3', 'fibonacci1', 'fibonacci2', 'gpa2', 'scale', 'sum1',
+ 'sum2']
diff --git a/ch01/age1.py b/ch01/age1.py
index 6511969..9c384d7 100644
--- a/ch01/age1.py
+++ b/ch01/age1.py
@@ -1,29 +1,8 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
-age = -1 # an initially invalid choice
+age = -1 # an initially invalid choice
while age <= 0:
- try:
- age = int(input('Enter your age in years: '))
- if age <= 0:
- print('Your age must be positive')
- except (ValueError, EOFError):
- print('Invalid response')
+ try:
+ age = int(input('Enter your age in years: '))
+ if age <= 0:
+ print('Your age must be positive')
+ except (ValueError, EOFError):
+ print('Invalid response')
diff --git a/ch01/age2.py b/ch01/age2.py
index ddd5ce9..1fc06ae 100644
--- a/ch01/age2.py
+++ b/ch01/age2.py
@@ -1,32 +1,11 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
-age = -1 # an initially invalid choice
+age = -1 # an initially invalid choice
while age <= 0:
- try:
- age = int(input('Enter your age in years: '))
- if age <= 0:
- print('Your age must be positive')
- except ValueError:
- print('That is an invalid age specification')
- except EOFError:
- print('There was an unexpected error reading input.')
- raise # let's re-raise this exception
+ try:
+ age = int(input('Enter your age in years: '))
+ if age <= 0:
+ print('Your age must be positive')
+ except ValueError:
+ print('That is an invalid age specification')
+ except EOFError:
+ print('There was an unexpected error reading input.')
+ raise # let's re-raise this exception
diff --git a/ch01/contains.py b/ch01/contains.py
index e53e5f2..75efe0d 100644
--- a/ch01/contains.py
+++ b/ch01/contains.py
@@ -1,26 +1,5 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
def contains(data, target):
- for item in target:
- if item == target: # found a match
- return True
- return False
+ for item in target:
+ if item == target: # found a match
+ return True
+ return False
diff --git a/ch01/count.py b/ch01/count.py
index 72e5efc..b7a13b9 100644
--- a/ch01/count.py
+++ b/ch01/count.py
@@ -1,27 +1,6 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
def count(data, target):
- n = 0
- for item in data:
- if item == target: # found a match
- n += 1
- return n
+ n = 0
+ for item in data:
+ if item == target: # found a match
+ n += 1
+ return n
diff --git a/ch01/factors1.py b/ch01/factors1.py
index da839d0..2cb9d32 100644
--- a/ch01/factors1.py
+++ b/ch01/factors1.py
@@ -1,27 +1,6 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
-def factors(n): # traditional function that computes factors
- results = [] # store factors in a new list
- for k in range(1,n+1):
- if n % k == 0: # divides evenly, thus k is a factor
- results.append(k) # add k to the list of factors
- return results # return the entire list
+def factors(n): # traditional function that computes factors
+ results = [] # store factors in a new list
+ for k in range(1, n + 1):
+ if n % k == 0: # divides evenly, thus k is a factor
+ results.append(k) # add k to the list of factors
+ return results # return the entire list
diff --git a/ch01/factors2.py b/ch01/factors2.py
index 3147283..8bca851 100644
--- a/ch01/factors2.py
+++ b/ch01/factors2.py
@@ -1,25 +1,4 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
-def factors(n): # generator that computes factors
- for k in range(1,n+1):
- if n % k == 0: # divides evenly, thus k is a factor
- yield k # yield this factor as next result
+def factors(n): # generator that computes factors
+ for k in range(1, n + 1):
+ if n % k == 0: # divides evenly, thus k is a factor
+ yield k # yield this factor as next result
diff --git a/ch01/factors3.py b/ch01/factors3.py
index f672c84..168cfc9 100644
--- a/ch01/factors3.py
+++ b/ch01/factors3.py
@@ -1,30 +1,9 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
-def factors(n): # generator that computes factors
- k = 1
- while k * k < n: # while k < sqrt(n)
- if n % k == 0:
- yield k
- yield n // k
- k += 1
- if k * k == n: # special case if n is perfect square
- yield k
+def factors(n): # generator that computes factors
+ k = 1
+ while k * k < n: # while k < sqrt(n)
+ if n % k == 0:
+ yield k
+ yield n // k
+ k += 1
+ if k * k == n: # special case if n is perfect square
+ yield k
diff --git a/ch01/fibonacci1.py b/ch01/fibonacci1.py
index f84978e..e685c31 100644
--- a/ch01/fibonacci1.py
+++ b/ch01/fibonacci1.py
@@ -1,29 +1,8 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
def fibonacci():
- a = 0
- b = 1
- while True: # keep going...
- yield a # report value, a, during this pass
- future = a + b
- a = b # this will be next value reported
- b = future # and subsequently this
+ a = 0
+ b = 1
+ while True: # keep going...
+ yield a # report value, a, during this pass
+ future = a + b
+ a = b # this will be next value reported
+ b = future # and subsequently this
diff --git a/ch01/fibonacci2.py b/ch01/fibonacci2.py
index f63993b..240a2bc 100644
--- a/ch01/fibonacci2.py
+++ b/ch01/fibonacci2.py
@@ -1,26 +1,5 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
def fibonacci():
- a, b = 0, 1
- while True:
- yield a
- a, b = b, a+b
+ a, b = 0, 1
+ while True:
+ yield a
+ a, b = b, a + b
diff --git a/ch01/gpa1.py b/ch01/gpa1.py
index 3ddd43e..7e1c1bf 100644
--- a/ch01/gpa1.py
+++ b/ch01/gpa1.py
@@ -1,41 +1,31 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
print('Welcome to the GPA calculator.')
print('Please enter all your letter grades, one per line.')
print('Enter a blank line to designate the end.')
-# map from letter grade to point value
-points = {'A+':4.0, 'A':4.0, 'A-':3.67, 'B+':3.33, 'B':3.0, 'B-':2.67,
- 'C+':2.33, 'C':2.0, 'C':1.67, 'D+':1.33, 'D':1.0, 'F':0.0}
+points = {
+ 'A+': 4.0,
+ 'A': 4.0,
+ 'A-': 3.67,
+ 'B+': 3.33,
+ 'B': 3.0,
+ 'B-': 2.67,
+ 'C+': 2.33,
+ 'C': 2.0,
+ 'C': 1.67,
+ 'D+': 1.33,
+ 'D': 1.0,
+ 'F': 0.0
+}
num_courses = 0
total_points = 0
done = False
while not done:
- grade = input() # read line from user
- if grade == '': # empty line was entered
- done = True
- elif grade not in points: # unrecognized grade entered
- print("Unknown grade '{0}' being ignored".format(grade))
- else:
- num_courses += 1
- total_points += points[grade]
-if num_courses > 0: # avoid division by zero
- print('Your GPA is {0:.3}'.format(total_points / num_courses))
+ grade = input() # read line from user
+ if grade == '': # empty line was entered
+ done = True
+ elif grade not in points: # unrecognized grade entered
+ print("Unknown grade '{0}' being ignored".format(grade))
+ else:
+ num_courses += 1
+ total_points += points[grade]
+if num_courses > 0: # avoid division by zero
+ print('Your GPA is {0:.3}'.format(total_points / num_courses))
diff --git a/ch01/gpa2.py b/ch01/gpa2.py
index 22d48c9..2053d2c 100644
--- a/ch01/gpa2.py
+++ b/ch01/gpa2.py
@@ -1,31 +1,23 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-def compute_gpa(grades, points={'A+':4.0, 'A':4.0, 'A-':3.67, 'B+':3.33,
- 'B':3.0, 'B-':2.67,'C+':2.33, 'C':2.0,
- 'C':1.67, 'D+':1.33, 'D':1.0, 'F':0.0}):
- num_courses = 0
- total_points = 0
- for g in grades:
- if g in points: # a recognizable grade
- num_courses += 1
- total_points += points[g]
- return total_points / num_courses
+
+def compute_gpa(grades, points={
+ 'A+': 4.0,
+ 'A': 4.0,
+ 'A-': 3.67,
+ 'B+': 3.33,
+ 'B': 3.0,
+ 'B-': 2.67,
+ 'C+': 2.33,
+ 'C': 2.0,
+ 'C': 1.67,
+ 'D+': 1.33,
+ 'D': 1.0,
+ 'F': 0.0
+}):
+ num_courses = 0
+ total_points = 0
+ for g in grades:
+ if g in points: # a recognizable grade
+ num_courses += 1
+ total_points += points[g]
+ return total_points / num_courses
diff --git a/ch01/heartrate.py b/ch01/heartrate.py
index ab4c52c..76052da 100644
--- a/ch01/heartrate.py
+++ b/ch01/heartrate.py
@@ -1,25 +1,4 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
age = int(input('Enter your age in years: '))
-max_heart_rate = 206.9 - (0.67 * age) # as per Med Sci Sports Exerc.
+max_heart_rate = 206.9 - (0.67 * age) # as per Med Sci Sports Exerc.
target = 0.65 * max_heart_rate
print('Your target fat-burning heart rate is', target)
diff --git a/ch01/range.py b/ch01/range.py
index 654ac29..e728400 100644
--- a/ch01/range.py
+++ b/ch01/range.py
@@ -1,26 +1,5 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
def range(start, stop=None, step=1):
- if stop is None:
- stop = start
- start = 0
- ...
+ if stop is None:
+ stop = start
+ start = 0
+ ...
diff --git a/ch01/scale.py b/ch01/scale.py
index 580d354..807222e 100644
--- a/ch01/scale.py
+++ b/ch01/scale.py
@@ -1,24 +1,3 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
def scale(data, factor):
- for j in range(len(data)):
- data[j] *= factor
+ for j in range(len(data)):
+ data[j] *= factor
diff --git a/ch01/sum1.py b/ch01/sum1.py
index 3c8c3ab..23f43be 100644
--- a/ch01/sum1.py
+++ b/ch01/sum1.py
@@ -1,32 +1,12 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
import collections
+
def sum(values):
- if not isinstance(values, collections.Iterable):
- raise TypeError('parameter must be an iterable type')
- total = 0
- for v in values:
- if not isinstance(v, (int, float)):
- raise TypeError('elements must be numeric')
- total = total+ v
- return total
+ if not isinstance(values, collections.Iterable):
+ raise TypeError('parameter must be an iterable type')
+ total = 0
+ for v in values:
+ if not isinstance(v, (int, float)):
+ raise TypeError('elements must be numeric')
+ total = total + v
+ return total
diff --git a/ch01/sum2.py b/ch01/sum2.py
index 8c198b1..24a0ed7 100644
--- a/ch01/sum2.py
+++ b/ch01/sum2.py
@@ -1,26 +1,5 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
def sum(values):
- total = 0
- for v in values:
- total = total + v
- return total
+ total = 0
+ for v in values:
+ total = total + v
+ return total
diff --git a/ch02/credit_card.py b/ch02/credit_card.py
index 36899c2..0248fd1 100644
--- a/ch02/credit_card.py
+++ b/ch02/credit_card.py
@@ -1,99 +1,74 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
class CreditCard:
- """A consumer credit card."""
-
- def __init__(self, customer, bank, acnt, limit):
- """Create a new credit card instance.
+ """A consumer credit card."""
- The initial balance is zero.
+ def __init__(self, customer, bank, acnt, limit):
+ """Create a new credit card instance.
+ The initial balance is zero.
+ customer the name of the customer (e.g., 'John Bowman')
+ bank the name of the bank (e.g., 'California Savings')
+ acnt the acount identifier (e.g., '5391 0375 9387 5309')
+ limit credit limit (measured in dollars)
+ """
+ self._customer = customer
+ self._bank = bank
+ self._account = acnt
+ self._limit = limit
+ self._balance = 0
- customer the name of the customer (e.g., 'John Bowman')
- bank the name of the bank (e.g., 'California Savings')
- acnt the acount identifier (e.g., '5391 0375 9387 5309')
- limit credit limit (measured in dollars)
- """
- self._customer = customer
- self._bank = bank
- self._account = acnt
- self._limit = limit
- self._balance = 0
+ def get_customer(self):
+ """Return name of the customer."""
+ return self._customer
- def get_customer(self):
- """Return name of the customer."""
- return self._customer
-
- def get_bank(self):
- """Return the bank's name."""
- return self._bank
+ def get_bank(self):
+ """Return the bank's name."""
+ return self._bank
- def get_account(self):
- """Return the card identifying number (typically stored as a string)."""
- return self._account
+ def get_account(self):
+ """Return the card identifying number (typically stored as a string)."""
+ return self._account
- def get_limit(self):
- """Return current credit limit."""
- return self._limit
+ def get_limit(self):
+ """Return current credit limit."""
+ return self._limit
- def get_balance(self):
- """Return current balance."""
- return self._balance
+ def get_balance(self):
+ """Return current balance."""
+ return self._balance
- def charge(self, price):
- """Charge given price to the card, assuming sufficient credit limit.
+ def charge(self, price):
+ """Charge given price to the card, assuming sufficient credit limit.
+ Return True if charge was processed; False if charge was denied.
+ """
+ if price + self._balance > self._limit: # if charge would exceed limit,
+ return False # cannot accept charge
+ else:
+ self._balance += price
+ return True
- Return True if charge was processed; False if charge was denied.
- """
- if price + self._balance > self._limit: # if charge would exceed limit,
- return False # cannot accept charge
- else:
- self._balance += price
- return True
+ def make_payment(self, amount):
+ """Process customer payment that reduces balance."""
+ self._balance -= amount
- def make_payment(self, amount):
- """Process customer payment that reduces balance."""
- self._balance -= amount
if __name__ == '__main__':
- wallet = []
- wallet.append(CreditCard('John Bowman', 'California Savings',
- '5391 0375 9387 5309', 2500) )
- wallet.append(CreditCard('John Bowman', 'California Federal',
- '3485 0399 3395 1954', 3500) )
- wallet.append(CreditCard('John Bowman', 'California Finance',
- '5391 0375 9387 5309', 5000) )
-
- for val in range(1, 17):
- wallet[0].charge(val)
- wallet[1].charge(2*val)
- wallet[2].charge(3*val)
-
- for c in range(3):
- print('Customer =', wallet[c].get_customer())
- print('Bank =', wallet[c].get_bank())
- print('Account =', wallet[c].get_account())
- print('Limit =', wallet[c].get_limit())
- print('Balance =', wallet[c].get_balance())
- while wallet[c].get_balance() > 100:
- wallet[c].make_payment(100)
- print('New balance =', wallet[c].get_balance())
- print()
+ wallet = []
+ wallet.append(CreditCard('John Bowman', 'California Savings',
+ '5391 0375 9387 5309', 2500))
+ wallet.append(CreditCard('John Bowman', 'California Federal',
+ '3485 0399 3395 1954', 3500))
+ wallet.append(CreditCard('John Bowman', 'California Finance',
+ '5391 0375 9387 5309', 5000))
+ for val in range(1, 17):
+ wallet[0].charge(val)
+ wallet[1].charge(2 * val)
+ wallet[2].charge(3 * val)
+ for c in range(3):
+ print('Customer =', wallet[c].get_customer())
+ print('Bank =', wallet[c].get_bank())
+ print('Account =', wallet[c].get_account())
+ print('Limit =', wallet[c].get_limit())
+ print('Balance =', wallet[c].get_balance())
+ while wallet[c].get_balance() > 100:
+ wallet[c].make_payment(100)
+ print('New balance =', wallet[c].get_balance())
+ print()
diff --git a/ch02/predatory_credit_card.py b/ch02/predatory_credit_card.py
index 8e30bd4..23d9d05 100644
--- a/ch02/predatory_credit_card.py
+++ b/ch02/predatory_credit_card.py
@@ -1,57 +1,34 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
from .credit_card import CreditCard
-class PredatoryCreditCard(CreditCard):
- """An extension to CreditCard that compounds interest and fees."""
-
- def __init__(self, customer, bank, acnt, limit, apr):
- """Create a new predatory credit card instance.
-
- The initial balance is zero.
- customer the name of the customer (e.g., 'John Bowman')
- bank the name of the bank (e.g., 'California Savings')
- acnt the acount identifier (e.g., '5391 0375 9387 5309')
- limit credit limit (measured in dollars)
- apr annual percentage rate (e.g., 0.0825 for 8.25% APR)
- """
- super().__init__(customer, bank, acnt, limit) # call super constructor
- self._apr = apr
+class PredatoryCreditCard(CreditCard):
+ """An extension to CreditCard that compounds interest and fees."""
- def charge(self, price):
- """Charge given price to the card, assuming sufficient credit limit.
+ def __init__(self, customer, bank, acnt, limit, apr):
+ """Create a new predatory credit card instance.
+ The initial balance is zero.
+ customer the name of the customer (e.g., 'John Bowman')
+ bank the name of the bank (e.g., 'California Savings')
+ acnt the acount identifier (e.g., '5391 0375 9387 5309')
+ limit credit limit (measured in dollars)
+ apr annual percentage rate (e.g., 0.0825 for 8.25% APR)
+ """
+ super().__init__(customer, bank, acnt, limit) # call super constructor
+ self._apr = apr
- Return True if charge was processed.
- Return False and assess $5 fee if charge is denied.
- """
- success = super().charge(price) # call inherited method
- if not success:
- self._balance += 5 # assess penalty
- return success # caller expects return value
+ def charge(self, price):
+ """Charge given price to the card, assuming sufficient credit limit.
+ Return True if charge was processed.
+ Return False and assess $5 fee if charge is denied.
+ """
+ success = super().charge(price) # call inherited method
+ if not success:
+ self._balance += 5 # assess penalty
+ return success # caller expects return value
- def process_month(self):
- """Assess monthly interest on outstanding balance."""
- if self._balance > 0:
- # if positive balance, convert APR to monthly multiplicative factor
- monthly_factor = pow(1 + self._apr, 1/12)
- self._balance *= monthly_factor
+ def process_month(self):
+ """Assess monthly interest on outstanding balance."""
+ if self._balance > 0:
+ # if positive balance, convert APR to monthly multiplicative factor
+ monthly_factor = pow(1 + self._apr, 1 / 12)
+ self._balance *= monthly_factor
diff --git a/ch02/progressions.py b/ch02/progressions.py
index 88da9a3..c9023e9 100644
--- a/ch02/progressions.py
+++ b/ch02/progressions.py
@@ -1,130 +1,99 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
class Progression:
- """Iterator producing a generic progression.
-
- Default iterator produces the whole numbers 0, 1, 2, ...
- """
+ """Iterator producing a generic progression.
+ Default iterator produces the whole numbers 0, 1, 2, ...
+ """
- def __init__(self, start=0):
- """Initialize current to the first value of the progression."""
- self._current = start
+ def __init__(self, start=0):
+ """Initialize current to the first value of the progression."""
+ self._current = start
- def _advance(self):
- """Update self._current to a new value.
+ def _advance(self):
+ """Update self._current to a new value.
+ This should be overridden by a subclass to customize progression.
+ By convention, if current is set to None, this designates the
+ end of a finite progression.
+ """
+ self._current += 1
- This should be overridden by a subclass to customize progression.
+ def __next__(self):
+ """Return the next element, or else raise StopIteration error."""
+ if self._current is None: # our convention to end a progression
+ raise StopIteration()
+ else:
+ answer = self._current # record current value to return
+ self._advance() # advance to prepare for next time
+ return answer # return the answer
- By convention, if current is set to None, this designates the
- end of a finite progression.
- """
- self._current += 1
+ def __iter__(self):
+ """By convention, an iterator must return itself as an iterator."""
+ return self
- def __next__(self):
- """Return the next element, or else raise StopIteration error."""
- if self._current is None: # our convention to end a progression
- raise StopIteration()
- else:
- answer = self._current # record current value to return
- self._advance() # advance to prepare for next time
- return answer # return the answer
+ def print_progression(self, n):
+ """Print next n values of the progression."""
+ print(' '.join(str(next(self)) for j in range(n)))
- def __iter__(self):
- """By convention, an iterator must return itself as an iterator."""
- return self
-
- def print_progression(self, n):
- """Print next n values of the progression."""
- print(' '.join(str(next(self)) for j in range(n)))
class ArithmeticProgression(Progression): # inherit from Progression
- """Iterator producing an arithmetic progression."""
-
- def __init__(self, increment=1, start=0):
- """Create a new arithmetic progression.
+ """Iterator producing an arithmetic progression."""
- increment the fixed constant to add to each term (default 1)
- start the first term of the progression (default 0)
- """
- super().__init__(start) # initialize base class
- self._increment = increment
-
- def _advance(self): # override inherited version
- """Update current value by adding the fixed increment."""
- self._current += self._increment
-
-
-class GeometricProgression(Progression): # inherit from Progression
- """Iterator producing a geometric progression."""
-
- def __init__(self, base=2, start=1):
- """Create a new geometric progression.
-
- base the fixed constant multiplied to each term (default 2)
- start the first term of the progression (default 1)
- """
- super().__init__(start)
- self._base = base
+ def __init__(self, increment=1, start=0):
+ """Create a new arithmetic progression.
+ increment the fixed constant to add to each term (default 1)
+ start the first term of the progression (default 0)
+ """
+ super().__init__(start) # initialize base class
+ self._increment = increment
- def _advance(self): # override inherited version
- """Update current value by multiplying it by the base value."""
- self._current *= self._base
+ def _advance(self): # override inherited version
+ """Update current value by adding the fixed increment."""
+ self._current += self._increment
-class FibonacciProgression(Progression):
- """Iterator producing a generalized Fibonacci progression."""
-
- def __init__(self, first=0, second=1):
- """Create a new fibonacci progression.
-
- first the first term of the progression (default 0)
- second the second term of the progression (default 1)
- """
- super().__init__(first) # start progression at first
- self._prev = second - first # fictitious value preceding the first
+class GeometricProgression(Progression): # inherit from Progression
+ """Iterator producing a geometric progression."""
- def _advance(self):
- """Update current value by taking sum of previous two."""
- self._prev, self._current = self._current, self._prev + self._current
+ def __init__(self, base=2, start=1):
+ """Create a new geometric progression.
+ base the fixed constant multiplied to each term (default 2)
+ start the first term of the progression (default 1)
+ """
+ super().__init__(start)
+ self._base = base
+ def _advance(self): # override inherited version
+ """Update current value by multiplying it by the base value."""
+ self._current *= self._base
-if __name__ == '__main__':
- print('Default progression:')
- Progression().print_progression(10)
- print('Arithmetic progression with increment 5:')
- ArithmeticProgression(5).print_progression(10)
+class FibonacciProgression(Progression):
+ """Iterator producing a generalized Fibonacci progression."""
- print('Arithmetic progression with increment 5 and start 2:')
- ArithmeticProgression(5, 2).print_progression(10)
+ def __init__(self, first=0, second=1):
+ """Create a new fibonacci progression.
+ first the first term of the progression (default 0)
+ second the second term of the progression (default 1)
+ """
+ super().__init__(first) # start progression at first
+ self._prev = second - first # fictitious value preceding the first
- print('Geometric progression with default base:')
- GeometricProgression().print_progression(10)
+ def _advance(self):
+ """Update current value by taking sum of previous two."""
+ self._prev, self._current = self._current, self._prev + self._current
- print('Geometric progression with base 3:')
- GeometricProgression(3).print_progression(10)
- print('Fibonacci progression with default start values:')
- FibonacciProgression().print_progression(10)
-
- print('Fibonacci progression with start values 4 and 6:')
- FibonacciProgression(4, 6).print_progression(10)
+if __name__ == '__main__':
+ print('Default progression:')
+ Progression().print_progression(10)
+ print('Arithmetic progression with increment 5:')
+ ArithmeticProgression(5).print_progression(10)
+ print('Arithmetic progression with increment 5 and start 2:')
+ ArithmeticProgression(5, 2).print_progression(10)
+ print('Geometric progression with default base:')
+ GeometricProgression().print_progression(10)
+ print('Geometric progression with base 3:')
+ GeometricProgression(3).print_progression(10)
+ print('Fibonacci progression with default start values:')
+ FibonacciProgression().print_progression(10)
+
+ print('Fibonacci progression with start values 4 and 6:')
+ FibonacciProgression(4, 6).print_progression(10)
diff --git a/ch02/range.py b/ch02/range.py
index 6482090..ae78e6e 100644
--- a/ch02/range.py
+++ b/ch02/range.py
@@ -1,55 +1,29 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
class Range:
- """A class that mimic's the built-in range class."""
-
- def __init__(self, start, stop=None, step=1):
- """Initialize a Range instance.
-
- Semantics is similar to built-in range class.
- """
- if step == 0:
- raise ValueError('step cannot be 0')
-
- if stop is None: # special case of range(n)
- start, stop = 0, start # should be treated as if range(0,n)
-
- # calculate the effective length once
- self._length = max(0, (stop - start + step - 1) // step)
-
- # need knowledge of start and step (but not stop) to support __getitem__
- self._start = start
- self._step = step
-
- def __len__(self):
- """Return number of entries in the range."""
- return self._length
-
- def __getitem__(self, k):
- """Return entry at index k (using standard interpretation if negative)."""
- if k < 0:
- k += len(self) # attempt to convert negative index
-
- if not 0 <= k < self._length:
- raise IndexError('index out of range')
-
- return self._start + k * self._step
+ """A class that mimic's the built-in range class."""
+
+ def __init__(self, start, stop=None, step=1):
+ """Initialize a Range instance.
+ Semantics is similar to built-in range class.
+ """
+ if step == 0:
+ raise ValueError('step cannot be 0')
+
+ if stop is None: # special case of range(n)
+ start, stop = 0, start # should be treated as if range(0,n)
+ # calculate the effective length once
+ self._length = max(0, (stop - start + step - 1) // step)
+ # need knowledge of start and step (but not stop) to support __getitem__
+ self._start = start
+ self._step = step
+
+ def __len__(self):
+ """Return number of entries in the range."""
+ return self._length
+
+ def __getitem__(self, k):
+ """Return entry at index k (using standard interpretation if negative)."""
+ if k < 0:
+ k += len(self) # attempt to convert negative index
+ if not 0 <= k < self._length:
+ raise IndexError('index out of range')
+ return self._start + k * self._step
diff --git a/ch02/sequence_abc.py b/ch02/sequence_abc.py
index 520da87..171fec7 100644
--- a/ch02/sequence_abc.py
+++ b/ch02/sequence_abc.py
@@ -1,55 +1,35 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
+from abc import ABCMeta, abstractmethod # need these definitions
-from abc import ABCMeta, abstractmethod # need these definitions
class Sequence(metaclass=ABCMeta):
- """Our own version of collections.Sequence abstract base class."""
+ """Our own version of collections.Sequence abstract base class."""
- @abstractmethod
- def __len__(self):
- """Return the length of the sequence."""
+ @abstractmethod
+ def __len__(self):
+ """Return the length of the sequence."""
- @abstractmethod
- def __getitem__(self, j):
- """Return the element at index j of the sequence."""
+ @abstractmethod
+ def __getitem__(self, j):
+ """Return the element at index j of the sequence."""
- def __contains__(self, val):
- """Return True if val found in the sequence; False otherwise."""
- for j in range(len(self)):
- if self[j] == val: # found match
- return True
- return False
+ def __contains__(self, val):
+ """Return True if val found in the sequence; False otherwise."""
+ for j in range(len(self)):
+ if self[j] == val: # found match
+ return True
+ return False
- def index(self, val):
- """Return leftmost index at which val is found (or raise ValueError)."""
- for j in range(len(self)):
- if self[j] == val: # leftmost match
- return j
- raise ValueError('value not in sequence') # never found a match
+ def index(self, val):
+ """Return leftmost index at which val is found (or raise ValueError)."""
+ for j in range(len(self)):
+ if self[j] == val: # leftmost match
+ return j
+ raise ValueError('value not in sequence') # never found a match
- def count(self, val):
- """Return the number of elements equal to given value."""
- k = 0
- for j in range(len(self)):
- if self[j] == val: # found a match
- k += 1
- return k
+ def count(self, val):
+ """Return the number of elements equal to given value."""
+ k = 0
+ for j in range(len(self)):
+ if self[j] == val: # found a match
+ k += 1
+ return k
diff --git a/ch02/sequence_iterator.py b/ch02/sequence_iterator.py
index 91c7a5f..dbe33ab 100644
--- a/ch02/sequence_iterator.py
+++ b/ch02/sequence_iterator.py
@@ -1,40 +1,19 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
class SequenceIterator:
- """An iterator for any of Python's sequence types."""
+ """An iterator for any of Python's sequence types."""
- def __init__(self, sequence):
- """Create an iterator for the given sequence."""
- self._seq = sequence # keep a reference to the underlying data
- self._k = -1 # will increment to 0 on first call to next
+ def __init__(self, sequence):
+ """Create an iterator for the given sequence."""
+ self._seq = sequence # keep a reference to the underlying data
+ self._k = -1 # will increment to 0 on first call to next
- def __next__(self):
- """Return the next element, or else raise StopIteration error."""
- self._k += 1 # advance to next index
- if self._k < len(self._seq):
- return(self._seq[self._k]) # return the data element
- else:
- raise StopIteration() # there are no more elements
+ def __next__(self):
+ """Return the next element, or else raise StopIteration error."""
+ self._k += 1 # advance to next index
+ if self._k < len(self._seq):
+ return (self._seq[self._k]) # return the data element
+ else:
+ raise StopIteration() # there are no more elements
- def __iter__(self):
- """By convention, an iterator must return itself as an iterator."""
- return self
+ def __iter__(self):
+ """By convention, an iterator must return itself as an iterator."""
+ return self
diff --git a/ch02/vector.py b/ch02/vector.py
index 5d6fb5a..5d4c4c0 100644
--- a/ch02/vector.py
+++ b/ch02/vector.py
@@ -1,98 +1,79 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
import collections
+
class Vector:
- """Represent a vector in a multidimensional space."""
+ """Represent a vector in a multidimensional space."""
+
+ def __init__(self, d):
+ if isinstance(d, int):
+ self._coords = [0] * d
+ else:
+ try: # we test if param is iterable
+ self._coords = [val for val in d]
+ except TypeError:
+ raise TypeError('invalid parameter type')
- def __init__(self, d):
- if isinstance(d, int):
- self._coords = [0] * d
- else:
- try: # we test if param is iterable
- self._coords = [val for val in d]
- except TypeError:
- raise TypeError('invalid parameter type')
+ def __len__(self):
+ """Return the dimension of the vector."""
+ return len(self._coords)
- def __len__(self):
- """Return the dimension of the vector."""
- return len(self._coords)
+ def __getitem__(self, j):
+ """Return jth coordinate of vector."""
+ return self._coords[j]
- def __getitem__(self, j):
- """Return jth coordinate of vector."""
- return self._coords[j]
+ def __setitem__(self, j, val):
+ """Set jth coordinate of vector to given value."""
+ self._coords[j] = val
- def __setitem__(self, j, val):
- """Set jth coordinate of vector to given value."""
- self._coords[j] = val
+ def __add__(self, other):
+ """Return sum of two vectors."""
+ if len(self) != len(other): # relies on __len__ method
+ raise ValueError('dimensions must agree')
+ result = Vector(len(self)) # start with vector of zeros
+ for j in range(len(self)):
+ result[j] = self[j] + other[j]
+ return result
- def __add__(self, other):
- """Return sum of two vectors."""
- if len(self) != len(other): # relies on __len__ method
- raise ValueError('dimensions must agree')
- result = Vector(len(self)) # start with vector of zeros
- for j in range(len(self)):
- result[j] = self[j] + other[j]
- return result
+ def __eq__(self, other):
+ """Return True if vector has same coordinates as other."""
+ return self._coords == other._coords
- def __eq__(self, other):
- """Return True if vector has same coordinates as other."""
- return self._coords == other._coords
+ def __ne__(self, other):
+ """Return True if vector differs from other."""
+ return not self == other # rely on existing __eq__ definition
- def __ne__(self, other):
- """Return True if vector differs from other."""
- return not self == other # rely on existing __eq__ definition
+ def __str__(self):
+ """Produce string representation of vector."""
+ return '<' + str(self._coords)[1:-1] + '>' # adapt list representation
- def __str__(self):
- """Produce string representation of vector."""
- return '<' + str(self._coords)[1:-1] + '>' # adapt list representation
+ def __neg__(self):
+ """Return copy of vector with all coordinates negated."""
+ result = Vector(len(self)) # start with vector of zeros
+ for j in range(len(self)):
+ result[j] = -self[j]
+ return result
- def __neg__(self):
- """Return copy of vector with all coordinates negated."""
- result = Vector(len(self)) # start with vector of zeros
- for j in range(len(self)):
- result[j] = -self[j]
- return result
+ def __lt__(self, other):
+ """Compare vectors based on lexicographical order."""
+ if len(self) != len(other):
+ raise ValueError('dimensions must agree')
+ return self._coords < other._coords
- def __lt__(self, other):
- """Compare vectors based on lexicographical order."""
- if len(self) != len(other):
- raise ValueError('dimensions must agree')
- return self._coords < other._coords
+ def __le__(self, other):
+ """Compare vectors based on lexicographical order."""
+ if len(self) != len(other):
+ raise ValueError('dimensions must agree')
+ return self._coords <= other._coords
- def __le__(self, other):
- """Compare vectors based on lexicographical order."""
- if len(self) != len(other):
- raise ValueError('dimensions must agree')
- return self._coords <= other._coords
if __name__ == '__main__':
- # the following demonstrates usage of a few methods
- v = Vector(5) # construct five-dimensional <0, 0, 0, 0, 0>
- v[1] = 23 # <0, 23, 0, 0, 0> (based on use of __setitem__)
- v[-1] = 45 # <0, 23, 0, 0, 45> (also via __setitem__)
- print(v[4]) # print 45 (via __getitem__)
- u = v + v # <0, 46, 0, 0, 90> (via __add__)
- print(u) # print <0, 46, 0, 0, 90>
- total = 0
- for entry in v: # implicit iteration via __len__ and __getitem__
- total += entry
+ # the following demonstrates usage of a few methods
+ v = Vector(5) # construct five-dimensional <0, 0, 0, 0, 0>
+ v[1] = 23 # <0, 23, 0, 0, 0> (based on use of __setitem__)
+ v[-1] = 45 # <0, 23, 0, 0, 45> (also via __setitem__)
+ print(v[4]) # print 45 (via __getitem__)
+ u = v + v # <0, 46, 0, 0, 90> (via __add__)
+ print(u) # print <0, 46, 0, 0, 90>
+ total = 0
+ for entry in v: # implicit iteration via __len__ and __getitem__
+ total += entry
diff --git a/ch03/disjoint.py b/ch03/disjoint.py
index 74dbcaa..417dbe5 100644
--- a/ch03/disjoint.py
+++ b/ch03/disjoint.py
@@ -1,39 +1,19 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
def disjoint1(A, B, C):
- """Return True if there is no element common to all three lists."""
- for a in A:
- for b in B:
- for c in C:
- if a == b == c:
- return False # we found a common value
- return True # if we reach this, sets are disjoint
+ """Return True if there is no element common to all three lists."""
+ for a in A:
+ for b in B:
+ for c in C:
+ if a == b == c:
+ return False # we found a common value
+ return True # if we reach this, sets are disjoint
+
def disjoint2(A, B, C):
- """Return True if there is no element common to all three lists."""
- for a in A:
- for b in B:
- if a == b: # only check C if we found match from A and B
- for c in C:
- if a == c # (and thus a == b == c)
- return False # we found a common value
- return True # if we reach this, sets are disjoint
+ """Return True if there is no element common to all three lists."""
+ for a in A:
+ for b in B:
+ if a == b: # only check C if we found match from A and B
+ for c in C:
+ if a == c # (and thus a == b == c)
+ return False # we found a common value
+ return True # if we reach this, sets are disjoint
diff --git a/ch03/exercises.py b/ch03/exercises.py
index afc0497..68ee510 100644
--- a/ch03/exercises.py
+++ b/ch03/exercises.py
@@ -1,68 +1,51 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
def example1(S):
- """Return the sum of the elements in sequence S."""
- n = len(S)
- total = 0
- for j in range(n): # loop from 0 to n-1
- total += S[j]
- return total
+ """Return the sum of the elements in sequence S."""
+ n = len(S)
+ total = 0
+ for j in range(n): # loop from 0 to n-1
+ total += S[j]
+ return total
+
def example2(S):
- """Return the sum of the elements with even index in sequence S."""
- n = len(S)
- total = 0
- for j in range(0, n, 2): # note the increment of 2
- total += S[j]
- return total
-
+ """Return the sum of the elements with even index in sequence S."""
+ n = len(S)
+ total = 0
+ for j in range(0, n, 2): # note the increment of 2
+ total += S[j]
+ return total
+
+
def example3(S):
- """Return the sum of the prefix sums of sequence S."""
- n = len(S)
- total = 0
- for j in range(n): # loop from 0 to n-1
- for k in range(1+j): # loop from 0 to j
- total += S[k]
- return total
+ """Return the sum of the prefix sums of sequence S."""
+ n = len(S)
+ total = 0
+ for j in range(n): # loop from 0 to n-1
+ for k in range(1 + j): # loop from 0 to j
+ total += S[k]
+ return total
-def example4(S):
- """Return the sum of the prefix sums of sequence S."""
- n = len(S)
- prefix = 0
- total = 0
- for j in range(n):
- prefix += S[j]
- total += prefix
- return total
-def example5(A, B): # assume that A and B have equal length
- """Return the number of elements in B equal to the sum of prefix sums in A."""
- n = len(A)
- count = 0
- for i in range(n): # loop from 0 to n-1
+def example4(S):
+ """Return the sum of the prefix sums of sequence S."""
+ n = len(S)
+ prefix = 0
total = 0
- for j in range(n): # loop from 0 to n-1
- for k in range(1+j): # loop from 0 to j
- total += A[k]
- if B[i] == total:
- count += 1
- return count
+ for j in range(n):
+ prefix += S[j]
+ total += prefix
+ return total
+
+
+def example5(A, B): # assume that A and B have equal length
+ """Return the number of elements in B equal to the sum of prefix sums in A."""
+ n = len(A)
+ count = 0
+ for i in range(n): # loop from 0 to n-1
+ total = 0
+ for j in range(n): # loop from 0 to n-1
+ for k in range(1 + j): # loop from 0 to j
+ total += A[k]
+ if B[i] == total:
+ count += 1
+ return count
diff --git a/ch03/find.py b/ch03/find.py
index 6221bbb..ec78c30 100644
--- a/ch03/find.py
+++ b/ch03/find.py
@@ -1,30 +1,9 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
def find(S, val):
- """Return index j such that S[j] == val, or -1 if no such element."""
- n = len(S)
- j = 0
- while j < n:
- if S[j] == val:
- return j # a match was found at index j
- j += 1
- return -1
+ """Return index j such that S[j] == val, or -1 if no such element."""
+ n = len(S)
+ j = 0
+ while j < n:
+ if S[j] == val:
+ return j # a match was found at index j
+ j += 1
+ return -1
diff --git a/ch03/find_max.py b/ch03/find_max.py
index e3b5e3a..6b036a5 100644
--- a/ch03/find_max.py
+++ b/ch03/find_max.py
@@ -1,28 +1,7 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
def find_max(data):
- """Return the maximum element from a nonempty Python list."""
- biggest = data[0] # The initial value to beat
- for val in data: # For each value:
- if val > biggest # if it is greater than the best so far,
- biggest = val # we have found a new best (so far)
- return biggest # When loop ends, biggest is the max
+ """Return the maximum element from a nonempty Python list."""
+ biggest = data[0] # The initial value to beat
+ for val in data: # For each value:
+ if val > biggest: # if it is greater than the best so far,
+ biggest = val # we have found a new best (so far)
+ return biggest # When loop ends, biggest is the max
diff --git a/ch03/prefix_averages.py b/ch03/prefix_averages.py
index 00b98fb..9d78143 100644
--- a/ch03/prefix_averages.py
+++ b/ch03/prefix_averages.py
@@ -1,49 +1,30 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
def prefix_average1(S):
- """Return list such that, for all j, A[j] equals average of S[0], ..., S[j]."""
- n = len(S)
- A = [0] * n # create new list of n zeros
- for j in range(n):
- total = 0 # begin computing S[0] + ... + S[j]
- for i in range(j + 1):
- total += S[i]
- A[j] = total / (j+1) # record the average
- return A
+ """Return list such that, for all j, A[j] equals average of S[0], ..., S[j]."""
+ n = len(S)
+ A = [0] * n # create new list of n zeros
+ for j in range(n):
+ total = 0 # begin computing S[0] + ... + S[j]
+ for i in range(j + 1):
+ total += S[i]
+ A[j] = total / (j + 1) # record the average
+ return A
+
def prefix_average2(S):
- """Return list such that, for all j, A[j] equals average of S[0], ..., S[j]."""
- n = len(S)
- A = [0] * n # create new list of n zeros
- for j in range(n):
- A[j] = sum(S[0:j+1]) / (j+1) # record the average
- return A
+ """Return list such that, for all j, A[j] equals average of S[0], ..., S[j]."""
+ n = len(S)
+ A = [0] * n # create new list of n zeros
+ for j in range(n):
+ A[j] = sum(S[0:j + 1]) / (j + 1) # record the average
+ return A
+
def prefix_average3(S):
- """Return list such that, for all j, A[j] equals average of S[0], ..., S[j]."""
- n = len(S)
- A = [0] * n # create new list of n zeros
- total = 0 # compute prefix sum as S[0] + S[1] + ...
- for j in range(n):
- total += S[j] # update prefix sum to include S[j]
- A[j] = total / (j+1) # compute average based on current sum
- return A
+ """Return list such that, for all j, A[j] equals average of S[0], ..., S[j]."""
+ n = len(S)
+ A = [0] * n # create new list of n zeros
+ total = 0 # compute prefix sum as S[0] + S[1] + ...
+ for j in range(n):
+ total += S[j] # update prefix sum to include S[j]
+ A[j] = total / (j + 1) # compute average based on current sum
+ return A
diff --git a/ch03/unique.py b/ch03/unique.py
index ae1f287..6cfbcef 100644
--- a/ch03/unique.py
+++ b/ch03/unique.py
@@ -1,36 +1,16 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
def unique1(S):
- """Return True if there are no duplicate elements in sequence S."""
- for j in range(len(S)):
- for k in range(j+1, len(S)):
- if S[j] == S[k]:
- return False # found duplicate pair
- return True # if we reach this, elements were unique
+ """Return True if there are no duplicate elements in sequence S."""
+ for j in range(len(S)):
+ for k in range(j + 1, len(S)):
+ if S[j] == S[k]:
+ return False # found duplicate pair
+ return True # if we reach this, elements were unique
+
def unique2(S):
- """Return True if there are no duplicate elements in sequence S."""
- temp = sorted(S) # create a sorted copy of S
- for j in range(1, len(temp)):
- if S[j-1] == S[j]:
- return False # found duplicate pair
- return True # if we reach this, elements were unique
+ """Return True if there are no duplicate elements in sequence S."""
+ temp = sorted(S) # create a sorted copy of S
+ for j in range(1, len(temp)):
+ if S[j - 1] == S[j]:
+ return False # found duplicate pair
+ return True # if we reach this, elements were unique
diff --git a/ch04/binary_search.py b/ch04/binary_search.py
index ed1267d..4902025 100644
--- a/ch04/binary_search.py
+++ b/ch04/binary_search.py
@@ -1,38 +1,16 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
def binary_search(data, target, low, high):
- """Return True if target is found in indicated portion of a Python list.
-
- The search only considers the portion from data[low] to data[high] inclusive.
- """
- if low > high:
- return False # interval is empty; no match
- else:
- mid = (low + high) // 2
- if target == data[mid]: # found a match
- return True
- elif target < data[mid]:
- # recur on the portion left of the middle
- return binary_search(data, target, low, mid - 1)
+ """Return True if target is found in indicated portion of a Python list.
+ The search only considers the portion from data[low] to data[high] inclusive.
+ """
+ if low > high:
+ return False # interval is empty; no match
else:
- # recur on the portion right of the middle
- return binary_search(data, target, mid + 1, high)
+ mid = (low + high) // 2
+ if target == data[mid]: # found a match
+ return True
+ elif target < data[mid]:
+ # recur on the portion left of the middle
+ return binary_search(data, target, low, mid - 1)
+ else:
+ # recur on the portion right of the middle
+ return binary_search(data, target, mid + 1, high)
diff --git a/ch04/binary_search_iterative.py b/ch04/binary_search_iterative.py
index d20d0f8..6170117 100644
--- a/ch04/binary_search_iterative.py
+++ b/ch04/binary_search_iterative.py
@@ -1,34 +1,13 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
def binary_search_iterative(data, target):
- """Return True if target is found in the given Python list."""
- low = 0
- high = len(data)-1
- while low <= high:
- mid = (low + high) // 2
- if target == data[mid]: # found a match
- return True
- elif target < data[mid]:
- high = mid - 1 # only consider values left of mid
- else:
- low = mid + 1 # only consider values right of mid
- return False # loop ended without success
+ """Return True if target is found in the given Python list."""
+ low = 0
+ high = len(data) - 1
+ while low <= high:
+ mid = (low + high) // 2
+ if target == data[mid]: # found a match
+ return True
+ elif target < data[mid]:
+ high = mid - 1 # only consider values left of mid
+ else:
+ low = mid + 1 # only consider values right of mid
+ return False # loop ended without success
diff --git a/ch04/binary_sum.py b/ch04/binary_sum.py
index d80e629..e7a887b 100644
--- a/ch04/binary_sum.py
+++ b/ch04/binary_sum.py
@@ -1,30 +1,9 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
def binary_sum(S, start, stop):
- """Return the sum of the numbers in implicit slice S[start:stop]."""
- if start >= stop: # zero elements in slice
- return 0
- elif start == stop-1: # one element in slice
- return S[start]
- else: # two or more elements in slice
- mid = (start + stop) // 2
- return binary_sum(S, start, mid) + binary_sum(S, mid, stop)
+ """Return the sum of the numbers in implicit slice S[start:stop]."""
+ if start >= stop: # zero elements in slice
+ return 0
+ elif start == stop - 1: # one element in slice
+ return S[start]
+ else: # two or more elements in slice
+ mid = (start + stop) // 2
+ return binary_sum(S, start, mid) + binary_sum(S, mid, stop)
diff --git a/ch04/disk_usage.py b/ch04/disk_usage.py
index dbba9a9..be64fb7 100644
--- a/ch04/disk_usage.py
+++ b/ch04/disk_usage.py
@@ -1,33 +1,12 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
import os
-def disk_usage(path):
- """Return the number of bytes used by a file/folder and any descendents."""
- total = os.path.getsize(path) # account for direct usage
- if os.path.isdir(path): # if this is a directory,
- for filename in os.listdir(path): # then for each child:
- childpath = os.path.join(path, filename) # compose full path to child
- total += disk_usage(childpath) # add child's usage to total
- print ('{0:<7}'.format(total), path) # descriptive output (optional)
- return total # return the grand total
+def disk_usage(path):
+ """Return the number of bytes used by a file/folder and any descendents."""
+ total = os.path.getsize(path) # account for direct usage
+ if os.path.isdir(path): # if this is a directory,
+ for filename in os.listdir(path): # then for each child:
+ childpath = os.path.join(path, filename) # compose full path to child
+ total += disk_usage(childpath) # add child's usage to total
+ print('{0:<7}'.format(total), path) # descriptive output (optional)
+ return total # return the grand total
diff --git a/ch04/factorial.py b/ch04/factorial.py
index 6fef23a..efdc491 100644
--- a/ch04/factorial.py
+++ b/ch04/factorial.py
@@ -1,26 +1,5 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
def factorial(n):
- if n == 0:
- return 1
- else:
- return n * factorial(n-1)
+ if n == 0:
+ return 1
+ else:
+ return n * factorial(n - 1)
diff --git a/ch04/fibonacci.py b/ch04/fibonacci.py
index bb82127..1f06972 100644
--- a/ch04/fibonacci.py
+++ b/ch04/fibonacci.py
@@ -1,35 +1,15 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
def bad_fibonacci(n):
- """Return the nth Fibonacci number."""
- if n <= 1:
- return n
- else:
- return bad_fibonacci(n-2) + bad_fibonacci(n-1)
+ """Return the nth Fibonacci number."""
+ if n <= 1:
+ return n
+ else:
+ return bad_fibonacci(n - 2) + bad_fibonacci(n - 1)
+
def good_fibonacci(n):
- """Return pair of Fibonacci numbers, F(n) and F(n-1)."""
- if n <= 1:
- return (n,0)
- else:
- (a, b) = good_fibonacci(n-1)
- return (a+b, a)
+ """Return pair of Fibonacci numbers, F(n) and F(n-1)."""
+ if n <= 1:
+ return (n, 0)
+ else:
+ (a, b) = good_fibonacci(n - 1)
+ return (a + b, a)
diff --git a/ch04/linear_sum.py b/ch04/linear_sum.py
index 22bd7c1..5571f3e 100644
--- a/ch04/linear_sum.py
+++ b/ch04/linear_sum.py
@@ -1,27 +1,6 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
def linear_sum(S, n):
- """Return the sum of the first n numbers of sequence S."""
- if n == 0:
- return 0
- else:
- return linear_sum(S, n-1) + S[n-1]
+ """Return the sum of the first n numbers of sequence S."""
+ if n == 0:
+ return 0
+ else:
+ return linear_sum(S, n - 1) + S[n - 1]
diff --git a/ch04/power_fast.py b/ch04/power_fast.py
index e277bc7..4e4b529 100644
--- a/ch04/power_fast.py
+++ b/ch04/power_fast.py
@@ -1,31 +1,10 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
def power(x, n):
- """Compute the value x**n for integer n."""
- if n == 0:
- return 1
- else:
- partial = power(x, n // 2) # rely on truncated division
- result = partial * partial
- if n % 2 == 1: # if n odd, include extra factor of x
- result *= x
- return result
+ """Compute the value x**n for integer n."""
+ if n == 0:
+ return 1
+ else:
+ partial = power(x, n // 2) # rely on truncated division
+ result = partial * partial
+ if n % 2 == 1: # if n odd, include extra factor of x
+ result *= x
+ return result
diff --git a/ch04/power_slow.py b/ch04/power_slow.py
index 1bd8a83..a6d403a 100644
--- a/ch04/power_slow.py
+++ b/ch04/power_slow.py
@@ -1,27 +1,6 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
def power(x, n):
- """Compute the value x**n for integer n."""
- if n == 0:
- return 1
- else:
- return x * power(x, n-1)
+ """Compute the value x**n for integer n."""
+ if n == 0:
+ return 1
+ else:
+ return x * power(x, n - 1)
diff --git a/ch04/reverse.py b/ch04/reverse.py
index 8a4477b..91513bd 100644
--- a/ch04/reverse.py
+++ b/ch04/reverse.py
@@ -1,26 +1,5 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
def reverse(S, start, stop):
- """Reverse elements in implicit slice S[start:stop]."""
- if start < stop - 1: # if at least 2 elements:
- S[start], S[stop-1] = S[stop-1], S[start] # swap first and last
- reverse(S, start+1, stop-1) # recur on rest
+ """Reverse elements in implicit slice S[start:stop]."""
+ if start < stop - 1: # if at least 2 elements:
+ S[start], S[stop - 1] = S[stop - 1], S[start] # swap first and last
+ reverse(S, start + 1, stop - 1) # recur on rest
diff --git a/ch04/reverse_iterative.py b/ch04/reverse_iterative.py
index b953b1c..18f3d73 100644
--- a/ch04/reverse_iterative.py
+++ b/ch04/reverse_iterative.py
@@ -1,27 +1,6 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
def reverse_iterative(S):
- """Reverse elements in sequence S."""
- start, stop = 0, len(S)
- while start < stop - 1:
- S[start], S[stop-1] = S[stop-1], S[start] # swap first and last
- start, stop = start + 1, stop - 1 # narrow the range
+ """Reverse elements in sequence S."""
+ start, stop = 0, len(S)
+ while start < stop - 1:
+ S[start], S[stop - 1] = S[stop - 1], S[start] # swap first and last
+ start, stop = start + 1, stop - 1 # narrow the range
diff --git a/ch04/ruler.py b/ch04/ruler.py
index be540fe..0bd13e5 100644
--- a/ch04/ruler.py
+++ b/ch04/ruler.py
@@ -1,49 +1,30 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
def draw_line(tick_length, tick_label=''):
- """Draw one line with given tick length (followed by optional label)."""
- line = '-' * tick_length
- if tick_label:
- line += ' ' + tick_label
- print(line)
+ """Draw one line with given tick length (followed by optional label)."""
+ line = '-' * tick_length
+ if tick_label:
+ line += ' ' + tick_label
+ print(line)
+
def draw_interval(center_length):
- """Draw tick interval based upon a central tick length."""
- if center_length > 0: # stop when length drops to 0
- draw_interval(center_length - 1) # recursively draw top ticks
- draw_line(center_length) # draw center tick
- draw_interval(center_length - 1) # recursively draw bottom ticks
+ """Draw tick interval based upon a central tick length."""
+ if center_length > 0: # stop when length drops to 0
+ draw_interval(center_length - 1) # recursively draw top ticks
+ draw_line(center_length) # draw center tick
+ draw_interval(center_length - 1) # recursively draw bottom ticks
+
def draw_ruler(num_inches, major_length):
- """Draw English ruler with given number of inches and major tick length."""
- draw_line(major_length, '0') # draw inch 0 line
- for j in range(1, 1 + num_inches):
- draw_interval(major_length - 1) # draw interior ticks for inch
- draw_line(major_length, str(j)) # draw inch j line and label
+ """Draw English ruler with given number of inches and major tick length."""
+ draw_line(major_length, '0') # draw inch 0 line
+ for j in range(1, 1 + num_inches):
+ draw_interval(major_length - 1) # draw interior ticks for inch
+ draw_line(major_length, str(j)) # draw inch j line and label
if __name__ == '__main__':
- draw_ruler(2,4)
- print('='*30)
- draw_ruler(1,5)
- print('='*30)
- draw_ruler(3,3)
+ draw_ruler(2, 4)
+ print('=' * 30)
+ draw_ruler(1, 5)
+ print('=' * 30)
+ draw_ruler(3, 3)
diff --git a/ch04/unique_bad.py b/ch04/unique_bad.py
index 8ce6651..cf4aaa6 100644
--- a/ch04/unique_bad.py
+++ b/ch04/unique_bad.py
@@ -1,28 +1,10 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
-# WARNING: this is a terribly inefficient algorithm
def unique3(S, start, stop):
- """Return True if there are no duplicate elements in slice S[start:stop]."""
- if stop - start <= 1: return True # at most one item
- elif not unique(S, start, stop-1): return False # first part has duplicate
- elif not unique(S, start+1, stop): return False # second part has duplicate
- else: return S[start] != S[stop-1] # do first and last differ?
+ """Return True if there are no duplicate elements in slice S[start:stop]."""
+ if stop - start <= 1:
+ return True # at most one item
+ elif not unique(S, start, stop - 1):
+ return False # first part has duplicate
+ elif not unique(S, start + 1, stop):
+ return False # second part has duplicate
+ else:
+ return S[start] != S[stop - 1] # do first and last differ?
diff --git a/ch05/caesar.py b/ch05/caesar.py
index cb2d091..51bc9bb 100644
--- a/ch05/caesar.py
+++ b/ch05/caesar.py
@@ -1,58 +1,38 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
class CaesarCipher:
- """Class for doing encryption and decryption using a Caesar cipher."""
+ """Class for doing encryption and decryption using a Caesar cipher."""
+
+ def __init__(self, shift):
+ """Construct Caesar cipher using given integer shift for rotation."""
+ encoder = [None] * 26 # temp array for encryption
+ decoder = [None] * 26 # temp array for decryption
+ for k in range(26):
+ encoder[k] = chr((k + shift) % 26 + ord('A'))
+ decoder[k] = chr((k - shift) % 26 + ord('A'))
+ self._forward = ''.join(encoder) # will store as string
+ self._backward = ''.join(decoder) # since fixed
- def __init__(self, shift):
- """Construct Caesar cipher using given integer shift for rotation."""
- encoder = [None] * 26 # temp array for encryption
- decoder = [None] * 26 # temp array for decryption
- for k in range(26):
- encoder[k] = chr((k + shift) % 26 + ord('A'))
- decoder[k] = chr((k - shift) % 26 + ord('A'))
- self._forward = ''.join(encoder) # will store as string
- self._backward = ''.join(decoder) # since fixed
+ def encrypt(self, message):
+ """Return string representing encripted message."""
+ return self._transform(message, self._forward)
- def encrypt(self, message):
- """Return string representing encripted message."""
- return self._transform(message, self._forward)
+ def decrypt(self, secret):
+ """Return decrypted message given encrypted secret."""
+ return self._transform(secret, self._backward)
- def decrypt(self, secret):
- """Return decrypted message given encrypted secret."""
- return self._transform(secret, self._backward)
+ def _transform(self, original, code):
+ """Utility to perform transformation based on given code string."""
+ msg = list(original)
+ for k in range(len(msg)):
+ if msg[k].isupper():
+ j = ord(msg[k]) - ord('A') # index from 0 to 25
+ msg[k] = code[j] # replace this character
+ return ''.join(msg)
- def _transform(self, original, code):
- """Utility to perform transformation based on given code string."""
- msg = list(original)
- for k in range(len(msg)):
- if msg[k].isupper():
- j = ord(msg[k]) - ord('A') # index from 0 to 25
- msg[k] = code[j] # replace this character
- return ''.join(msg)
if __name__ == '__main__':
- cipher = CaesarCipher(3)
- message = "THE EAGLE IS IN PLAY; MEET AT JOE'S."
- coded = cipher.encrypt(message)
- print('Secret: ', coded)
- answer = cipher.decrypt(coded)
- print('Message:', answer)
+ cipher = CaesarCipher(3)
+ message = "THE EAGLE IS IN PLAY; MEET AT JOE'S."
+ coded = cipher.encrypt(message)
+ print('Secret: ', coded)
+ answer = cipher.decrypt(coded)
+ print('Message:', answer)
diff --git a/ch05/dynamic_array.py b/ch05/dynamic_array.py
index 6377cf1..d11d0b0 100644
--- a/ch05/dynamic_array.py
+++ b/ch05/dynamic_array.py
@@ -1,82 +1,62 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
+import ctypes # provides low-level arrays
-import ctypes # provides low-level arrays
class DynamicArray:
- """A dynamic array class akin to a simplified Python list."""
+ """A dynamic array class akin to a simplified Python list."""
- def __init__(self):
- """Create an empty array."""
- self._n = 0 # count actual elements
- self._capacity = 1 # default array capacity
- self._A = self._make_array(self._capacity) # low-level array
-
- def __len__(self):
- """Return number of elements stored in the array."""
- return self._n
-
- def __getitem__(self, k):
- """Return element at index k."""
- if not 0 <= k < self._n:
- raise IndexError('invalid index')
- return self._A[k] # retrieve from array
-
- def append(self, obj):
- """Add object to end of the array."""
- if self._n == self._capacity: # not enough room
- self._resize(2 * self._capacity) # so double capacity
- self._A[self._n] = obj
- self._n += 1
+ def __init__(self):
+ """Create an empty array."""
+ self._n = 0 # count actual elements
+ self._capacity = 1 # default array capacity
+ self._A = self._make_array(self._capacity) # low-level array
- def _resize(self, c): # nonpublic utitity
- """Resize internal array to capacity c."""
- B = self._make_array(c) # new (bigger) array
- for k in range(self._n): # for each existing value
- B[k] = self._A[k]
- self._A = B # use the bigger array
- self._capacity = c
+ def __len__(self):
+ """Return number of elements stored in the array."""
+ return self._n
- def _make_array(self, c): # nonpublic utitity
- """Return new array with capacity c."""
- return (c * ctypes.py_object)() # see ctypes documentation
+ def __getitem__(self, k):
+ """Return element at index k."""
+ if not 0 <= k < self._n:
+ raise IndexError('invalid index')
+ return self._A[k] # retrieve from array
- def insert(self, k, value):
- """Insert value at index k, shifting subsequent values rightward."""
- # (for simplicity, we assume 0 <= k <= n in this verion)
- if self._n == self._capacity: # not enough room
- self._resize(2 * self._capacity) # so double capacity
- for j in range(self._n, k, -1): # shift rightmost first
- self._A[j] = self._A[j-1]
- self._A[k] = value # store newest element
- self._n += 1
+ def append(self, obj):
+ """Add object to end of the array."""
+ if self._n == self._capacity: # not enough room
+ self._resize(2 * self._capacity) # so double capacity
+ self._A[self._n] = obj
+ self._n += 1
- def remove(self, value):
- """Remove first occurrence of value (or raise ValueError)."""
- # note: we do not consider shrinking the dynamic array in this version
- for k in range(self._n):
- if self._A[k] == value: # found a match!
- for j in range(k, self._n - 1): # shift others to fill gap
- self._A[j] = self._A[j+1]
- self._A[self._n - 1] = None # help garbage collection
- self._n -= 1 # we have one less item
- return # exit immediately
- raise ValueError('value not found') # only reached if no match
+ def _resize(self, c): # nonpublic utitity
+ """Resize internal array to capacity c."""
+ B = self._make_array(c) # new (bigger) array
+ for k in range(self._n): # for each existing value
+ B[k] = self._A[k]
+ self._A = B # use the bigger array
+ self._capacity = c
+
+ def _make_array(self, c): # nonpublic utitity
+ """Return new array with capacity c."""
+ return (c * ctypes.py_object)() # see ctypes documentation
+
+ def insert(self, k, value):
+ """Insert value at index k, shifting subsequent values rightward."""
+ # (for simplicity, we assume 0 <= k <= n in this verion)
+ if self._n == self._capacity: # not enough room
+ self._resize(2 * self._capacity) # so double capacity
+ for j in range(self._n, k, -1): # shift rightmost first
+ self._A[j] = self._A[j - 1]
+ self._A[k] = value # store newest element
+ self._n += 1
+
+ def remove(self, value):
+ """Remove first occurrence of value (or raise ValueError)."""
+ # note: we do not consider shrinking the dynamic array in this version
+ for k in range(self._n):
+ if self._A[k] == value: # found a match!
+ for j in range(k, self._n - 1): # shift others to fill gap
+ self._A[j] = self._A[j + 1]
+ self._A[self._n - 1] = None # help garbage collection
+ self._n -= 1 # we have one less item
+ return # exit immediately
+ raise ValueError('value not found') # only reached if no match
diff --git a/ch05/experiment_list_append.py b/ch05/experiment_list_append.py
index dc9dddc..be4b213 100644
--- a/ch05/experiment_list_append.py
+++ b/ch05/experiment_list_append.py
@@ -1,24 +1,3 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
import sys
from time import time
@@ -26,18 +5,20 @@
maxN = int(sys.argv[1])
except:
maxN = 10000000
+from time import time # import time function from time module
+
-from time import time # import time function from time module
def compute_average(n):
- """Perform n appends to an empty list and return average time elapsed."""
- data = []
- start = time() # record the start time (in seconds)
- for k in range(n):
- data.append(None)
- end = time() # record the end time (in seconds)
- return (end - start) / n # compute average per operation
+ """Perform n appends to an empty list and return average time elapsed."""
+ data = []
+ start = time() # record the start time (in seconds)
+ for k in range(n):
+ data.append(None)
+ end = time() # record the end time (in seconds)
+ return (end - start) / n # compute average per operation
+
n = 10
while n <= maxN:
- print('Average of {0:.3f} for n {1}'.format(compute_average(n)*1000000, n))
- n *= 10
+ print('Average of {0:.3f} for n {1}'.format(compute_average(n) * 1000000, n))
+ n *= 10
diff --git a/ch05/experiment_list_size.py b/ch05/experiment_list_size.py
index d788992..3c0b274 100644
--- a/ch05/experiment_list_size.py
+++ b/ch05/experiment_list_size.py
@@ -1,35 +1,14 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
import sys
try:
n = int(sys.argv[1])
except:
n = 100
+import sys # provides getsizeof function
-import sys # provides getsizeof function
data = []
-for k in range(n): # NOTE: must fix choice of n
- a = len(data) # number of elements
- b = sys.getsizeof(data) # actual size in bytes
- print('Length: {0:3d}; Size in bytes: {1:4d}'.format(a, b))
- data.append(None) # increase length by one
+for k in range(n): # NOTE: must fix choice of n
+ a = len(data) # number of elements
+ b = sys.getsizeof(data) # actual size in bytes
+ print('Length: {0:3d}; Size in bytes: {1:4d}'.format(a, b))
+ data.append(None) # increase length by one
diff --git a/ch05/high_scores.py b/ch05/high_scores.py
index 1dad535..45bf48f 100644
--- a/ch05/high_scores.py
+++ b/ch05/high_scores.py
@@ -1,90 +1,67 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
class GameEntry:
- """Represents one entry of a list of high scores."""
-
- def __init__(self, name, score):
- """Create an entry with given name and score."""
- self._name = name
- self._score = score
+ """Represents one entry of a list of high scores."""
- def get_name(self):
- """Return the name of the person for this entry."""
- return self._name
-
- def get_score(self):
- """Return the score of this entry."""
- return self._score
+ def __init__(self, name, score):
+ """Create an entry with given name and score."""
+ self._name = name
+ self._score = score
- def __str__(self):
- """Return string representation of the entry."""
- return '({0}, {1})'.format(self._name, self._score) # e.g., '(Bob, 98)'
+ def get_name(self):
+ """Return the name of the person for this entry."""
+ return self._name
-class Scoreboard:
- """Fixed-length sequence of high scores in nondecreasing order."""
+ def get_score(self):
+ """Return the score of this entry."""
+ return self._score
- def __init__(self, capacity=10):
- """Initialize scoreboard with given maximum capacity.
+ def __str__(self):
+ """Return string representation of the entry."""
+ return '({0}, {1})'.format(self._name, self._score) # e.g., '(Bob, 98)'
- All entries are initially None.
- """
- self._board = [None] * capacity # reserve space for future scores
- self._n = 0 # number of actual entries
- def __getitem__(self, k):
- """Return entry at index k."""
- return self._board[k]
+class Scoreboard:
+ """Fixed-length sequence of high scores in nondecreasing order."""
- def __str__(self):
- """Return string representation of the high score list."""
- return '\n'.join(str(self._board[j]) for j in range(self._n))
+ def __init__(self, capacity=10):
+ """Initialize scoreboard with given maximum capacity.
+ All entries are initially None.
+ """
+ self._board = [None] * capacity # reserve space for future scores
+ self._n = 0 # number of actual entries
- def add(self, entry):
- """Consider adding entry to high scores."""
- score = entry.get_score()
+ def __getitem__(self, k):
+ """Return entry at index k."""
+ return self._board[k]
- # Does new entry qualify as a high score?
- # answer is yes if board not full or score is higher than last entry
- good = self._n < len(self._board) or score > self._board[-1].get_score()
+ def __str__(self):
+ """Return string representation of the high score list."""
+ return '\n'.join(str(self._board[j]) for j in range(self._n))
- if good:
- if self._n < len(self._board): # no score drops from list
- self._n += 1 # so overall number increases
+ def add(self, entry):
+ """Consider adding entry to high scores."""
+ score = entry.get_score()
+ # Does new entry qualify as a high score?
+ # answer is yes if board not full or score is higher than last entry
+ good = self._n < len(self._board) or score > self._board[-1].get_score()
+ if good:
+ if self._n < len(self._board): # no score drops from list
+ self._n += 1 # so overall number increases
+ # shift lower scores rightward to make room for new entry
+ j = self._n - 1
+ while j > 0 and self._board[j - 1].get_score() < score:
+ self._board[j] = self._board[j - 1] # shift entry from j-1 to j
+ j -= 1 # and decrement j
+ self._board[j] = entry # when done, add new entry
- # shift lower scores rightward to make room for new entry
- j = self._n - 1
- while j > 0 and self._board[j-1].get_score() < score:
- self._board[j] = self._board[j-1] # shift entry from j-1 to j
- j -= 1 # and decrement j
- self._board[j] = entry # when done, add new entry
if __name__ == '__main__':
- board = Scoreboard(5)
- for e in (
- ('Rob', 750), ('Mike',1105), ('Rose', 590), ('Jill', 740),
- ('Jack', 510), ('Anna', 660), ('Paul', 720), ('Bob', 400),
+ board = Scoreboard(5)
+ for e in (
+ ('Rob', 750), ('Mike', 1105), ('Rose', 590), ('Jill', 740),
+ ('Jack', 510), ('Anna', 660), ('Paul', 720), ('Bob', 400),
):
- ge = GameEntry(e[0], e[1])
- board.add(ge)
- print('After considering {0}, scoreboard is:'.format(ge))
- print(board)
- print()
+ ge = GameEntry(e[0], e[1])
+ board.add(ge)
+ print('After considering {0}, scoreboard is:'.format(ge))
+ print(board)
+ print()
diff --git a/ch05/insertion_sort.py b/ch05/insertion_sort.py
index bd615da..a3819ed 100644
--- a/ch05/insertion_sort.py
+++ b/ch05/insertion_sort.py
@@ -1,30 +1,9 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
def insertion_sort(A):
- """Sort list of comparable elements into nondecreasing order."""
- for k in range(1, len(A)): # from 1 to n-1
- cur = A[k] # current element to be inserted
- j = k # find correct index j for current
- while j > 0 and A[j-1] > cur: # element A[j-1] must be after current
- A[j] = A[j-1]
- j -= 1
- A[j] = cur # cur is now in the right place
+ """Sort list of comparable elements into nondecreasing order."""
+ for k in range(1, len(A)): # from 1 to n-1
+ cur = A[k] # current element to be inserted
+ j = k # find correct index j for current
+ while j > 0 and A[j - 1] > cur: # element A[j-1] must be after current
+ A[j] = A[j - 1]
+ j -= 1
+ A[j] = cur # cur is now in the right place
diff --git a/ch05/tic_tac_toe.py b/ch05/tic_tac_toe.py
index cfde6c8..0f8291f 100644
--- a/ch05/tic_tac_toe.py
+++ b/ch05/tic_tac_toe.py
@@ -1,83 +1,65 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
class TicTacToe:
- """Management of a Tic-Tac-Toe game (does not do strategy)."""
+ """Management of a Tic-Tac-Toe game (does not do strategy)."""
- def __init__(self):
- """Start a new game."""
- self._board = [ [' '] * 3 for j in range(3) ]
- self._player = 'X'
+ def __init__(self):
+ """Start a new game."""
+ self._board = [[' '] * 3 for j in range(3)]
+ self._player = 'X'
- def mark(self, i, j):
- """Put an X or O mark at position (i,j) for next player's turn."""
- if not (0 <= i <= 2 and 0 <= j <= 2):
- raise ValueError('Invalid board position')
- if self._board[i][j] != ' ':
- raise ValueError('Board position occupied')
- if self.winner() is not None:
- raise ValueError('Game is already complete')
- self._board[i][j] = self._player
- if self._player == 'X':
- self._player = 'O'
- else:
- self._player = 'X'
+ def mark(self, i, j):
+ """Put an X or O mark at position (i,j) for next player's turn."""
+ if not (0 <= i <= 2 and 0 <= j <= 2):
+ raise ValueError('Invalid board position')
+ if self._board[i][j] != ' ':
+ raise ValueError('Board position occupied')
+ if self.winner() is not None:
+ raise ValueError('Game is already complete')
+ self._board[i][j] = self._player
+ if self._player == 'X':
+ self._player = 'O'
+ else:
+ self._player = 'X'
- def _is_win(self, mark):
- """Check whether the board configuration is a win for the given player."""
- board = self._board # local variable for shorthand
- return (mark == board[0][0] == board[0][1] == board[0][2] or # row 0
- mark == board[1][0] == board[1][1] == board[1][2] or # row 1
- mark == board[2][0] == board[2][1] == board[2][2] or # row 2
- mark == board[0][0] == board[1][0] == board[2][0] or # column 0
- mark == board[0][1] == board[1][1] == board[2][1] or # column 1
- mark == board[0][2] == board[1][2] == board[2][2] or # column 2
- mark == board[0][0] == board[1][1] == board[2][2] or # diagonal
- mark == board[0][2] == board[1][1] == board[2][0]) # rev diag
+ def _is_win(self, mark):
+ """Check whether the board configuration is a win for the given player."""
+ board = self._board # local variable for shorthand
+ return (mark == board[0][0] == board[0][1] == board[0][2] or # row 0
+ mark == board[1][0] == board[1][1] == board[1][2] or # row 1
+ mark == board[2][0] == board[2][1] == board[2][2] or # row 2
+ mark == board[0][0] == board[1][0] == board[2][0] or # column 0
+ mark == board[0][1] == board[1][1] == board[2][1] or # column 1
+ mark == board[0][2] == board[1][2] == board[2][2] or # column 2
+ mark == board[0][0] == board[1][1] == board[2][2] or # diagonal
+ mark == board[0][2] == board[1][1] == board[2][0]) # rev diag
- def winner(self):
- """Return mark of winning player, or None to indicate a tie."""
- for mark in 'XO':
- if self._is_win(mark):
- return mark
- return None
+ def winner(self):
+ """Return mark of winning player, or None to indicate a tie."""
+ for mark in 'XO':
+ if self._is_win(mark):
+ return mark
+ return None
- def __str__(self):
- """Return string representation of current game board."""
- rows = ['|'.join(self._board[r]) for r in range(3)]
- return '\n-----\n'.join(rows)
+ def __str__(self):
+ """Return string representation of current game board."""
+ rows = ['|'.join(self._board[r]) for r in range(3)]
+ return '\n-----\n'.join(rows)
if __name__ == '__main__':
- game = TicTacToe()
- # X moves: # O moves:
- game.mark(1, 1); game.mark(0, 2)
- game.mark(2, 2); game.mark(0, 0)
- game.mark(0, 1); game.mark(2, 1)
- game.mark(1, 2); game.mark(1, 0)
- game.mark(2, 0)
-
- print(game)
- winner = game.winner()
- if winner is None:
- print('Tie')
- else:
- print(winner, 'wins')
+ game = TicTacToe()
+ # X moves: # O moves:
+ game.mark(1, 1);
+ game.mark(0, 2)
+ game.mark(2, 2);
+ game.mark(0, 0)
+ game.mark(0, 1);
+ game.mark(2, 1)
+ game.mark(1, 2);
+ game.mark(1, 0)
+ game.mark(2, 0)
+ print(game)
+ winner = game.winner()
+ if winner is None:
+ print('Tie')
+ else:
+ print(winner, 'wins')
diff --git a/ch06/array_queue.py b/ch06/array_queue.py
index 3579def..1154816 100644
--- a/ch06/array_queue.py
+++ b/ch06/array_queue.py
@@ -1,80 +1,58 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
from ..exceptions import Empty
-class ArrayQueue:
- """FIFO queue implementation using a Python list as underlying storage."""
- DEFAULT_CAPACITY = 10 # moderate capacity for all new queues
-
- def __init__(self):
- """Create an empty queue."""
- self._data = [None] * ArrayQueue.DEFAULT_CAPACITY
- self._size = 0
- self._front = 0
-
- def __len__(self):
- """Return the number of elements in the queue."""
- return self._size
-
- def is_empty(self):
- """Return True if the queue is empty."""
- return self._size == 0
-
- def first(self):
- """Return (but do not remove) the element at the front of the queue.
- Raise Empty exception if the queue is empty.
- """
- if self.is_empty():
- raise Empty('Queue is empty')
- return self._data[self._front]
-
- def dequeue(self):
- """Remove and return the first element of the queue (i.e., FIFO).
-
- Raise Empty exception if the queue is empty.
- """
- if self.is_empty():
- raise Empty('Queue is empty')
- answer = self._data[self._front]
- self._data[self._front] = None # help garbage collection
- self._front = (self._front + 1) % len(self._data)
- self._size -= 1
- return answer
-
- def enqueue(self, e):
- """Add an element to the back of queue."""
- if self._size == len(self._data):
- self._resize(2 * len(self.data)) # double the array size
- avail = (self._front + self._size) % len(self._data)
- self._data[avail] = e
- self._size += 1
-
- def _resize(self, cap): # we assume cap >= len(self)
- """Resize to a new list of capacity >= len(self)."""
- old = self._data # keep track of existing list
- self._data = [None] * cap # allocate list with new capacity
- walk = self._front
- for k in range(self._size): # only consider existing elements
- self._data[k] = old[walk] # intentionally shift indices
- walk = (1 + walk) % len(old) # use old size as modulus
- self._front = 0 # front has been realigned
+class ArrayQueue:
+ """FIFO queue implementation using a Python list as underlying storage."""
+ DEFAULT_CAPACITY = 10 # moderate capacity for all new queues
+
+ def __init__(self):
+ """Create an empty queue."""
+ self._data = [None] * ArrayQueue.DEFAULT_CAPACITY
+ self._size = 0
+ self._front = 0
+
+ def __len__(self):
+ """Return the number of elements in the queue."""
+ return self._size
+
+ def is_empty(self):
+ """Return True if the queue is empty."""
+ return self._size == 0
+
+ def first(self):
+ """Return (but do not remove) the element at the front of the queue.
+ Raise Empty exception if the queue is empty.
+ """
+ if self.is_empty():
+ raise Empty('Queue is empty')
+ return self._data[self._front]
+
+ def dequeue(self):
+ """Remove and return the first element of the queue (i.e., FIFO).
+ Raise Empty exception if the queue is empty.
+ """
+ if self.is_empty():
+ raise Empty('Queue is empty')
+ answer = self._data[self._front]
+ self._data[self._front] = None # help garbage collection
+ self._front = (self._front + 1) % len(self._data)
+ self._size -= 1
+ return answer
+
+ def enqueue(self, e):
+ """Add an element to the back of queue."""
+ if self._size == len(self._data):
+ self._resize(2 * len(self.data)) # double the array size
+ avail = (self._front + self._size) % len(self._data)
+ self._data[avail] = e
+ self._size += 1
+
+ def _resize(self, cap): # we assume cap >= len(self)
+ """Resize to a new list of capacity >= len(self)."""
+ old = self._data # keep track of existing list
+ self._data = [None] * cap # allocate list with new capacity
+ walk = self._front
+ for k in range(self._size): # only consider existing elements
+ self._data[k] = old[walk] # intentionally shift indices
+ walk = (1 + walk) % len(old) # use old size as modulus
+ self._front = 0 # front has been realigned
diff --git a/ch06/array_stack.py b/ch06/array_stack.py
index c912da2..9b8f5f7 100644
--- a/ch06/array_stack.py
+++ b/ch06/array_stack.py
@@ -1,80 +1,58 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
"""Basic example of an adapter class to provide a stack interface to Python's list."""
-
from ..exceptions import Empty
-class ArrayStack:
- """LIFO Stack implementation using a Python list as underlying storage."""
- def __init__(self):
- """Create an empty stack."""
- self._data = [] # nonpublic list instance
+class ArrayStack:
+ """LIFO Stack implementation using a Python list as underlying storage."""
- def __len__(self):
- """Return the number of elements in the stack."""
- return len(self._data)
+ def __init__(self):
+ """Create an empty stack."""
+ self._data = [] # nonpublic list instance
- def is_empty(self):
- """Return True if the stack is empty."""
- return len(self._data) == 0
+ def __len__(self):
+ """Return the number of elements in the stack."""
+ return len(self._data)
- def push(self, e):
- """Add element e to the top of the stack."""
- self._data.append(e) # new item stored at end of list
+ def is_empty(self):
+ """Return True if the stack is empty."""
+ return len(self._data) == 0
- def top(self):
- """Return (but do not remove) the element at the top of the stack.
+ def push(self, e):
+ """Add element e to the top of the stack."""
+ self._data.append(e) # new item stored at end of list
- Raise Empty exception if the stack is empty.
- """
- if self.is_empty():
- raise Empty('Stack is empty')
- return self._data[-1] # the last item in the list
+ def top(self):
+ """Return (but do not remove) the element at the top of the stack.
+ Raise Empty exception if the stack is empty.
+ """
+ if self.is_empty():
+ raise Empty('Stack is empty')
+ return self._data[-1] # the last item in the list
- def pop(self):
- """Remove and return the element from the top of the stack (i.e., LIFO).
+ def pop(self):
+ """Remove and return the element from the top of the stack (i.e., LIFO).
+ Raise Empty exception if the stack is empty.
+ """
+ if self.is_empty():
+ raise Empty('Stack is empty')
+ return self._data.pop() # remove last item from list
- Raise Empty exception if the stack is empty.
- """
- if self.is_empty():
- raise Empty('Stack is empty')
- return self._data.pop() # remove last item from list
if __name__ == '__main__':
- S = ArrayStack() # contents: [ ]
- S.push(5) # contents: [5]
- S.push(3) # contents: [5, 3]
- print(len(S)) # contents: [5, 3]; outputs 2
- print(S.pop()) # contents: [5]; outputs 3
- print(S.is_empty()) # contents: [5]; outputs False
- print(S.pop()) # contents: [ ]; outputs 5
- print(S.is_empty()) # contents: [ ]; outputs True
- S.push(7) # contents: [7]
- S.push(9) # contents: [7, 9]
- print(S.top()) # contents: [7, 9]; outputs 9
- S.push(4) # contents: [7, 9, 4]
- print(len(S)) # contents: [7, 9, 4]; outputs 3
- print(S.pop()) # contents: [7, 9]; outputs 4
- S.push(6) # contents: [7, 9, 6]
- S.push(8) # contents: [7, 9, 6, 8]
- print(S.pop()) # contents: [7, 9, 6]; outputs 8
+ S = ArrayStack() # contents: [ ]
+ S.push(5) # contents: [5]
+ S.push(3) # contents: [5, 3]
+ print(len(S)) # contents: [5, 3]; outputs 2
+ print(S.pop()) # contents: [5]; outputs 3
+ print(S.is_empty()) # contents: [5]; outputs False
+ print(S.pop()) # contents: [ ]; outputs 5
+ print(S.is_empty()) # contents: [ ]; outputs True
+ S.push(7) # contents: [7]
+ S.push(9) # contents: [7, 9]
+ print(S.top()) # contents: [7, 9]; outputs 9
+ S.push(4) # contents: [7, 9, 4]
+ print(len(S)) # contents: [7, 9, 4]; outputs 3
+ print(S.pop()) # contents: [7, 9]; outputs 4
+ S.push(6) # contents: [7, 9, 6]
+ S.push(8) # contents: [7, 9, 6, 8]
+ print(S.pop()) # contents: [7, 9, 6]; outputs 8
diff --git a/ch06/match_delimiters.py b/ch06/match_delimiters.py
index dd2ddf8..f7fa941 100644
--- a/ch06/match_delimiters.py
+++ b/ch06/match_delimiters.py
@@ -1,35 +1,14 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
def is_matched(expr):
- """Return True if all delimiters are properly match; False otherwise."""
- lefty = '({[' # opening delimiters
- righty = ')}]' # respective closing delims
- S = ArrayStack()
- for c in expr:
- if c in lefty:
- S.push(c) # push left delimiter on stack
- elif c in righty:
- if S.is_empty():
- return False # nothing to match with
- if righty.index(c) != lefty.index(S.pop()):
- return False # mismatched
- return S.is_empty() # were all symbols matched?
+ """Return True if all delimiters are properly match; False otherwise."""
+ lefty = '({[' # opening delimiters
+ righty = ')}]' # respective closing delims
+ S = ArrayStack()
+ for c in expr:
+ if c in lefty:
+ S.push(c) # push left delimiter on stack
+ elif c in righty:
+ if S.is_empty():
+ return False # nothing to match with
+ if righty.index(c) != lefty.index(S.pop()):
+ return False # mismatched
+ return S.is_empty() # were all symbols matched?
diff --git a/ch06/match_html.py b/ch06/match_html.py
index 460ddfe..ab79fc7 100644
--- a/ch06/match_html.py
+++ b/ch06/match_html.py
@@ -1,39 +1,18 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
def is_matched_html(raw):
- """Return True if all HTML tags are properly match; False otherwise."""
- S = ArrayStack()
- j = raw.find('<') # find first '<' character (if any)
- while j != -1:
- k = raw.find('>', j+1) # find next '>' character
- if k == -1:
- return False # invalid tag
- tag = raw[j+1:k] # strip away < >
- if not tag.startswith('/'): # this is opening tag
- S.push(tag)
- else: # this is closing tag
- if S.is_empty():
- return False # nothing to match with
- if tag[1:] != S.pop():
- return False # mismatched delimiter
- j = raw.find('<', k+1) # find next '<' character (if any)
- return S.is_empty() # were all opening tags matched?
+ """Return True if all HTML tags are properly match; False otherwise."""
+ S = ArrayStack()
+ j = raw.find('<') # find first '<' character (if any)
+ while j != -1:
+ k = raw.find('>', j + 1) # find next '>' character
+ if k == -1:
+ return False # invalid tag
+ tag = raw[j + 1:k] # strip away < >
+ if not tag.startswith('/'): # this is opening tag
+ S.push(tag)
+ else: # this is closing tag
+ if S.is_empty():
+ return False # nothing to match with
+ if tag[1:] != S.pop():
+ return False # mismatched delimiter
+ j = raw.find('<', k + 1) # find next '<' character (if any)
+ return S.is_empty() # were all opening tags matched?
diff --git a/ch06/reverse_file.py b/ch06/reverse_file.py
index b165161..8d31202 100644
--- a/ch06/reverse_file.py
+++ b/ch06/reverse_file.py
@@ -1,34 +1,12 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
def reverse_file(filename):
- """Overwrite given file with its contents line-by-line reversed."""
- S = ArrayStack()
- original = open(filename)
- for line in original:
- S.push(line.rstrip('\n')) # we will re-insert newlines when writing
- original.close()
-
- # now we overwrite with contents in LIFO order
- output = open(filename, 'w') # reopening file overwrites original
- while not S.is_empty():
- output.write(S.pop() + '\n') # re-insert newline characters
- output.close()
+ """Overwrite given file with its contents line-by-line reversed."""
+ S = ArrayStack()
+ original = open(filename)
+ for line in original:
+ S.push(line.rstrip('\n')) # we will re-insert newlines when writing
+ original.close()
+ # now we overwrite with contents in LIFO order
+ output = open(filename, 'w') # reopening file overwrites original
+ while not S.is_empty():
+ output.write(S.pop() + '\n') # re-insert newline characters
+ output.close()
diff --git a/ch07/circular_queue.py b/ch07/circular_queue.py
index 18d9d57..74207f1 100644
--- a/ch07/circular_queue.py
+++ b/ch07/circular_queue.py
@@ -1,92 +1,69 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
from ..exceptions import Empty
-class CircularQueue:
- """Queue implementation using circularly linked list for storage."""
-
- #---------------------------------------------------------------------------------
- # nested _Node class
- class _Node:
- """Lightweight, nonpublic class for storing a singly linked node."""
- __slots__ = '_element', '_next' # streamline memory usage
- def __init__(self, element, next):
- self._element = element
- self._next = next
-
- # end of _Node class
- #---------------------------------------------------------------------------------
+class CircularQueue:
+ """Queue implementation using circularly linked list for storage."""
- def __init__(self):
- """Create an empty queue."""
- self._tail = None # will represent tail of queue
- self._size = 0 # number of queue elements
+ # ---------------------------------------------------------------------------------
+ # nested _Node class
+ class _Node:
+ """Lightweight, nonpublic class for storing a singly linked node."""
+ __slots__ = '_element', '_next' # streamline memory usage
- def __len__(self):
- """Return the number of elements in the queue."""
- return self._size
+ def __init__(self, element, next):
+ self._element = element
+ self._next = next
- def is_empty(self):
- """Return True if the queue is empty."""
- return self._size == 0
+ # end of _Node class
+ # ---------------------------------------------------------------------------------
+ def __init__(self):
+ """Create an empty queue."""
+ self._tail = None # will represent tail of queue
+ self._size = 0 # number of queue elements
- def first(self):
- """Return (but do not remove) the element at the front of the queue.
+ def __len__(self):
+ """Return the number of elements in the queue."""
+ return self._size
- Raise Empty exception if the queue is empty.
- """
- if self.is_empty():
- raise Empty('Queue is empty')
- head = self._tail._next
- return head._element
+ def is_empty(self):
+ """Return True if the queue is empty."""
+ return self._size == 0
- def dequeue(self):
- """Remove and return the first element of the queue (i.e., FIFO).
+ def first(self):
+ """Return (but do not remove) the element at the front of the queue.
+ Raise Empty exception if the queue is empty.
+ """
+ if self.is_empty():
+ raise Empty('Queue is empty')
+ head = self._tail._next
+ return head._element
- Raise Empty exception if the queue is empty.
- """
- if self.is_empty():
- raise Empty('Queue is empty')
- oldhead = self._tail._next
- if self._size == 1: # removing only element
- self._tail = None # queue becomes empty
- else:
- self._tail._next = oldhead._next # bypass the old head
- self._size -= 1
- return oldhead._element
+ def dequeue(self):
+ """Remove and return the first element of the queue (i.e., FIFO).
+ Raise Empty exception if the queue is empty.
+ """
+ if self.is_empty():
+ raise Empty('Queue is empty')
+ oldhead = self._tail._next
+ if self._size == 1: # removing only element
+ self._tail = None # queue becomes empty
+ else:
+ self._tail._next = oldhead._next # bypass the old head
+ self._size -= 1
+ return oldhead._element
- def enqueue(self, e):
- """Add an element to the back of queue."""
- newest = self._Node(e, None) # node will be new tail node
- if self.is_empty():
- newest._next = newest # initialize circularly
- else:
- newest._next = self._tail._next # new node points to head
- self._tail._next = newest # old tail points to new node
- self._tail = newest # new node becomes the tail
- self._size += 1
+ def enqueue(self, e):
+ """Add an element to the back of queue."""
+ newest = self._Node(e, None) # node will be new tail node
+ if self.is_empty():
+ newest._next = newest # initialize circularly
+ else:
+ newest._next = self._tail._next # new node points to head
+ self._tail._next = newest # old tail points to new node
+ self._tail = newest # new node becomes the tail
+ self._size += 1
- def rotate(self):
- """Rotate front element to the back of the queue."""
- if self._size > 0:
- self._tail = self._tail._next # old head becomes new tail
+ def rotate(self):
+ """Rotate front element to the back of the queue."""
+ if self._size > 0:
+ self._tail = self._tail._next # old head becomes new tail
diff --git a/ch07/doubly_linked_base.py b/ch07/doubly_linked_base.py
index f1fd5d9..1f908f1 100644
--- a/ch07/doubly_linked_base.py
+++ b/ch07/doubly_linked_base.py
@@ -1,75 +1,51 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
class _DoublyLinkedBase:
- """A base class providing a doubly linked list representation."""
-
- #-------------------------- nested _Node class --------------------------
- # nested _Node class
- class _Node:
- """Lightweight, nonpublic class for storing a doubly linked node."""
- __slots__ = '_element', '_prev', '_next' # streamline memory
-
- def __init__(self, element, prev, next): # initialize node's fields
- self._element = element # user's element
- self._prev = prev # previous node reference
- self._next = next # next node reference
-
- #-------------------------- list constructor --------------------------
-
- def __init__(self):
- """Create an empty list."""
- self._header = self._Node(None, None, None)
- self._trailer = self._Node(None, None, None)
- self._header._next = self._trailer # trailer is after header
- self._trailer._prev = self._header # header is before trailer
- self._size = 0 # number of elements
-
- #-------------------------- public accessors --------------------------
-
- def __len__(self):
- """Return the number of elements in the list."""
- return self._size
-
- def is_empty(self):
- """Return True if list is empty."""
- return self._size == 0
-
- #-------------------------- nonpublic utilities --------------------------
-
- def _insert_between(self, e, predecessor, successor):
- """Add element e between two existing nodes and return new node."""
- newest = self._Node(e, predecessor, successor) # linked to neighbors
- predecessor._next = newest
- successor._prev = newest
- self._size += 1
- return newest
-
- def _delete_node(self, node):
- """Delete nonsentinel node from the list and return its element."""
- predecessor = node._prev
- successor = node._next
- predecessor._next = successor
- successor._prev = predecessor
- self._size -= 1
- element = node._element # record deleted element
- node._prev = node._next = node._element = None # deprecate node
- return element # return deleted element
+ """A base class providing a doubly linked list representation."""
+
+ # -------------------------- nested _Node class --------------------------
+ # nested _Node class
+ class _Node:
+ """Lightweight, nonpublic class for storing a doubly linked node."""
+ __slots__ = '_element', '_prev', '_next' # streamline memory
+
+ def __init__(self, element, prev, next): # initialize node's fields
+ self._element = element # user's element
+ self._prev = prev # previous node reference
+ self._next = next # next node reference
+
+ # -------------------------- list constructor --------------------------
+ def __init__(self):
+ """Create an empty list."""
+ self._header = self._Node(None, None, None)
+ self._trailer = self._Node(None, None, None)
+ self._header._next = self._trailer # trailer is after header
+ self._trailer._prev = self._header # header is before trailer
+ self._size = 0 # number of elements
+
+ # -------------------------- public accessors --------------------------
+ def __len__(self):
+ """Return the number of elements in the list."""
+ return self._size
+
+ def is_empty(self):
+ """Return True if list is empty."""
+ return self._size == 0
+
+ # -------------------------- nonpublic utilities --------------------------
+ def _insert_between(self, e, predecessor, successor):
+ """Add element e between two existing nodes and return new node."""
+ newest = self._Node(e, predecessor, successor) # linked to neighbors
+ predecessor._next = newest
+ successor._prev = newest
+ self._size += 1
+ return newest
+
+ def _delete_node(self, node):
+ """Delete nonsentinel node from the list and return its element."""
+ predecessor = node._prev
+ successor = node._next
+ predecessor._next = successor
+ successor._prev = predecessor
+ self._size -= 1
+ element = node._element # record deleted element
+ node._prev = node._next = node._element = None # deprecate node
+ return element # return deleted element
diff --git a/ch07/favorites_list.py b/ch07/favorites_list.py
index 0bcccbf..5f9dcaa 100644
--- a/ch07/favorites_list.py
+++ b/ch07/favorites_list.py
@@ -1,99 +1,81 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
from .positional_list import PositionalList
+
class FavoritesList:
- """List of elements ordered from most frequently accessed to least."""
+ """List of elements ordered from most frequently accessed to least."""
+
+ # ------------------------------ nested _Item class ------------------------------
+ class _Item:
+ __slots__ = '_value', '_count' # streamline memory usage
+
+ def __init__(self, e):
+ self._value = e # the user's element
+ self._count = 0 # access count initially zero
+
+ # ------------------------------- nonpublic utilities -------------------------------
+ def _find_position(self, e):
+ """Search for element e and return its Position (or None if not found)."""
+ walk = self._data.first()
+ while walk is not None and walk.element()._value != e:
+ walk = self._data.after(walk)
+ return walk
- #------------------------------ nested _Item class ------------------------------
- class _Item:
- __slots__ = '_value', '_count' # streamline memory usage
- def __init__(self, e):
- self._value = e # the user's element
- self._count = 0 # access count initially zero
+ def _move_up(self, p):
+ """Move item at Position p earlier in the list based on access count."""
+ if p != self._data.first(): # consider moving...
+ cnt = p.element()._count
+ walk = self._data.before(p)
+ if cnt > walk.element()._count: # must shift forward
+ while (walk != self._data.first() and
+ cnt > self._data.before(walk).element()._count):
+ walk = self._data.before(walk)
+ self._data.add_before(walk, self._data.delete(p)) # delete/reinsert
- #------------------------------- nonpublic utilities -------------------------------
- def _find_position(self, e):
- """Search for element e and return its Position (or None if not found)."""
- walk = self._data.first()
- while walk is not None and walk.element()._value != e:
- walk = self._data.after(walk)
- return walk
+ # ------------------------------- public methods -------------------------------
+ def __init__(self):
+ """Create an empty list of favorites."""
+ self._data = PositionalList() # will be list of _Item instances
- def _move_up(self, p):
- """Move item at Position p earlier in the list based on access count."""
- if p != self._data.first(): # consider moving...
- cnt = p.element()._count
- walk = self._data.before(p)
- if cnt > walk.element()._count: # must shift forward
- while (walk != self._data.first() and
- cnt > self._data.before(walk).element()._count):
- walk = self._data.before(walk)
- self._data.add_before(walk, self._data.delete(p)) # delete/reinsert
-
- #------------------------------- public methods -------------------------------
- def __init__(self):
- """Create an empty list of favorites."""
- self._data = PositionalList() # will be list of _Item instances
+ def __len__(self):
+ """Return number of entries on favorites list."""
+ return len(self._data)
- def __len__(self):
- """Return number of entries on favorites list."""
- return len(self._data)
+ def is_empty(self):
+ """Return True if list is empty."""
+ return len(self._data) == 0
- def is_empty(self):
- """Return True if list is empty."""
- return len(self._data) == 0
+ def access(self, e):
+ """Access element e, thereby increasing its access count."""
+ p = self._find_position(e) # try to locate existing element
+ if p is None:
+ p = self._data.add_last(self._Item(e)) # if new, place at end
+ p.element()._count += 1 # always increment count
+ self._move_up(p) # consider moving forward
- def access(self, e):
- """Access element e, thereby increasing its access count."""
- p = self._find_position(e) # try to locate existing element
- if p is None:
- p = self._data.add_last(self._Item(e)) # if new, place at end
- p.element()._count += 1 # always increment count
- self._move_up(p) # consider moving forward
+ def remove(self, e):
+ """Remove element e from the list of favorites."""
+ p = self._find_position(e) # try to locate existing element
+ if p is not None:
+ self._data.delete(p) # delete, if found
- def remove(self, e):
- """Remove element e from the list of favorites."""
- p = self._find_position(e) # try to locate existing element
- if p is not None:
- self._data.delete(p) # delete, if found
+ def top(self, k):
+ """Generate sequence of top k elements in terms of access count."""
+ if not 1 <= k <= len(self):
+ raise ValueError('Illegal value for k')
+ walk = self._data.first()
+ for j in range(k):
+ item = walk.element() # element of list is _Item
+ yield item._value # report user's element
+ walk = self._data.after(walk)
- def top(self, k):
- """Generate sequence of top k elements in terms of access count."""
- if not 1 <= k <= len(self):
- raise ValueError('Illegal value for k')
- walk = self._data.first()
- for j in range(k):
- item = walk.element() # element of list is _Item
- yield item._value # report user's element
- walk = self._data.after(walk)
+ def __repr__(self):
+ """Create string representation of the favorites list."""
+ return ', '.join('({0}:{1})'.format(i._value, i._count) for i in self._data)
- def __repr__(self):
- """Create string representation of the favorites list."""
- return ', '.join('({0}:{1})'.format(i._value, i._count) for i in self._data)
if __name__ == '__main__':
- fav = FavoritesList()
- for c in 'hello. this is a test of mtf': # well, not the mtf part...
- fav.access(c)
- k = min(5, len(fav))
- print('Top {0}) {1:25} {2}'.format(k, [x for x in fav.top(k)], fav))
+ fav = FavoritesList()
+ for c in 'hello. this is a test of mtf': # well, not the mtf part...
+ fav.access(c)
+ k = min(5, len(fav))
+ print('Top {0}) {1:25} {2}'.format(k, [x for x in fav.top(k)], fav))
diff --git a/ch07/favorites_list_mtf.py b/ch07/favorites_list_mtf.py
index eb21b9d..1d2008a 100644
--- a/ch07/favorites_list_mtf.py
+++ b/ch07/favorites_list_mtf.py
@@ -1,63 +1,42 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
from .favorites_list import FavoritesList
from .positional_list import PositionalList
-class FavoritesListMTF(FavoritesList):
- """List of elements ordered with move-to-front heuristic."""
- # we override _move_up to provide move-to-front semantics
- def _move_up(self, p):
- """Move accessed item at Position p to front of list."""
- if p != self._data.first():
- self._data.add_first(self._data.delete(p)) # delete/reinsert
+class FavoritesListMTF(FavoritesList):
+ """List of elements ordered with move-to-front heuristic."""
- # we override top because list is no longer sorted
- def top(self, k):
- """Generate sequence of top k elements in terms of access count."""
- if not 1 <= k <= len(self):
- raise ValueError('Illegal value for k')
+ # we override _move_up to provide move-to-front semantics
+ def _move_up(self, p):
+ """Move accessed item at Position p to front of list."""
+ if p != self._data.first():
+ self._data.add_first(self._data.delete(p)) # delete/reinsert
- # we begin by making a copy of the original list
- temp = PositionalList()
- for item in self._data: # positional lists support iteration
- temp.add_last(item)
+ # we override top because list is no longer sorted
+ def top(self, k):
+ """Generate sequence of top k elements in terms of access count."""
+ if not 1 <= k <= len(self):
+ raise ValueError('Illegal value for k')
+ # we begin by making a copy of the original list
+ temp = PositionalList()
+ for item in self._data: # positional lists support iteration
+ temp.add_last(item)
+ # we repeatedly find, report, and remove element with largest count
+ for j in range(k):
+ # find and report next highest from temp
+ highPos = temp.first()
+ walk = temp.after(highPos)
+ while walk is not None:
+ if walk.element()._count > highPos.element()._count:
+ highPos = walk
+ walk = temp.after(walk)
+ # we have found the element with highest count
+ yield highPos.element()._value # report element to user
+ temp.delete(highPos) # remove from temp list
- # we repeatedly find, report, and remove element with largest count
- for j in range(k):
- # find and report next highest from temp
- highPos = temp.first()
- walk = temp.after(highPos)
- while walk is not None:
- if walk.element()._count > highPos.element()._count:
- highPos = walk
- walk = temp.after(walk)
- # we have found the element with highest count
- yield highPos.element()._value # report element to user
- temp.delete(highPos) # remove from temp list
if __name__ == '__main__':
- fav = FavoritesListMTF()
- for c in 'hello. this is a test of mtf':
- fav.access(c)
- k = min(5, len(fav))
- print('Top {0}) {1:25} {2}'.format(k, [x for x in fav.top(k)], fav))
+ fav = FavoritesListMTF()
+ for c in 'hello. this is a test of mtf':
+ fav.access(c)
+ k = min(5, len(fav))
+ print('Top {0}) {1:25} {2}'.format(k, [x for x in fav.top(k)], fav))
diff --git a/ch07/insertion_sort_positional.py b/ch07/insertion_sort_positional.py
index eef13de..baff36b 100644
--- a/ch07/insertion_sort_positional.py
+++ b/ch07/insertion_sort_positional.py
@@ -1,36 +1,15 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
def insertion_sort(L):
- """Sort PositionalList of comparable elements into nondecreasing order."""
- if len(L) > 1: # otherwise, no need to sort it
- marker = L.first()
- while marker != L.last():
- pivot = L.after(marker) # next item to place
- value = pivot.element()
- if value > marker.element(): # pivot is already sorted
- marker = pivot # pivot becomes new marker
- else: # must relocate pivot
- walk = marker # find leftmost item greater than value
- while walk != L.first() and L.before(walk).element() > value:
- walk = L.before(walk)
- L.delete(pivot)
- L.add_before(walk, value) # reinsert value before walk
+ """Sort PositionalList of comparable elements into nondecreasing order."""
+ if len(L) > 1: # otherwise, no need to sort it
+ marker = L.first()
+ while marker != L.last():
+ pivot = L.after(marker) # next item to place
+ value = pivot.element()
+ if value > marker.element(): # pivot is already sorted
+ marker = pivot # pivot becomes new marker
+ else: # must relocate pivot
+ walk = marker # find leftmost item greater than value
+ while walk != L.first() and L.before(walk).element() > value:
+ walk = L.before(walk)
+ L.delete(pivot)
+ L.add_before(walk, value) # reinsert value before walk
diff --git a/ch07/linked_deque.py b/ch07/linked_deque.py
index de633fd..a044d0d 100644
--- a/ch07/linked_deque.py
+++ b/ch07/linked_deque.py
@@ -1,70 +1,46 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
from .doubly_linked_base import _DoublyLinkedBase
from ..exceptions import Empty
-class LinkedDeque(_DoublyLinkedBase): # note the use of inheritance
- """Double-ended queue implementation based on a doubly linked list."""
-
- def first(self):
- """Return (but do not remove) the element at the front of the deque.
-
- Raise Empty exception if the deque is empty.
- """
- if self.is_empty():
- raise Empty("Deque is empty")
- return self._header._next._element # real item just after header
-
- def last(self):
- """Return (but do not remove) the element at the back of the deque.
-
- Raise Empty exception if the deque is empty.
- """
- if self.is_empty():
- raise Empty("Deque is empty")
- return self._trailer._prev._element # real item just before trailer
-
- def insert_first(self, e):
- """Add an element to the front of the deque."""
- self._insert_between(e, self._header, self._header._next) # after header
-
- def insert_last(self, e):
- """Add an element to the back of the deque."""
- self._insert_between(e, self._trailer._prev, self._trailer) # before trailer
-
- def delete_first(self):
- """Remove and return the element from the front of the deque.
-
- Raise Empty exception if the deque is empty.
- """
- if self.is_empty():
- raise Empty("Deque is empty")
- return self._delete_node(self._header._next) # use inherited method
-
- def delete_last(self):
- """Remove and return the element from the back of the deque.
- Raise Empty exception if the deque is empty.
- """
- if self.is_empty():
- raise Empty("Deque is empty")
- return self._delete_node(self._trailer._prev) # use inherited method
+class LinkedDeque(_DoublyLinkedBase): # note the use of inheritance
+ """Double-ended queue implementation based on a doubly linked list."""
+
+ def first(self):
+ """Return (but do not remove) the element at the front of the deque.
+ Raise Empty exception if the deque is empty.
+ """
+ if self.is_empty():
+ raise Empty("Deque is empty")
+ return self._header._next._element # real item just after header
+
+ def last(self):
+ """Return (but do not remove) the element at the back of the deque.
+ Raise Empty exception if the deque is empty.
+ """
+ if self.is_empty():
+ raise Empty("Deque is empty")
+ return self._trailer._prev._element # real item just before trailer
+
+ def insert_first(self, e):
+ """Add an element to the front of the deque."""
+ self._insert_between(e, self._header, self._header._next) # after header
+
+ def insert_last(self, e):
+ """Add an element to the back of the deque."""
+ self._insert_between(e, self._trailer._prev, self._trailer) # before trailer
+
+ def delete_first(self):
+ """Remove and return the element from the front of the deque.
+ Raise Empty exception if the deque is empty.
+ """
+ if self.is_empty():
+ raise Empty("Deque is empty")
+ return self._delete_node(self._header._next) # use inherited method
+
+ def delete_last(self):
+ """Remove and return the element from the back of the deque.
+ Raise Empty exception if the deque is empty.
+ """
+ if self.is_empty():
+ raise Empty("Deque is empty")
+ return self._delete_node(self._trailer._prev) # use inherited method
diff --git a/ch07/linked_queue.py b/ch07/linked_queue.py
index 3409df8..e604b89 100644
--- a/ch07/linked_queue.py
+++ b/ch07/linked_queue.py
@@ -1,82 +1,60 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
from ..exceptions import Empty
-class LinkedQueue:
- """FIFO queue implementation using a singly linked list for storage."""
-
- #-------------------------- nested _Node class --------------------------
- class _Node:
- """Lightweight, nonpublic class for storing a singly linked node."""
- __slots__ = '_element', '_next' # streamline memory usage
-
- def __init__(self, element, next):
- self._element = element
- self._next = next
-
- #------------------------------- queue methods -------------------------------
- def __init__(self):
- """Create an empty queue."""
- self._head = None
- self._tail = None
- self._size = 0 # number of queue elements
-
- def __len__(self):
- """Return the number of elements in the queue."""
- return self._size
- def is_empty(self):
- """Return True if the queue is empty."""
- return self._size == 0
-
- def first(self):
- """Return (but do not remove) the element at the front of the queue.
-
- Raise Empty exception if the queue is empty.
- """
- if self.is_empty():
- raise Empty('Queue is empty')
- return self._head._element # front aligned with head of list
-
- def dequeue(self):
- """Remove and return the first element of the queue (i.e., FIFO).
-
- Raise Empty exception if the queue is empty.
- """
- if self.is_empty():
- raise Empty('Queue is empty')
- answer = self._head._element
- self._head = self._head._next
- self._size -= 1
- if self.is_empty(): # special case as queue is empty
- self._tail = None # removed head had been the tail
- return answer
-
- def enqueue(self, e):
- """Add an element to the back of queue."""
- newest = self._Node(e, None) # node will be new tail node
- if self.is_empty():
- self._head = newest # special case: previously empty
- else:
- self._tail._next = newest
- self._tail = newest # update reference to tail node
- self._size += 1
+class LinkedQueue:
+ """FIFO queue implementation using a singly linked list for storage."""
+
+ # -------------------------- nested _Node class --------------------------
+ class _Node:
+ """Lightweight, nonpublic class for storing a singly linked node."""
+ __slots__ = '_element', '_next' # streamline memory usage
+
+ def __init__(self, element, next):
+ self._element = element
+ self._next = next
+
+ # ------------------------------- queue methods -------------------------------
+ def __init__(self):
+ """Create an empty queue."""
+ self._head = None
+ self._tail = None
+ self._size = 0 # number of queue elements
+
+ def __len__(self):
+ """Return the number of elements in the queue."""
+ return self._size
+
+ def is_empty(self):
+ """Return True if the queue is empty."""
+ return self._size == 0
+
+ def first(self):
+ """Return (but do not remove) the element at the front of the queue.
+ Raise Empty exception if the queue is empty.
+ """
+ if self.is_empty():
+ raise Empty('Queue is empty')
+ return self._head._element # front aligned with head of list
+
+ def dequeue(self):
+ """Remove and return the first element of the queue (i.e., FIFO).
+ Raise Empty exception if the queue is empty.
+ """
+ if self.is_empty():
+ raise Empty('Queue is empty')
+ answer = self._head._element
+ self._head = self._head._next
+ self._size -= 1
+ if self.is_empty(): # special case as queue is empty
+ self._tail = None # removed head had been the tail
+ return answer
+
+ def enqueue(self, e):
+ """Add an element to the back of queue."""
+ newest = self._Node(e, None) # node will be new tail node
+ if self.is_empty():
+ self._head = newest # special case: previously empty
+ else:
+ self._tail._next = newest
+ self._tail = newest # update reference to tail node
+ self._size += 1
diff --git a/ch07/linked_stack.py b/ch07/linked_stack.py
index b376a44..d9f67b4 100644
--- a/ch07/linked_stack.py
+++ b/ch07/linked_stack.py
@@ -1,74 +1,52 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
from ..exceptions import Empty
-class LinkedStack:
- """LIFO Stack implementation using a singly linked list for storage."""
-
- #-------------------------- nested _Node class --------------------------
- class _Node:
- """Lightweight, nonpublic class for storing a singly linked node."""
- __slots__ = '_element', '_next' # streamline memory usage
-
- def __init__(self, element, next): # initialize node's fields
- self._element = element # reference to user's element
- self._next = next # reference to next node
-
- #------------------------------- stack methods -------------------------------
- def __init__(self):
- """Create an empty stack."""
- self._head = None # reference to the head node
- self._size = 0 # number of stack elements
-
- def __len__(self):
- """Return the number of elements in the stack."""
- return self._size
- def is_empty(self):
- """Return True if the stack is empty."""
- return self._size == 0
-
- def push(self, e):
- """Add element e to the top of the stack."""
- self._head = self._Node(e, self._head) # create and link a new node
- self._size += 1
-
- def top(self):
- """Return (but do not remove) the element at the top of the stack.
-
- Raise Empty exception if the stack is empty.
- """
- if self.is_empty():
- raise Empty('Stack is empty')
- return self._head._element # top of stack is at head of list
-
- def pop(self):
- """Remove and return the element from the top of the stack (i.e., LIFO).
-
- Raise Empty exception if the stack is empty.
- """
- if self.is_empty():
- raise Empty('Stack is empty')
- answer = self._head._element
- self._head = self._head._next # bypass the former top node
- self._size -= 1
- return answer
+class LinkedStack:
+ """LIFO Stack implementation using a singly linked list for storage."""
+
+ # -------------------------- nested _Node class --------------------------
+ class _Node:
+ """Lightweight, nonpublic class for storing a singly linked node."""
+ __slots__ = '_element', '_next' # streamline memory usage
+
+ def __init__(self, element, next): # initialize node's fields
+ self._element = element # reference to user's element
+ self._next = next # reference to next node
+
+ # ------------------------------- stack methods -------------------------------
+ def __init__(self):
+ """Create an empty stack."""
+ self._head = None # reference to the head node
+ self._size = 0 # number of stack elements
+
+ def __len__(self):
+ """Return the number of elements in the stack."""
+ return self._size
+
+ def is_empty(self):
+ """Return True if the stack is empty."""
+ return self._size == 0
+
+ def push(self, e):
+ """Add element e to the top of the stack."""
+ self._head = self._Node(e, self._head) # create and link a new node
+ self._size += 1
+
+ def top(self):
+ """Return (but do not remove) the element at the top of the stack.
+ Raise Empty exception if the stack is empty.
+ """
+ if self.is_empty():
+ raise Empty('Stack is empty')
+ return self._head._element # top of stack is at head of list
+
+ def pop(self):
+ """Remove and return the element from the top of the stack (i.e., LIFO).
+ Raise Empty exception if the stack is empty.
+ """
+ if self.is_empty():
+ raise Empty('Stack is empty')
+ answer = self._head._element
+ self._head = self._head._next # bypass the former top node
+ self._size -= 1
+ return answer
diff --git a/ch07/positional_list.py b/ch07/positional_list.py
index 9673d3b..0deee32 100644
--- a/ch07/positional_list.py
+++ b/ch07/positional_list.py
@@ -1,136 +1,114 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
from .doubly_linked_base import _DoublyLinkedBase
+
class PositionalList(_DoublyLinkedBase):
- """A sequential container of elements allowing positional access."""
-
- #-------------------------- nested Position class --------------------------
- class Position:
- """An abstraction representing the location of a single element.
-
- Note that two position instaces may represent the same inherent
- location in the list. Therefore, users should always rely on
- syntax 'p == q' rather than 'p is q' when testing equivalence of
- positions.
- """
-
- def __init__(self, container, node):
- """Constructor should not be invoked by user."""
- self._container = container
- self._node = node
-
- def element(self):
- """Return the element stored at this Position."""
- return self._node._element
-
- def __eq__(self, other):
- """Return True if other is a Position representing the same location."""
- return type(other) is type(self) and other._node is self._node
-
- def __ne__(self, other):
- """Return True if other does not represent the same location."""
- return not (self == other) # opposite of __eq__
-
- #------------------------------- utility methods -------------------------------
- def _validate(self, p):
- """Return position's node, or raise appropriate error if invalid."""
- if not isinstance(p, self.Position):
- raise TypeError('p must be proper Position type')
- if p._container is not self:
- raise ValueError('p does not belong to this container')
- if p._node._next is None: # convention for deprecated nodes
- raise ValueError('p is no longer valid')
- return p._node
-
- def _make_position(self, node):
- """Return Position instance for given node (or None if sentinel)."""
- if node is self._header or node is self._trailer:
- return None # boundary violation
- else:
- return self.Position(self, node) # legitimate position
-
- #------------------------------- accessors -------------------------------
- def first(self):
- """Return the first Position in the list (or None if list is empty)."""
- return self._make_position(self._header._next)
-
- def last(self):
- """Return the last Position in the list (or None if list is empty)."""
- return self._make_position(self._trailer._prev)
-
- def before(self, p):
- """Return the Position just before Position p (or None if p is first)."""
- node = self._validate(p)
- return self._make_position(node._prev)
-
- def after(self, p):
- """Return the Position just after Position p (or None if p is last)."""
- node = self._validate(p)
- return self._make_position(node._next)
-
- def __iter__(self):
- """Generate a forward iteration of the elements of the list."""
- cursor = self.first()
- while cursor is not None:
- yield cursor.element()
- cursor = self.after(cursor)
-
- #------------------------------- mutators -------------------------------
- # override inherited version to return Position, rather than Node
- def _insert_between(self, e, predecessor, successor):
- """Add element between existing nodes and return new Position."""
- node = super()._insert_between(e, predecessor, successor)
- return self._make_position(node)
-
- def add_first(self, e):
- """Insert element e at the front of the list and return new Position."""
- return self._insert_between(e, self._header, self._header._next)
-
- def add_last(self, e):
- """Insert element e at the back of the list and return new Position."""
- return self._insert_between(e, self._trailer._prev, self._trailer)
-
- def add_before(self, p, e):
- """Insert element e into list before Position p and return new Position."""
- original = self._validate(p)
- return self._insert_between(e, original._prev, original)
-
- def add_after(self, p, e):
- """Insert element e into list after Position p and return new Position."""
- original = self._validate(p)
- return self._insert_between(e, original, original._next)
-
- def delete(self, p):
- """Remove and return the element at Position p."""
- original = self._validate(p)
- return self._delete_node(original) # inherited method returns element
-
- def replace(self, p, e):
- """Replace the element at Position p with e.
-
- Return the element formerly at Position p.
- """
- original = self._validate(p)
- old_value = original._element # temporarily store old element
- original._element = e # replace with new element
- return old_value # return the old element value
+ """A sequential container of elements allowing positional access."""
+
+ # -------------------------- nested Position class --------------------------
+ class Position:
+ """An abstraction representing the location of a single element.
+ Note that two position instaces may represent the same inherent
+ location in the list. Therefore, users should always rely on
+ syntax 'p == q' rather than 'p is q' when testing equivalence of
+ positions.
+ """
+
+ def __init__(self, container, node):
+ """Constructor should not be invoked by user."""
+ self._container = container
+ self._node = node
+
+ def element(self):
+ """Return the element stored at this Position."""
+ return self._node._element
+
+ def __eq__(self, other):
+ """Return True if other is a Position representing the same location."""
+ return type(other) is type(self) and other._node is self._node
+
+ def __ne__(self, other):
+ """Return True if other does not represent the same location."""
+ return not (self == other) # opposite of __eq__
+
+ # ------------------------------- utility methods -------------------------------
+ def _validate(self, p):
+ """Return position's node, or raise appropriate error if invalid."""
+ if not isinstance(p, self.Position):
+ raise TypeError('p must be proper Position type')
+ if p._container is not self:
+ raise ValueError('p does not belong to this container')
+ if p._node._next is None: # convention for deprecated nodes
+ raise ValueError('p is no longer valid')
+ return p._node
+
+ def _make_position(self, node):
+ """Return Position instance for given node (or None if sentinel)."""
+ if node is self._header or node is self._trailer:
+ return None # boundary violation
+ else:
+ return self.Position(self, node) # legitimate position
+
+ # ------------------------------- accessors -------------------------------
+ def first(self):
+ """Return the first Position in the list (or None if list is empty)."""
+ return self._make_position(self._header._next)
+
+ def last(self):
+ """Return the last Position in the list (or None if list is empty)."""
+ return self._make_position(self._trailer._prev)
+
+ def before(self, p):
+ """Return the Position just before Position p (or None if p is first)."""
+ node = self._validate(p)
+ return self._make_position(node._prev)
+
+ def after(self, p):
+ """Return the Position just after Position p (or None if p is last)."""
+ node = self._validate(p)
+ return self._make_position(node._next)
+
+ def __iter__(self):
+ """Generate a forward iteration of the elements of the list."""
+ cursor = self.first()
+ while cursor is not None:
+ yield cursor.element()
+ cursor = self.after(cursor)
+
+ # ------------------------------- mutators -------------------------------
+ # override inherited version to return Position, rather than Node
+ def _insert_between(self, e, predecessor, successor):
+ """Add element between existing nodes and return new Position."""
+ node = super()._insert_between(e, predecessor, successor)
+ return self._make_position(node)
+
+ def add_first(self, e):
+ """Insert element e at the front of the list and return new Position."""
+ return self._insert_between(e, self._header, self._header._next)
+
+ def add_last(self, e):
+ """Insert element e at the back of the list and return new Position."""
+ return self._insert_between(e, self._trailer._prev, self._trailer)
+
+ def add_before(self, p, e):
+ """Insert element e into list before Position p and return new Position."""
+ original = self._validate(p)
+ return self._insert_between(e, original._prev, original)
+
+ def add_after(self, p, e):
+ """Insert element e into list after Position p and return new Position."""
+ original = self._validate(p)
+ return self._insert_between(e, original, original._next)
+
+ def delete(self, p):
+ """Remove and return the element at Position p."""
+ original = self._validate(p)
+ return self._delete_node(original) # inherited method returns element
+
+ def replace(self, p, e):
+ """Replace the element at Position p with e.
+ Return the element formerly at Position p.
+ """
+ original = self._validate(p)
+ old_value = original._element # temporarily store old element
+ original._element = e # replace with new element
+ return old_value # return the old element value
diff --git a/ch08/binary_tree.py b/ch08/binary_tree.py
index 2f3e27b..c13dce1 100644
--- a/ch08/binary_tree.py
+++ b/ch08/binary_tree.py
@@ -1,80 +1,58 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
from .tree import Tree
-class BinaryTree(Tree):
- """Abstract base class representing a binary tree structure."""
-
- # --------------------- additional abstract methods ---------------------
- def left(self, p):
- """Return a Position representing p's left child.
-
- Return None if p does not have a left child.
- """
- raise NotImplementedError('must be implemented by subclass')
-
- def right(self, p):
- """Return a Position representing p's right child.
-
- Return None if p does not have a right child.
- """
- raise NotImplementedError('must be implemented by subclass')
- # ---------- concrete methods implemented in this class ----------
- def sibling(self, p):
- """Return a Position representing p's sibling (or None if no sibling)."""
- parent = self.parent(p)
- if parent is None: # p must be the root
- return None # root has no sibling
- else:
- if p == self.left(parent):
- return self.right(parent) # possibly None
- else:
- return self.left(parent) # possibly None
-
- def children(self, p):
- """Generate an iteration of Positions representing p's children."""
- if self.left(p) is not None:
- yield self.left(p)
- if self.right(p) is not None:
- yield self.right(p)
-
- def inorder(self):
- """Generate an inorder iteration of positions in the tree."""
- if not self.is_empty():
- for p in self._subtree_inorder(self.root()):
- yield p
-
- def _subtree_inorder(self, p):
- """Generate an inorder iteration of positions in subtree rooted at p."""
- if self.left(p) is not None: # if left child exists, traverse its subtree
- for other in self._subtree_inorder(self.left(p)):
- yield other
- yield p # visit p between its subtrees
- if self.right(p) is not None: # if right child exists, traverse its subtree
- for other in self._subtree_inorder(self.right(p)):
- yield other
-
- # override inherited version to make inorder the default
- def positions(self):
- """Generate an iteration of the tree's positions."""
- return self.inorder() # make inorder the default
+class BinaryTree(Tree):
+ """Abstract base class representing a binary tree structure."""
+
+ # --------------------- additional abstract methods ---------------------
+ def left(self, p):
+ """Return a Position representing p's left child.
+ Return None if p does not have a left child.
+ """
+ raise NotImplementedError('must be implemented by subclass')
+
+ def right(self, p):
+ """Return a Position representing p's right child.
+ Return None if p does not have a right child.
+ """
+ raise NotImplementedError('must be implemented by subclass')
+
+ # ---------- concrete methods implemented in this class ----------
+ def sibling(self, p):
+ """Return a Position representing p's sibling (or None if no sibling)."""
+ parent = self.parent(p)
+ if parent is None: # p must be the root
+ return None # root has no sibling
+ else:
+ if p == self.left(parent):
+ return self.right(parent) # possibly None
+ else:
+ return self.left(parent) # possibly None
+
+ def children(self, p):
+ """Generate an iteration of Positions representing p's children."""
+ if self.left(p) is not None:
+ yield self.left(p)
+ if self.right(p) is not None:
+ yield self.right(p)
+
+ def inorder(self):
+ """Generate an inorder iteration of positions in the tree."""
+ if not self.is_empty():
+ for p in self._subtree_inorder(self.root()):
+ yield p
+
+ def _subtree_inorder(self, p):
+ """Generate an inorder iteration of positions in subtree rooted at p."""
+ if self.left(p) is not None: # if left child exists, traverse its subtree
+ for other in self._subtree_inorder(self.left(p)):
+ yield other
+ yield p # visit p between its subtrees
+ if self.right(p) is not None: # if right child exists, traverse its subtree
+ for other in self._subtree_inorder(self.right(p)):
+ yield other
+
+ # override inherited version to make inorder the default
+ def positions(self):
+ """Generate an iteration of the tree's positions."""
+ return self.inorder() # make inorder the default
diff --git a/ch08/euler_tour.py b/ch08/euler_tour.py
index b4c22e5..99237ea 100644
--- a/ch08/euler_tour.py
+++ b/ch08/euler_tour.py
@@ -1,139 +1,121 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
class EulerTour:
- """Abstract base class for performing Euler tour of a tree.
-
- _hook_previsit and _hook_postvisit may be overridden by subclasses.
- """
- def __init__(self, tree):
- """Prepare an Euler tour template for given tree."""
- self._tree = tree
-
- def tree(self):
- """Return reference to the tree being traversed."""
- return self._tree
-
- def execute(self):
- """Perform the tour and return any result from post visit of root."""
- if len(self._tree) > 0:
- return self._tour(self._tree.root(), 0, []) # start the recursion
-
- def _tour(self, p, d, path):
- """Perform tour of subtree rooted at Position p.
-
- p Position of current node being visited
- d depth of p in the tree
- path list of indices of children on path from root to p
- """
- self._hook_previsit(p, d, path) # "pre visit" p
- results = []
- path.append(0) # add new index to end of path before recursion
- for c in self._tree.children(p):
- results.append(self._tour(c, d+1, path)) # recur on child's subtree
- path[-1] += 1 # increment index
- path.pop() # remove extraneous index from end of path
- answer = self._hook_postvisit(p, d, path, results) # "post visit" p
- return answer
-
- def _hook_previsit(self, p, d, path):
- """Visit Position p, before the tour of its children.
-
- p Position of current position being visited
- d depth of p in the tree
- path list of indices of children on path from root to p
+ """Abstract base class for performing Euler tour of a tree.
+ _hook_previsit and _hook_postvisit may be overridden by subclasses.
"""
- pass
- def _hook_postvisit(self, p, d, path, results):
- """Visit Position p, after the tour of its children.
+ def __init__(self, tree):
+ """Prepare an Euler tour template for given tree."""
+ self._tree = tree
+
+ def tree(self):
+ """Return reference to the tree being traversed."""
+ return self._tree
+
+ def execute(self):
+ """Perform the tour and return any result from post visit of root."""
+ if len(self._tree) > 0:
+ return self._tour(self._tree.root(), 0, []) # start the recursion
+
+ def _tour(self, p, d, path):
+ """Perform tour of subtree rooted at Position p.
+ p Position of current node being visited
+ d depth of p in the tree
+ path list of indices of children on path from root to p
+ """
+ self._hook_previsit(p, d, path) # "pre visit" p
+ results = []
+ path.append(0) # add new index to end of path before recursion
+ for c in self._tree.children(p):
+ results.append(self._tour(c, d + 1, path)) # recur on child's subtree
+ path[-1] += 1 # increment index
+ path.pop() # remove extraneous index from end of path
+ answer = self._hook_postvisit(p, d, path, results) # "post visit" p
+ return answer
+
+ def _hook_previsit(self, p, d, path):
+ """Visit Position p, before the tour of its children.
+ p Position of current position being visited
+ d depth of p in the tree
+ path list of indices of children on path from root to p
+ """
+ pass
+
+ def _hook_postvisit(self, p, d, path, results):
+ """Visit Position p, after the tour of its children.
+ p Position of current position being visited
+ d depth of p in the tree
+ path list of indices of children on path from root to p
+ results is a list of values returned by _hook_postvisit(c)
+ for each child c of p.
+ """
+ pass
- p Position of current position being visited
- d depth of p in the tree
- path list of indices of children on path from root to p
- results is a list of values returned by _hook_postvisit(c)
- for each child c of p.
- """
- pass
class PreorderPrintIndentedTour(EulerTour):
- def _hook_previsit(self, p, d, path):
- print(2*d*' ' + str(p.element()))
+ def _hook_previsit(self, p, d, path):
+ print(2 * d * ' ' + str(p.element()))
+
class PreorderPrintIndentedLabeledTour(EulerTour):
- def _hook_previsit(self, p, d, path):
- label = '.'.join(str(j+1) for j in path) # labels are one-indexed
- print(2*d*' ' + label, p.element())
+ def _hook_previsit(self, p, d, path):
+ label = '.'.join(str(j + 1) for j in path) # labels are one-indexed
+ print(2 * d * ' ' + label, p.element())
+
class ParenthesizeTour(EulerTour):
- def _hook_previsit(self, p, d, path):
- if path and path[-1] > 0: # p follows a sibling
- print(', ', end='') # so preface with comma
- print(p.element(), end='') # then print element
- if not self.tree().is_leaf(p): # if p has children
- print(' (', end='') # print opening parenthesis
+ def _hook_previsit(self, p, d, path):
+ if path and path[-1] > 0: # p follows a sibling
+ print(', ', end='') # so preface with comma
+ print(p.element(), end='') # then print element
+ if not self.tree().is_leaf(p): # if p has children
+ print(' (', end='') # print opening parenthesis
+
+ def _hook_postvisit(self, p, d, path, results):
+ if not self.tree().is_leaf(p): # if p has children
+ print(')', end='') # print closing parenthesis
- def _hook_postvisit(self, p, d, path, results):
- if not self.tree().is_leaf(p): # if p has children
- print(')', end='') # print closing parenthesis
class DiskSpaceTour(EulerTour):
- def _hook_postvisit(self, p, d, path, results):
- # we simply add space associated with p to that of its subtrees
- return p.element().space() + sum(results)
+ def _hook_postvisit(self, p, d, path, results):
+ # we simply add space associated with p to that of its subtrees
+ return p.element().space() + sum(results)
+
class BinaryEulerTour(EulerTour):
- """Abstract base class for performing Euler tour of a binary tree.
-
- This version includes an additional _hook_invisit that is called after the tour
- of the left subtree (if any), yet before the tour of the right subtree (if any).
-
- Note: Right child is always assigned index 1 in path, even if no left sibling.
- """
- def _tour(self, p, d, path):
- results = [None, None] # will update with results of recursions
- self._hook_previsit(p, d, path) # "pre visit" for p
- if self._tree.left(p) is not None: # consider left child
- path.append(0)
- results[0] = self._tour(self._tree.left(p), d+1, path)
- path.pop()
- self._hook_invisit(p, d, path) # "in visit" for p
- if self._tree.right(p) is not None: # consider right child
- path.append(1)
- results[1] = self._tour(self._tree.right(p), d+1, path)
- path.pop()
- answer = self._hook_postvisit(p, d, path, results) # "post visit" p
- return answer
-
- def _hook_invisit(self, p, d, path):
- """Visit Position p, between tour of its left and right subtrees."""
- pass
+ """Abstract base class for performing Euler tour of a binary tree.
+ This version includes an additional _hook_invisit that is called after the tour
+ of the left subtree (if any), yet before the tour of the right subtree (if any).
+ Note: Right child is always assigned index 1 in path, even if no left sibling.
+ """
+
+ def _tour(self, p, d, path):
+ results = [None, None] # will update with results of recursions
+ self._hook_previsit(p, d, path) # "pre visit" for p
+ if self._tree.left(p) is not None: # consider left child
+ path.append(0)
+ results[0] = self._tour(self._tree.left(p), d + 1, path)
+ path.pop()
+ self._hook_invisit(p, d, path) # "in visit" for p
+ if self._tree.right(p) is not None: # consider right child
+ path.append(1)
+ results[1] = self._tour(self._tree.right(p), d + 1, path)
+ path.pop()
+ answer = self._hook_postvisit(p, d, path, results) # "post visit" p
+ return answer
+
+ def _hook_invisit(self, p, d, path):
+ """Visit Position p, between tour of its left and right subtrees."""
+ pass
+
class BinaryLayout(BinaryEulerTour):
- """Class for computing (x,y) coordinates for each node of a binary tree."""
- def __init__(self, tree):
- super().__init__(tree) # must call the parent constructor
- self._count = 0 # initialize count of processed nodes
-
- def _hook_invisit(self, p, d, path):
- p.element().setX(self._count) # x-coordinate serialized by count
- p.element().setY(d) # y-coordinate is depth
- self._count += 1 # advance count of processed nodes
+ """Class for computing (x,y) coordinates for each node of a binary tree."""
+
+ def __init__(self, tree):
+ super().__init__(tree) # must call the parent constructor
+ self._count = 0 # initialize count of processed nodes
+
+ def _hook_invisit(self, p, d, path):
+ p.element().setX(self._count) # x-coordinate serialized by count
+ p.element().setY(d) # y-coordinate is depth
+ self._count += 1 # advance count of processed nodes
diff --git a/ch08/expression_tree.py b/ch08/expression_tree.py
index 0535689..56e213f 100644
--- a/ch08/expression_tree.py
+++ b/ch08/expression_tree.py
@@ -1,129 +1,106 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
from .linked_binary_tree import LinkedBinaryTree
-class ExpressionTree(LinkedBinaryTree):
- """An arithmetic expression tree."""
-
- def __init__(self, token, left=None, right=None):
- """Create an expression tree.
-
- In a single parameter form, token should be a leaf value (e.g., '42'),
- and the expression tree will have that value at an isolated node.
-
- In a three-parameter version, token should be an operator,
- and left and right should be existing ExpressionTree instances
- that become the operands for the binary operator.
- """
- super().__init__() # LinkedBinaryTree initialization
- if not isinstance(token, str):
- raise TypeError('Token must be a string')
- self._add_root(token) # use inherited, nonpublic method
- if left is not None: # presumably three-parameter form
- if token not in '+-*x/':
- raise ValueError('token must be valid operator')
- self._attach(self.root(), left, right) # use inherited, nonpublic method
-
- def __str__(self):
- """Return string representation of the expression."""
- pieces = [] # sequence of piecewise strings to compose
- self._parenthesize_recur(self.root(), pieces)
- return ''.join(pieces)
- def _parenthesize_recur(self, p, result):
- """Append piecewise representation of p's subtree to resulting list."""
- if self.is_leaf(p):
- result.append(str(p.element())) # leaf value as a string
- else:
- result.append('(') # opening parenthesis
- self._parenthesize_recur(self.left(p), result) # left subtree
- result.append(p.element()) # operator
- self._parenthesize_recur(self.right(p), result) # right subtree
- result.append(')') # closing parenthesis
-
- def evaluate(self):
- """Return the numeric result of the expression."""
- return self._evaluate_recur(self.root())
-
- def _evaluate_recur(self, p):
- """Return the numeric result of subtree rooted at p."""
- if self.is_leaf(p):
- return float(p.element()) # we assume element is numeric
- else:
- op = p.element()
- left_val = self._evaluate_recur(self.left(p))
- right_val = self._evaluate_recur(self.right(p))
- if op == '+':
- return left_val + right_val
- elif op == '-':
- return left_val - right_val
- elif op == '/':
- return left_val / right_val
- else: # treat 'x' or '*' as multiplication
- return left_val * right_val
+class ExpressionTree(LinkedBinaryTree):
+ """An arithmetic expression tree."""
+
+ def __init__(self, token, left=None, right=None):
+ """Create an expression tree.
+ In a single parameter form, token should be a leaf value (e.g., '42'),
+ and the expression tree will have that value at an isolated node.
+ In a three-parameter version, token should be an operator,
+ and left and right should be existing ExpressionTree instances
+ that become the operands for the binary operator.
+ """
+ super().__init__() # LinkedBinaryTree initialization
+ if not isinstance(token, str):
+ raise TypeError('Token must be a string')
+ self._add_root(token) # use inherited, nonpublic method
+ if left is not None: # presumably three-parameter form
+ if token not in '+-*x/':
+ raise ValueError('token must be valid operator')
+ self._attach(self.root(), left, right) # use inherited, nonpublic method
+
+ def __str__(self):
+ """Return string representation of the expression."""
+ pieces = [] # sequence of piecewise strings to compose
+ self._parenthesize_recur(self.root(), pieces)
+ return ''.join(pieces)
+
+ def _parenthesize_recur(self, p, result):
+ """Append piecewise representation of p's subtree to resulting list."""
+ if self.is_leaf(p):
+ result.append(str(p.element())) # leaf value as a string
+ else:
+ result.append('(') # opening parenthesis
+ self._parenthesize_recur(self.left(p), result) # left subtree
+ result.append(p.element()) # operator
+ self._parenthesize_recur(self.right(p), result) # right subtree
+ result.append(')') # closing parenthesis
+
+ def evaluate(self):
+ """Return the numeric result of the expression."""
+ return self._evaluate_recur(self.root())
+
+ def _evaluate_recur(self, p):
+ """Return the numeric result of subtree rooted at p."""
+ if self.is_leaf(p):
+ return float(p.element()) # we assume element is numeric
+ else:
+ op = p.element()
+ left_val = self._evaluate_recur(self.left(p))
+ right_val = self._evaluate_recur(self.right(p))
+ if op == '+':
+ return left_val + right_val
+ elif op == '-':
+ return left_val - right_val
+ elif op == '/':
+ return left_val / right_val
+ else: # treat 'x' or '*' as multiplication
+ return left_val * right_val
def tokenize(raw):
- """Produces list of tokens indicated by a raw expression string.
-
- For example the string '(43-(3*10))' results in the list
- ['(', '43', '-', '(', '3', '*', '10', ')', ')']
- """
- SYMBOLS = set('+-x*/() ') # allow for '*' or 'x' for multiplication
+ """Produces list of tokens indicated by a raw expression string.
+ For example the string '(43-(3*10))' results in the list
+ ['(', '43', '-', '(', '3', '*', '10', ')', ')']
+ """
+ SYMBOLS = set('+-x*/() ') # allow for '*' or 'x' for multiplication
+ mark = 0
+ tokens = []
+ n = len(raw)
+ for j in range(n):
+ if raw[j] in SYMBOLS:
+ if mark != j:
+ tokens.append(raw[mark:j]) # complete preceding token
+ if raw[j] != ' ':
+ tokens.append(raw[j]) # include this token
+ mark = j + 1 # update mark to being at next index
+ if mark != n:
+ tokens.append(raw[mark:n]) # complete preceding token
+ return tokens
- mark = 0
- tokens = []
- n = len(raw)
- for j in range(n):
- if raw[j] in SYMBOLS:
- if mark != j:
- tokens.append(raw[mark:j]) # complete preceding token
- if raw[j] != ' ':
- tokens.append(raw[j]) # include this token
- mark = j+1 # update mark to being at next index
- if mark != n:
- tokens.append(raw[mark:n]) # complete preceding token
- return tokens
def build_expression_tree(tokens):
- """Returns an ExpressionTree based upon by a tokenized expression.
+ """Returns an ExpressionTree based upon by a tokenized expression.
+ tokens must be an iterable of strings representing a fully parenthesized
+ binary expression, such as ['(', '43', '-', '(', '3', '*', '10', ')', ')']
+ """
+ S = [] # we use Python list as stack
+ for t in tokens:
+ if t in '+-x*/': # t is an operator symbol
+ S.append(t) # push the operator symbol
+ elif t not in '()': # consider t to be a literal
+ S.append(ExpressionTree(t)) # push trivial tree storing value
+ elif t == ')': # compose a new tree from three constituent parts
+ right = S.pop() # right subtree as per LIFO
+ op = S.pop() # operator symbol
+ left = S.pop() # left subtree
+ S.append(ExpressionTree(op, left, right)) # repush tree
+ # we ignore a left parenthesis
+ return S.pop()
- tokens must be an iterable of strings representing a fully parenthesized
- binary expression, such as ['(', '43', '-', '(', '3', '*', '10', ')', ')']
- """
- S = [] # we use Python list as stack
- for t in tokens:
- if t in '+-x*/': # t is an operator symbol
- S.append(t) # push the operator symbol
- elif t not in '()': # consider t to be a literal
- S.append(ExpressionTree(t)) # push trivial tree storing value
- elif t == ')': # compose a new tree from three constituent parts
- right = S.pop() # right subtree as per LIFO
- op = S.pop() # operator symbol
- left = S.pop() # left subtree
- S.append(ExpressionTree(op, left, right)) # repush tree
- # we ignore a left parenthesis
- return S.pop()
if __name__ == '__main__':
- big = build_expression_tree(tokenize('((((3+1)x3)/((9-5)+2))-((3x(7-4))+6))'))
- print(big, '=', big.evaluate())
+ big = build_expression_tree(tokenize('((((3+1)x3)/((9-5)+2))-((3x(7-4))+6))'))
+ print(big, '=', big.evaluate())
diff --git a/ch08/linked_binary_tree.py b/ch08/linked_binary_tree.py
index be3eeeb..23bd6a0 100644
--- a/ch08/linked_binary_tree.py
+++ b/ch08/linked_binary_tree.py
@@ -1,201 +1,176 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
from .binary_tree import BinaryTree
+
class LinkedBinaryTree(BinaryTree):
- """Linked representation of a binary tree structure."""
-
- #-------------------------- nested _Node class --------------------------
- class _Node:
- """Lightweight, nonpublic class for storing a node."""
- __slots__ = '_element', '_parent', '_left', '_right' # streamline memory usage
-
- def __init__(self, element, parent=None, left=None, right=None):
- self._element = element
- self._parent = parent
- self._left = left
- self._right = right
-
- #-------------------------- nested Position class --------------------------
- class Position(BinaryTree.Position):
- """An abstraction representing the location of a single element."""
-
- def __init__(self, container, node):
- """Constructor should not be invoked by user."""
- self._container = container
- self._node = node
-
- def element(self):
- """Return the element stored at this Position."""
- return self._node._element
-
- def __eq__(self, other):
- """Return True if other is a Position representing the same location."""
- return type(other) is type(self) and other._node is self._node
-
- #------------------------------- utility methods -------------------------------
- def _validate(self, p):
- """Return associated node, if position is valid."""
- if not isinstance(p, self.Position):
- raise TypeError('p must be proper Position type')
- if p._container is not self:
- raise ValueError('p does not belong to this container')
- if p._node._parent is p._node: # convention for deprecated nodes
- raise ValueError('p is no longer valid')
- return p._node
-
- def _make_position(self, node):
- """Return Position instance for given node (or None if no node)."""
- return self.Position(self, node) if node is not None else None
-
- #-------------------------- binary tree constructor --------------------------
- def __init__(self):
- """Create an initially empty binary tree."""
- self._root = None
- self._size = 0
-
- #-------------------------- public accessors --------------------------
- def __len__(self):
- """Return the total number of elements in the tree."""
- return self._size
-
- def root(self):
- """Return the root Position of the tree (or None if tree is empty)."""
- return self._make_position(self._root)
-
- def parent(self, p):
- """Return the Position of p's parent (or None if p is root)."""
- node = self._validate(p)
- return self._make_position(node._parent)
-
- def left(self, p):
- """Return the Position of p's left child (or None if no left child)."""
- node = self._validate(p)
- return self._make_position(node._left)
-
- def right(self, p):
- """Return the Position of p's right child (or None if no right child)."""
- node = self._validate(p)
- return self._make_position(node._right)
-
- def num_children(self, p):
- """Return the number of children of Position p."""
- node = self._validate(p)
- count = 0
- if node._left is not None: # left child exists
- count += 1
- if node._right is not None: # right child exists
- count += 1
- return count
-
- #-------------------------- nonpublic mutators --------------------------
- def _add_root(self, e):
- """Place element e at the root of an empty tree and return new Position.
-
- Raise ValueError if tree nonempty.
- """
- if self._root is not None:
- raise ValueError('Root exists')
- self._size = 1
- self._root = self._Node(e)
- return self._make_position(self._root)
-
- def _add_left(self, p, e):
- """Create a new left child for Position p, storing element e.
-
- Return the Position of new node.
- Raise ValueError if Position p is invalid or p already has a left child.
- """
- node = self._validate(p)
- if node._left is not None:
- raise ValueError('Left child exists')
- self._size += 1
- node._left = self._Node(e, node) # node is its parent
- return self._make_position(node._left)
-
- def _add_right(self, p, e):
- """Create a new right child for Position p, storing element e.
-
- Return the Position of new node.
- Raise ValueError if Position p is invalid or p already has a right child.
- """
- node = self._validate(p)
- if node._right is not None:
- raise ValueError('Right child exists')
- self._size += 1
- node._right = self._Node(e, node) # node is its parent
- return self._make_position(node._right)
-
- def _replace(self, p, e):
- """Replace the element at position p with e, and return old element."""
- node = self._validate(p)
- old = node._element
- node._element = e
- return old
-
- def _delete(self, p):
- """Delete the node at Position p, and replace it with its child, if any.
-
- Return the element that had been stored at Position p.
- Raise ValueError if Position p is invalid or p has two children.
- """
- node = self._validate(p)
- if self.num_children(p) == 2:
- raise ValueError('Position has two children')
- child = node._left if node._left else node._right # might be None
- if child is not None:
- child._parent = node._parent # child's grandparent becomes parent
- if node is self._root:
- self._root = child # child becomes root
- else:
- parent = node._parent
- if node is parent._left:
- parent._left = child
- else:
- parent._right = child
- self._size -= 1
- node._parent = node # convention for deprecated node
- return node._element
-
- def _attach(self, p, t1, t2):
- """Attach trees t1 and t2, respectively, as the left and right subtrees of the external Position p.
-
- As a side effect, set t1 and t2 to empty.
- Raise TypeError if trees t1 and t2 do not match type of this tree.
- Raise ValueError if Position p is invalid or not external.
- """
- node = self._validate(p)
- if not self.is_leaf(p):
- raise ValueError('position must be leaf')
- if not type(self) is type(t1) is type(t2): # all 3 trees must be same type
- raise TypeError('Tree types must match')
- self._size += len(t1) + len(t2)
- if not t1.is_empty(): # attached t1 as left subtree of node
- t1._root._parent = node
- node._left = t1._root
- t1._root = None # set t1 instance to empty
- t1._size = 0
- if not t2.is_empty(): # attached t2 as right subtree of node
- t2._root._parent = node
- node._right = t2._root
- t2._root = None # set t2 instance to empty
- t2._size = 0
+ """Linked representation of a binary tree structure."""
+
+ # -------------------------- nested _Node class --------------------------
+ class _Node:
+ """Lightweight, nonpublic class for storing a node."""
+ __slots__ = '_element', '_parent', '_left', '_right' # streamline memory usage
+
+ def __init__(self, element, parent=None, left=None, right=None):
+ self._element = element
+ self._parent = parent
+ self._left = left
+ self._right = right
+
+ # -------------------------- nested Position class --------------------------
+ class Position(BinaryTree.Position):
+ """An abstraction representing the location of a single element."""
+
+ def __init__(self, container, node):
+ """Constructor should not be invoked by user."""
+ self._container = container
+ self._node = node
+
+ def element(self):
+ """Return the element stored at this Position."""
+ return self._node._element
+
+ def __eq__(self, other):
+ """Return True if other is a Position representing the same location."""
+ return type(other) is type(self) and other._node is self._node
+
+ # ------------------------------- utility methods -------------------------------
+ def _validate(self, p):
+ """Return associated node, if position is valid."""
+ if not isinstance(p, self.Position):
+ raise TypeError('p must be proper Position type')
+ if p._container is not self:
+ raise ValueError('p does not belong to this container')
+ if p._node._parent is p._node: # convention for deprecated nodes
+ raise ValueError('p is no longer valid')
+ return p._node
+
+ def _make_position(self, node):
+ """Return Position instance for given node (or None if no node)."""
+ return self.Position(self, node) if node is not None else None
+
+ # -------------------------- binary tree constructor --------------------------
+ def __init__(self):
+ """Create an initially empty binary tree."""
+ self._root = None
+ self._size = 0
+
+ # -------------------------- public accessors --------------------------
+ def __len__(self):
+ """Return the total number of elements in the tree."""
+ return self._size
+
+ def root(self):
+ """Return the root Position of the tree (or None if tree is empty)."""
+ return self._make_position(self._root)
+
+ def parent(self, p):
+ """Return the Position of p's parent (or None if p is root)."""
+ node = self._validate(p)
+ return self._make_position(node._parent)
+
+ def left(self, p):
+ """Return the Position of p's left child (or None if no left child)."""
+ node = self._validate(p)
+ return self._make_position(node._left)
+
+ def right(self, p):
+ """Return the Position of p's right child (or None if no right child)."""
+ node = self._validate(p)
+ return self._make_position(node._right)
+
+ def num_children(self, p):
+ """Return the number of children of Position p."""
+ node = self._validate(p)
+ count = 0
+ if node._left is not None: # left child exists
+ count += 1
+ if node._right is not None: # right child exists
+ count += 1
+ return count
+
+ # -------------------------- nonpublic mutators --------------------------
+ def _add_root(self, e):
+ """Place element e at the root of an empty tree and return new Position.
+ Raise ValueError if tree nonempty.
+ """
+ if self._root is not None:
+ raise ValueError('Root exists')
+ self._size = 1
+ self._root = self._Node(e)
+ return self._make_position(self._root)
+
+ def _add_left(self, p, e):
+ """Create a new left child for Position p, storing element e.
+ Return the Position of new node.
+ Raise ValueError if Position p is invalid or p already has a left child.
+ """
+ node = self._validate(p)
+ if node._left is not None:
+ raise ValueError('Left child exists')
+ self._size += 1
+ node._left = self._Node(e, node) # node is its parent
+ return self._make_position(node._left)
+
+ def _add_right(self, p, e):
+ """Create a new right child for Position p, storing element e.
+ Return the Position of new node.
+ Raise ValueError if Position p is invalid or p already has a right child.
+ """
+ node = self._validate(p)
+ if node._right is not None:
+ raise ValueError('Right child exists')
+ self._size += 1
+ node._right = self._Node(e, node) # node is its parent
+ return self._make_position(node._right)
+
+ def _replace(self, p, e):
+ """Replace the element at position p with e, and return old element."""
+ node = self._validate(p)
+ old = node._element
+ node._element = e
+ return old
+
+ def _delete(self, p):
+ """Delete the node at Position p, and replace it with its child, if any.
+ Return the element that had been stored at Position p.
+ Raise ValueError if Position p is invalid or p has two children.
+ """
+ node = self._validate(p)
+ if self.num_children(p) == 2:
+ raise ValueError('Position has two children')
+ child = node._left if node._left else node._right # might be None
+ if child is not None:
+ child._parent = node._parent # child's grandparent becomes parent
+ if node is self._root:
+ self._root = child # child becomes root
+ else:
+ parent = node._parent
+ if node is parent._left:
+ parent._left = child
+ else:
+ parent._right = child
+ self._size -= 1
+ node._parent = node # convention for deprecated node
+ return node._element
+
+ def _attach(self, p, t1, t2):
+ """Attach trees t1 and t2, respectively, as the left and right subtrees of the external Position p.
+ As a side effect, set t1 and t2 to empty.
+ Raise TypeError if trees t1 and t2 do not match type of this tree.
+ Raise ValueError if Position p is invalid or not external.
+ """
+ node = self._validate(p)
+ if not self.is_leaf(p):
+ raise ValueError('position must be leaf')
+ if not type(self) is type(t1) is type(t2): # all 3 trees must be same type
+ raise TypeError('Tree types must match')
+ self._size += len(t1) + len(t2)
+ if not t1.is_empty(): # attached t1 as left subtree of node
+ t1._root._parent = node
+ node._left = t1._root
+ t1._root = None # set t1 instance to empty
+ t1._size = 0
+ if not t2.is_empty(): # attached t2 as right subtree of node
+ t2._root._parent = node
+ node._right = t2._root
+ t2._root = None # set t2 instance to empty
+ t2._size = 0
diff --git a/ch08/traversal_examples.py b/ch08/traversal_examples.py
index d8fa674..8b66cc5 100644
--- a/ch08/traversal_examples.py
+++ b/ch08/traversal_examples.py
@@ -1,63 +1,47 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
def toc_plain(T):
- for p in T.preorder():
- print(p.element())
+ for p in T.preorder():
+ print(p.element())
+
def toc_indent_bad(T):
- for p in T.preorder():
- print(2*T.depth(p)*' ' + str(p.element())) # beware of inefficiency
+ for p in T.preorder():
+ print(2 * T.depth(p) * ' ' + str(p.element())) # beware of inefficiency
+
def preorder_indent(T, p, d):
- """Print preorder representation of subtree of T rooted at p at depth d."""
- print(2*d*' ' + str(p.element())) # use depth for indentation
- for c in T.children(p):
- preorder_indent(T, c, d+1) # child depth is d+1
+ """Print preorder representation of subtree of T rooted at p at depth d."""
+ print(2 * d * ' ' + str(p.element())) # use depth for indentation
+ for c in T.children(p):
+ preorder_indent(T, c, d + 1) # child depth is d+1
+
def preorder_label(T, p, d, path):
- """Print labeled representation of subtree of T rooted at p at depth d."""
- label = '.'.join(str(j+1) for j in path) # displayed labels are one-indexed
- print(2*d*' ' + label, p.element())
- path.append(0) # path entries are zero-indexed
- for c in T.children(p):
- preorder_label(T, c, d+1, path) # child depth is d+1
- path[-1] += 1
- path.pop()
+ """Print labeled representation of subtree of T rooted at p at depth d."""
+ label = '.'.join(str(j + 1) for j in path) # displayed labels are one-indexed
+ print(2 * d * ' ' + label, p.element())
+ path.append(0) # path entries are zero-indexed
+ for c in T.children(p):
+ preorder_label(T, c, d + 1, path) # child depth is d+1
+ path[-1] += 1
+ path.pop()
+
def parenthesize(T, p):
- """Print parenthesized representation of subtree of T rooted at p."""
- print(p.element(), end='') # use of end avoids trailing newline
- if not T.is_leaf(p):
- first_time = True
- for c in T.children(p):
- sep = ' (' if first_time else ', ' # determine proper separator
- print(sep, end='')
- first_time = False # any future passes will not be the first
- parenthesize(T, c) # recur on child
- print(')', end='') # include closing parenthesis
+ """Print parenthesized representation of subtree of T rooted at p."""
+ print(p.element(), end='') # use of end avoids trailing newline
+ if not T.is_leaf(p):
+ first_time = True
+ for c in T.children(p):
+ sep = ' (' if first_time else ', ' # determine proper separator
+ print(sep, end='')
+ first_time = False # any future passes will not be the first
+ parenthesize(T, c) # recur on child
+ print(')', end='') # include closing parenthesis
+
def disk_space(T, p):
- """Return total disk space for subtree of T rooted at p."""
- subtotal = p.element().space() # space used at position p
- for c in T.children(p):
- subtotal += disk_space(T, c) # add child's space to subtotal
- return subtotal
+ """Return total disk space for subtree of T rooted at p."""
+ subtotal = p.element().space() # space used at position p
+ for c in T.children(p):
+ subtotal += disk_space(T, c) # add child's space to subtotal
+ return subtotal
diff --git a/ch08/tree.py b/ch08/tree.py
index f0f3041..21de665 100644
--- a/ch08/tree.py
+++ b/ch08/tree.py
@@ -1,154 +1,132 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
from ..ch07.linked_queue import LinkedQueue
import collections
+
class Tree:
- """Abstract base class representing a tree structure."""
-
- #------------------------------- nested Position class -------------------------------
- class Position:
- """An abstraction representing the location of a single element within a tree.
-
- Note that two position instaces may represent the same inherent location in a tree.
- Therefore, users should always rely on syntax 'p == q' rather than 'p is q' when testing
- equivalence of positions.
- """
-
- def element(self):
- """Return the element stored at this Position."""
- raise NotImplementedError('must be implemented by subclass')
-
- def __eq__(self, other):
- """Return True if other Position represents the same location."""
- raise NotImplementedError('must be implemented by subclass')
-
- def __ne__(self, other):
- """Return True if other does not represent the same location."""
- return not (self == other) # opposite of __eq__
-
- # ---------- abstract methods that concrete subclass must support ----------
- def root(self):
- """Return Position representing the tree's root (or None if empty)."""
- raise NotImplementedError('must be implemented by subclass')
-
- def parent(self, p):
- """Return Position representing p's parent (or None if p is root)."""
- raise NotImplementedError('must be implemented by subclass')
-
- def num_children(self, p):
- """Return the number of children that Position p has."""
- raise NotImplementedError('must be implemented by subclass')
-
- def children(self, p):
- """Generate an iteration of Positions representing p's children."""
- raise NotImplementedError('must be implemented by subclass')
-
- def __len__(self):
- """Return the total number of elements in the tree."""
- raise NotImplementedError('must be implemented by subclass')
-
- # ---------- concrete methods implemented in this class ----------
- def is_root(self, p):
- """Return True if Position p represents the root of the tree."""
- return self.root() == p
-
- def is_leaf(self, p):
- """Return True if Position p does not have any children."""
- return self.num_children(p) == 0
-
- def is_empty(self):
- """Return True if the tree is empty."""
- return len(self) == 0
-
- def depth(self, p):
- """Return the number of levels separating Position p from the root."""
- if self.is_root(p):
- return 0
- else:
- return 1 + self.depth(self.parent(p))
-
- def _height1(self): # works, but O(n^2) worst-case time
- """Return the height of the tree."""
- return max(self.depth(p) for p in self.positions() if self.is_leaf(p))
-
- def _height2(self, p): # time is linear in size of subtree
- """Return the height of the subtree rooted at Position p."""
- if self.is_leaf(p):
- return 0
- else:
- return 1 + max(self._height2(c) for c in self.children(p))
-
- def height(self, p=None):
- """Return the height of the subtree rooted at Position p.
-
- If p is None, return the height of the entire tree.
- """
- if p is None:
- p = self.root()
- return self._height2(p) # start _height2 recursion
-
- def __iter__(self):
- """Generate an iteration of the tree's elements."""
- for p in self.positions(): # use same order as positions()
- yield p.element() # but yield each element
-
- def positions(self):
- """Generate an iteration of the tree's positions."""
- return self.preorder() # return entire preorder iteration
-
- def preorder(self):
- """Generate a preorder iteration of positions in the tree."""
- if not self.is_empty():
- for p in self._subtree_preorder(self.root()): # start recursion
- yield p
-
- def _subtree_preorder(self, p):
- """Generate a preorder iteration of positions in subtree rooted at p."""
- yield p # visit p before its subtrees
- for c in self.children(p): # for each child c
- for other in self._subtree_preorder(c): # do preorder of c's subtree
- yield other # yielding each to our caller
-
- def postorder(self):
- """Generate a postorder iteration of positions in the tree."""
- if not self.is_empty():
- for p in self._subtree_postorder(self.root()): # start recursion
- yield p
-
- def _subtree_postorder(self, p):
- """Generate a postorder iteration of positions in subtree rooted at p."""
- for c in self.children(p): # for each child c
- for other in self._subtree_postorder(c): # do postorder of c's subtree
- yield other # yielding each to our caller
- yield p # visit p after its subtrees
-
- def breadthfirst(self):
- """Generate a breadth-first iteration of the positions of the tree."""
- if not self.is_empty():
- fringe = LinkedQueue() # known positions not yet yielded
- fringe.enqueue(self.root()) # starting with the root
- while not fringe.is_empty():
- p = fringe.dequeue() # remove from front of the queue
- yield p # report this position
- for c in self.children(p):
- fringe.enqueue(c) # add children to back of queue
+ """Abstract base class representing a tree structure."""
+
+ # ------------------------------- nested Position class -------------------------------
+ class Position:
+ """An abstraction representing the location of a single element within a tree.
+ Note that two position instaces may represent the same inherent location in a tree.
+ Therefore, users should always rely on syntax 'p == q' rather than 'p is q' when testing
+ equivalence of positions.
+ """
+
+ def element(self):
+ """Return the element stored at this Position."""
+ raise NotImplementedError('must be implemented by subclass')
+
+ def __eq__(self, other):
+ """Return True if other Position represents the same location."""
+ raise NotImplementedError('must be implemented by subclass')
+
+ def __ne__(self, other):
+ """Return True if other does not represent the same location."""
+ return not (self == other) # opposite of __eq__
+
+ # ---------- abstract methods that concrete subclass must support ----------
+ def root(self):
+ """Return Position representing the tree's root (or None if empty)."""
+ raise NotImplementedError('must be implemented by subclass')
+
+ def parent(self, p):
+ """Return Position representing p's parent (or None if p is root)."""
+ raise NotImplementedError('must be implemented by subclass')
+
+ def num_children(self, p):
+ """Return the number of children that Position p has."""
+ raise NotImplementedError('must be implemented by subclass')
+
+ def children(self, p):
+ """Generate an iteration of Positions representing p's children."""
+ raise NotImplementedError('must be implemented by subclass')
+
+ def __len__(self):
+ """Return the total number of elements in the tree."""
+ raise NotImplementedError('must be implemented by subclass')
+
+ # ---------- concrete methods implemented in this class ----------
+ def is_root(self, p):
+ """Return True if Position p represents the root of the tree."""
+ return self.root() == p
+
+ def is_leaf(self, p):
+ """Return True if Position p does not have any children."""
+ return self.num_children(p) == 0
+
+ def is_empty(self):
+ """Return True if the tree is empty."""
+ return len(self) == 0
+
+ def depth(self, p):
+ """Return the number of levels separating Position p from the root."""
+ if self.is_root(p):
+ return 0
+ else:
+ return 1 + self.depth(self.parent(p))
+
+ def _height1(self): # works, but O(n^2) worst-case time
+ """Return the height of the tree."""
+ return max(self.depth(p) for p in self.positions() if self.is_leaf(p))
+
+ def _height2(self, p): # time is linear in size of subtree
+ """Return the height of the subtree rooted at Position p."""
+ if self.is_leaf(p):
+ return 0
+ else:
+ return 1 + max(self._height2(c) for c in self.children(p))
+
+ def height(self, p=None):
+ """Return the height of the subtree rooted at Position p.
+ If p is None, return the height of the entire tree.
+ """
+ if p is None:
+ p = self.root()
+ return self._height2(p) # start _height2 recursion
+
+ def __iter__(self):
+ """Generate an iteration of the tree's elements."""
+ for p in self.positions(): # use same order as positions()
+ yield p.element() # but yield each element
+
+ def positions(self):
+ """Generate an iteration of the tree's positions."""
+ return self.preorder() # return entire preorder iteration
+
+ def preorder(self):
+ """Generate a preorder iteration of positions in the tree."""
+ if not self.is_empty():
+ for p in self._subtree_preorder(self.root()): # start recursion
+ yield p
+
+ def _subtree_preorder(self, p):
+ """Generate a preorder iteration of positions in subtree rooted at p."""
+ yield p # visit p before its subtrees
+ for c in self.children(p): # for each child c
+ for other in self._subtree_preorder(c): # do preorder of c's subtree
+ yield other # yielding each to our caller
+
+ def postorder(self):
+ """Generate a postorder iteration of positions in the tree."""
+ if not self.is_empty():
+ for p in self._subtree_postorder(self.root()): # start recursion
+ yield p
+
+ def _subtree_postorder(self, p):
+ """Generate a postorder iteration of positions in subtree rooted at p."""
+ for c in self.children(p): # for each child c
+ for other in self._subtree_postorder(c): # do postorder of c's subtree
+ yield other # yielding each to our caller
+ yield p # visit p after its subtrees
+
+ def breadthfirst(self):
+ """Generate a breadth-first iteration of the positions of the tree."""
+ if not self.is_empty():
+ fringe = LinkedQueue() # known positions not yet yielded
+ fringe.enqueue(self.root()) # starting with the root
+ while not fringe.is_empty():
+ p = fringe.dequeue() # remove from front of the queue
+ yield p # report this position
+ for c in self.children(p):
+ fringe.enqueue(c) # add children to back of queue
diff --git a/ch09/adaptable_heap_priority_queue.py b/ch09/adaptable_heap_priority_queue.py
index 76c3ff4..6e8e59d 100644
--- a/ch09/adaptable_heap_priority_queue.py
+++ b/ch09/adaptable_heap_priority_queue.py
@@ -1,77 +1,57 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
from .heap_priority_queue import HeapPriorityQueue
-class AdaptableHeapPriorityQueue(HeapPriorityQueue):
- """A locator-based priority queue implemented with a binary heap."""
-
- #------------------------------ nested Locator class ------------------------------
- class Locator(HeapPriorityQueue._Item):
- """Token for locating an entry of the priority queue."""
- __slots__ = '_index' # add index as additional field
-
- def __init__(self, k, v, j):
- super().__init__(k,v)
- self._index = j
-
- #------------------------------ nonpublic behaviors ------------------------------
- # override swap to record new indices
- def _swap(self, i, j):
- super()._swap(i,j) # perform the swap
- self._data[i]._index = i # reset locator index (post-swap)
- self._data[j]._index = j # reset locator index (post-swap)
- def _bubble(self, j):
- if j > 0 and self._data[j] < self._data[self._parent(j)]:
- self._upheap(j)
- else:
- self._downheap(j)
-
- #------------------------------ public behaviors ------------------------------
- def add(self, key, value):
- """Add a key-value pair."""
- token = self.Locator(key, value, len(self._data)) # initiaize locator index
- self._data.append(token)
- self._upheap(len(self._data) - 1)
- return token
-
- def update(self, loc, newkey, newval):
- """Update the key and value for the entry identified by Locator loc."""
- j = loc._index
- if not (0 <= j < len(self) and self._data[j] is loc):
- raise ValueError('Invalid locator')
- loc._key = newkey
- loc._value = newval
- self._bubble(j)
-
- def remove(self, loc):
- """Remove and return the (k,v) pair identified by Locator loc."""
- j = loc._index
- if not (0 <= j < len(self) and self._data[j] is loc):
- raise ValueError('Invalid locator')
- if j == len(self) - 1: # item at last position
- self._data.pop() # just remove it
- else:
- self._swap(j, len(self)-1) # swap item to the last position
- self._data.pop() # remove it from the list
- self._bubble(j) # fix item displaced by the swap
- return (loc._key, loc._value)
+class AdaptableHeapPriorityQueue(HeapPriorityQueue):
+ """A locator-based priority queue implemented with a binary heap."""
+
+ # ------------------------------ nested Locator class ------------------------------
+ class Locator(HeapPriorityQueue._Item):
+ """Token for locating an entry of the priority queue."""
+ __slots__ = '_index' # add index as additional field
+
+ def __init__(self, k, v, j):
+ super().__init__(k, v)
+ self._index = j
+
+ # ------------------------------ nonpublic behaviors ------------------------------
+ # override swap to record new indices
+ def _swap(self, i, j):
+ super()._swap(i, j) # perform the swap
+ self._data[i]._index = i # reset locator index (post-swap)
+ self._data[j]._index = j # reset locator index (post-swap)
+
+ def _bubble(self, j):
+ if j > 0 and self._data[j] < self._data[self._parent(j)]:
+ self._upheap(j)
+ else:
+ self._downheap(j)
+
+ # ------------------------------ public behaviors ------------------------------
+ def add(self, key, value):
+ """Add a key-value pair."""
+ token = self.Locator(key, value, len(self._data)) # initiaize locator index
+ self._data.append(token)
+ self._upheap(len(self._data) - 1)
+ return token
+
+ def update(self, loc, newkey, newval):
+ """Update the key and value for the entry identified by Locator loc."""
+ j = loc._index
+ if not (0 <= j < len(self) and self._data[j] is loc):
+ raise ValueError('Invalid locator')
+ loc._key = newkey
+ loc._value = newval
+ self._bubble(j)
+
+ def remove(self, loc):
+ """Remove and return the (k,v) pair identified by Locator loc."""
+ j = loc._index
+ if not (0 <= j < len(self) and self._data[j] is loc):
+ raise ValueError('Invalid locator')
+ if j == len(self) - 1: # item at last position
+ self._data.pop() # just remove it
+ else:
+ self._swap(j, len(self) - 1) # swap item to the last position
+ self._data.pop() # remove it from the list
+ self._bubble(j) # fix item displaced by the swap
+ return (loc._key, loc._value)
diff --git a/ch09/heap_priority_queue.py b/ch09/heap_priority_queue.py
index 7694a20..da51723 100644
--- a/ch09/heap_priority_queue.py
+++ b/ch09/heap_priority_queue.py
@@ -1,100 +1,78 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
from .priority_queue_base import PriorityQueueBase
from ..exceptions import Empty
-class HeapPriorityQueue(PriorityQueueBase): # base class defines _Item
- """A min-oriented priority queue implemented with a binary heap."""
- #------------------------------ nonpublic behaviors ------------------------------
- def _parent(self, j):
- return (j-1) // 2
+class HeapPriorityQueue(PriorityQueueBase): # base class defines _Item
+ """A min-oriented priority queue implemented with a binary heap."""
+
+ # ------------------------------ nonpublic behaviors ------------------------------
+ def _parent(self, j):
+ return (j - 1) // 2
+
+ def _left(self, j):
+ return 2 * j + 1
+
+ def _right(self, j):
+ return 2 * j + 2
+
+ def _has_left(self, j):
+ return self._left(j) < len(self._data) # index beyond end of list?
- def _left(self, j):
- return 2*j + 1
-
- def _right(self, j):
- return 2*j + 2
+ def _has_right(self, j):
+ return self._right(j) < len(self._data) # index beyond end of list?
- def _has_left(self, j):
- return self._left(j) < len(self._data) # index beyond end of list?
-
- def _has_right(self, j):
- return self._right(j) < len(self._data) # index beyond end of list?
-
- def _swap(self, i, j):
- """Swap the elements at indices i and j of array."""
- self._data[i], self._data[j] = self._data[j], self._data[i]
+ def _swap(self, i, j):
+ """Swap the elements at indices i and j of array."""
+ self._data[i], self._data[j] = self._data[j], self._data[i]
- def _upheap(self, j):
- parent = self._parent(j)
- if j > 0 and self._data[j] < self._data[parent]:
- self._swap(j, parent)
- self._upheap(parent) # recur at position of parent
-
- def _downheap(self, j):
- if self._has_left(j):
- left = self._left(j)
- small_child = left # although right may be smaller
- if self._has_right(j):
- right = self._right(j)
- if self._data[right] < self._data[left]:
- small_child = right
- if self._data[small_child] < self._data[j]:
- self._swap(j, small_child)
- self._downheap(small_child) # recur at position of small child
+ def _upheap(self, j):
+ parent = self._parent(j)
+ if j > 0 and self._data[j] < self._data[parent]:
+ self._swap(j, parent)
+ self._upheap(parent) # recur at position of parent
- #------------------------------ public behaviors ------------------------------
- def __init__(self):
- """Create a new empty Priority Queue."""
- self._data = []
+ def _downheap(self, j):
+ if self._has_left(j):
+ left = self._left(j)
+ small_child = left # although right may be smaller
+ if self._has_right(j):
+ right = self._right(j)
+ if self._data[right] < self._data[left]:
+ small_child = right
+ if self._data[small_child] < self._data[j]:
+ self._swap(j, small_child)
+ self._downheap(small_child) # recur at position of small child
- def __len__(self):
- """Return the number of items in the priority queue."""
- return len(self._data)
+ # ------------------------------ public behaviors ------------------------------
+ def __init__(self):
+ """Create a new empty Priority Queue."""
+ self._data = []
- def add(self, key, value):
- """Add a key-value pair to the priority queue."""
- self._data.append(self._Item(key, value))
- self._upheap(len(self._data) - 1) # upheap newly added position
-
- def min(self):
- """Return but do not remove (k,v) tuple with minimum key.
+ def __len__(self):
+ """Return the number of items in the priority queue."""
+ return len(self._data)
- Raise Empty exception if empty.
- """
- if self.is_empty():
- raise Empty('Priority queue is empty.')
- item = self._data[0]
- return (item._key, item._value)
+ def add(self, key, value):
+ """Add a key-value pair to the priority queue."""
+ self._data.append(self._Item(key, value))
+ self._upheap(len(self._data) - 1) # upheap newly added position
- def remove_min(self):
- """Remove and return (k,v) tuple with minimum key.
+ def min(self):
+ """Return but do not remove (k,v) tuple with minimum key.
+ Raise Empty exception if empty.
+ """
+ if self.is_empty():
+ raise Empty('Priority queue is empty.')
+ item = self._data[0]
+ return (item._key, item._value)
- Raise Empty exception if empty.
- """
- if self.is_empty():
- raise Empty('Priority queue is empty.')
- self._swap(0, len(self._data) - 1) # put minimum item at the end
- item = self._data.pop() # and remove it from the list;
- self._downheap(0) # then fix new root
- return (item._key, item._value)
+ def remove_min(self):
+ """Remove and return (k,v) tuple with minimum key.
+ Raise Empty exception if empty.
+ """
+ if self.is_empty():
+ raise Empty('Priority queue is empty.')
+ self._swap(0, len(self._data) - 1) # put minimum item at the end
+ item = self._data.pop() # and remove it from the list;
+ self._downheap(0) # then fix new root
+ return (item._key, item._value)
diff --git a/ch09/priority_queue_base.py b/ch09/priority_queue_base.py
index f445e1f..296dd4f 100644
--- a/ch09/priority_queue_base.py
+++ b/ch09/priority_queue_base.py
@@ -1,67 +1,45 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
from ..exceptions import Empty
-class PriorityQueueBase:
- """Abstract base class for a priority queue."""
-
- #------------------------------ nested _Item class ------------------------------
- class _Item:
- """Lightweight composite to store priority queue items."""
- __slots__ = '_key', '_value'
-
- def __init__(self, k, v):
- self._key = k
- self._value = v
-
- def __lt__(self, other):
- return self._key < other._key # compare items based on their keys
-
- def __repr__(self):
- return '({0},{1})'.format(self._key, self._value)
-
- #------------------------------ public behaviors ------------------------------
- def is_empty(self): # concrete method assuming abstract len
- """Return True if the priority queue is empty."""
- return len(self) == 0
- def __len__(self):
- """Return the number of items in the priority queue."""
- raise NotImplementedError('must be implemented by subclass')
-
- def add(self, key, value):
- """Add a key-value pair."""
- raise NotImplementedError('must be implemented by subclass')
-
- def min(self):
- """Return but do not remove (k,v) tuple with minimum key.
-
- Raise Empty exception if empty.
- """
- raise NotImplementedError('must be implemented by subclass')
-
- def remove_min(self):
- """Remove and return (k,v) tuple with minimum key.
-
- Raise Empty exception if empty.
- """
- raise NotImplementedError('must be implemented by subclass')
+class PriorityQueueBase:
+ """Abstract base class for a priority queue."""
+
+ # ------------------------------ nested _Item class ------------------------------
+ class _Item:
+ """Lightweight composite to store priority queue items."""
+ __slots__ = '_key', '_value'
+
+ def __init__(self, k, v):
+ self._key = k
+ self._value = v
+
+ def __lt__(self, other):
+ return self._key < other._key # compare items based on their keys
+
+ def __repr__(self):
+ return '({0},{1})'.format(self._key, self._value)
+
+ # ------------------------------ public behaviors ------------------------------
+ def is_empty(self): # concrete method assuming abstract len
+ """Return True if the priority queue is empty."""
+ return len(self) == 0
+
+ def __len__(self):
+ """Return the number of items in the priority queue."""
+ raise NotImplementedError('must be implemented by subclass')
+
+ def add(self, key, value):
+ """Add a key-value pair."""
+ raise NotImplementedError('must be implemented by subclass')
+
+ def min(self):
+ """Return but do not remove (k,v) tuple with minimum key.
+ Raise Empty exception if empty.
+ """
+ raise NotImplementedError('must be implemented by subclass')
+
+ def remove_min(self):
+ """Remove and return (k,v) tuple with minimum key.
+ Raise Empty exception if empty.
+ """
+ raise NotImplementedError('must be implemented by subclass')
diff --git a/ch09/sorted_priority_queue.py b/ch09/sorted_priority_queue.py
index 29909de..a8c7f73 100644
--- a/ch09/sorted_priority_queue.py
+++ b/ch09/sorted_priority_queue.py
@@ -1,68 +1,46 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
from .priority_queue_base import PriorityQueueBase
from ..ch07.positional_list import PositionalList
from ..exceptions import Empty
-class SortedPriorityQueue(PriorityQueueBase): # base class defines _Item
- """A min-oriented priority queue implemented with a sorted list."""
-
- #------------------------------ public behaviors ------------------------------
- def __init__(self):
- """Create a new empty Priority Queue."""
- self._data = PositionalList()
-
- def __len__(self):
- """Return the number of items in the priority queue."""
- return len(self._data)
-
- def add(self, key, value):
- """Add a key-value pair."""
- newest = self._Item(key, value) # make new item instance
- walk = self._data.last() # walk backward looking for smaller key
- while walk is not None and newest < walk.element():
- walk = self._data.before(walk)
- if walk is None:
- self._data.add_first(newest) # new key is smallest
- else:
- self._data.add_after(walk, newest) # newest goes after walk
-
- def min(self):
- """Return but do not remove (k,v) tuple with minimum key.
-
- Raise Empty exception if empty.
- """
- if self.is_empty():
- raise Empty('Priority queue is empty.')
- p = self._data.first()
- item = p.element()
- return (item._key, item._value)
-
- def remove_min(self):
- """Remove and return (k,v) tuple with minimum key.
- Raise Empty exception if empty.
- """
- if self.is_empty():
- raise Empty('Priority queue is empty.')
- item = self._data.delete(self._data.first())
- return (item._key, item._value)
+class SortedPriorityQueue(PriorityQueueBase): # base class defines _Item
+ """A min-oriented priority queue implemented with a sorted list."""
+
+ # ------------------------------ public behaviors ------------------------------
+ def __init__(self):
+ """Create a new empty Priority Queue."""
+ self._data = PositionalList()
+
+ def __len__(self):
+ """Return the number of items in the priority queue."""
+ return len(self._data)
+
+ def add(self, key, value):
+ """Add a key-value pair."""
+ newest = self._Item(key, value) # make new item instance
+ walk = self._data.last() # walk backward looking for smaller key
+ while walk is not None and newest < walk.element():
+ walk = self._data.before(walk)
+ if walk is None:
+ self._data.add_first(newest) # new key is smallest
+ else:
+ self._data.add_after(walk, newest) # newest goes after walk
+
+ def min(self):
+ """Return but do not remove (k,v) tuple with minimum key.
+ Raise Empty exception if empty.
+ """
+ if self.is_empty():
+ raise Empty('Priority queue is empty.')
+ p = self._data.first()
+ item = p.element()
+ return (item._key, item._value)
+
+ def remove_min(self):
+ """Remove and return (k,v) tuple with minimum key.
+ Raise Empty exception if empty.
+ """
+ if self.is_empty():
+ raise Empty('Priority queue is empty.')
+ item = self._data.delete(self._data.first())
+ return (item._key, item._value)
diff --git a/ch09/unsorted_priority_queue.py b/ch09/unsorted_priority_queue.py
index 96445bf..229c987 100644
--- a/ch09/unsorted_priority_queue.py
+++ b/ch09/unsorted_priority_queue.py
@@ -1,71 +1,49 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
from .priority_queue_base import PriorityQueueBase
from ..ch07.positional_list import PositionalList
from ..exceptions import Empty
-class UnsortedPriorityQueue(PriorityQueueBase): # base class defines _Item
- """A min-oriented priority queue implemented with an unsorted list."""
-
- #----------------------------- nonpublic behavior -----------------------------
- def _find_min(self):
- """Return Position of item with minimum key."""
- if self.is_empty(): # is_empty inherited from base class
- raise Empty('Priority queue is empty')
- small = self._data.first()
- walk = self._data.after(small)
- while walk is not None:
- if walk.element() < small.element():
- small = walk
- walk = self._data.after(walk)
- return small
-
- #------------------------------ public behaviors ------------------------------
- def __init__(self):
- """Create a new empty Priority Queue."""
- self._data = PositionalList()
-
- def __len__(self):
- """Return the number of items in the priority queue."""
- return len(self._data)
-
- def add(self, key, value):
- """Add a key-value pair."""
- self._data.add_last(self._Item(key, value))
-
- def min(self):
- """Return but do not remove (k,v) tuple with minimum key.
-
- Raise Empty exception if empty.
- """
- p = self._find_min()
- item = p.element()
- return (item._key, item._value)
-
- def remove_min(self):
- """Remove and return (k,v) tuple with minimum key.
- Raise Empty exception if empty.
- """
- p = self._find_min()
- item = self._data.delete(p)
- return (item._key, item._value)
+class UnsortedPriorityQueue(PriorityQueueBase): # base class defines _Item
+ """A min-oriented priority queue implemented with an unsorted list."""
+
+ # ----------------------------- nonpublic behavior -----------------------------
+ def _find_min(self):
+ """Return Position of item with minimum key."""
+ if self.is_empty(): # is_empty inherited from base class
+ raise Empty('Priority queue is empty')
+ small = self._data.first()
+ walk = self._data.after(small)
+ while walk is not None:
+ if walk.element() < small.element():
+ small = walk
+ walk = self._data.after(walk)
+ return small
+
+ # ------------------------------ public behaviors ------------------------------
+ def __init__(self):
+ """Create a new empty Priority Queue."""
+ self._data = PositionalList()
+
+ def __len__(self):
+ """Return the number of items in the priority queue."""
+ return len(self._data)
+
+ def add(self, key, value):
+ """Add a key-value pair."""
+ self._data.add_last(self._Item(key, value))
+
+ def min(self):
+ """Return but do not remove (k,v) tuple with minimum key.
+ Raise Empty exception if empty.
+ """
+ p = self._find_min()
+ item = p.element()
+ return (item._key, item._value)
+
+ def remove_min(self):
+ """Remove and return (k,v) tuple with minimum key.
+ Raise Empty exception if empty.
+ """
+ p = self._find_min()
+ item = self._data.delete(p)
+ return (item._key, item._value)
diff --git a/ch10/__init__.py b/ch10/__init__.py
index dee95b4..a32d5dc 100644
--- a/ch10/__init__.py
+++ b/ch10/__init__.py
@@ -1 +1,2 @@
-__all__ = ['chain_hash_map', 'cost_performance', 'multi_map', 'probe_hash_map', 'sorted_table_map', 'unsorted_table_map']
+__all__ = ['chain_hash_map', 'cost_performance', 'multi_map', 'probe_hash_map', 'sorted_table_map',
+ 'unsorted_table_map']
diff --git a/ch10/chain_hash_map.py b/ch10/chain_hash_map.py
index c73c14e..c527463 100644
--- a/ch10/chain_hash_map.py
+++ b/ch10/chain_hash_map.py
@@ -1,52 +1,32 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
from .hash_map_base import HashMapBase
from .unsorted_table_map import UnsortedTableMap
+
class ChainHashMap(HashMapBase):
- """Hash map implemented with separate chaining for collision resolution."""
+ """Hash map implemented with separate chaining for collision resolution."""
- def _bucket_getitem(self, j, k):
- bucket = self._table[j]
- if bucket is None:
- raise KeyError('Key Error: ' + repr(k)) # no match found
- return bucket[k] # may raise KeyError
+ def _bucket_getitem(self, j, k):
+ bucket = self._table[j]
+ if bucket is None:
+ raise KeyError('Key Error: ' + repr(k)) # no match found
+ return bucket[k] # may raise KeyError
- def _bucket_setitem(self, j, k, v):
- if self._table[j] is None:
- self._table[j] = UnsortedTableMap() # bucket is new to the table
- oldsize = len(self._table[j])
- self._table[j][k] = v
- if len(self._table[j]) > oldsize: # key was new to the table
- self._n += 1 # increase overall map size
+ def _bucket_setitem(self, j, k, v):
+ if self._table[j] is None:
+ self._table[j] = UnsortedTableMap() # bucket is new to the table
+ oldsize = len(self._table[j])
+ self._table[j][k] = v
+ if len(self._table[j]) > oldsize: # key was new to the table
+ self._n += 1 # increase overall map size
- def _bucket_delitem(self, j, k):
- bucket = self._table[j]
- if bucket is None:
- raise KeyError('Key Error: ' + repr(k)) # no match found
- del bucket[k] # may raise KeyError
+ def _bucket_delitem(self, j, k):
+ bucket = self._table[j]
+ if bucket is None:
+ raise KeyError('Key Error: ' + repr(k)) # no match found
+ del bucket[k] # may raise KeyError
- def __iter__(self):
- for bucket in self._table:
- if bucket is not None: # a nonempty slot
- for key in bucket:
- yield key
+ def __iter__(self):
+ for bucket in self._table:
+ if bucket is not None: # a nonempty slot
+ for key in bucket:
+ yield key
diff --git a/ch10/cost_performance.py b/ch10/cost_performance.py
index 40c32a1..394c02a 100644
--- a/ch10/cost_performance.py
+++ b/ch10/cost_performance.py
@@ -1,49 +1,28 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
from .sorted_table_map import SortedTableMap
-class CostPerformanceDatabase:
- """Maintain a database of maximal (cost,performance) pairs."""
- def __init__(self):
- """Create an empty database."""
- self._M = SortedTableMap() # or a more efficient sorted map
+class CostPerformanceDatabase:
+ """Maintain a database of maximal (cost,performance) pairs."""
- def best(self, c):
- """Return (cost,performance) pair with largest cost not exceeding c.
+ def __init__(self):
+ """Create an empty database."""
+ self._M = SortedTableMap() # or a more efficient sorted map
- Return None if there is no such pair.
- """
- return self._M.find_le(c)
+ def best(self, c):
+ """Return (cost,performance) pair with largest cost not exceeding c.
+ Return None if there is no such pair.
+ """
+ return self._M.find_le(c)
- def add(self, c, p):
- """Add new entry with cost c and performance p."""
- # determine if (c,p) is dominated by an existing pair
- other = self._M.find_le(c) # other is at least as cheap as c
- if other is not None and other[1] >= p: # if its performance is as good,
- return # (c,p) is dominated, so ignore
- self._M[c] = p # else, add (c,p) to database
- # and now remove any pairs that are dominated by (c,p)
- other = self._M.find_gt(c) # other more expensive than c
- while other is not None and other[1] <= p:
- del self._M[other[0]]
- other = self._M.find_gt(c)
+ def add(self, c, p):
+ """Add new entry with cost c and performance p."""
+ # determine if (c,p) is dominated by an existing pair
+ other = self._M.find_le(c) # other is at least as cheap as c
+ if other is not None and other[1] >= p: # if its performance is as good,
+ return # (c,p) is dominated, so ignore
+ self._M[c] = p # else, add (c,p) to database
+ # and now remove any pairs that are dominated by (c,p)
+ other = self._M.find_gt(c) # other more expensive than c
+ while other is not None and other[1] <= p:
+ del self._M[other[0]]
+ other = self._M.find_gt(c)
diff --git a/ch10/hash_map_base.py b/ch10/hash_map_base.py
index 427a614..b0349d3 100644
--- a/ch10/hash_map_base.py
+++ b/ch10/hash_map_base.py
@@ -1,71 +1,49 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
from .map_base import MapBase
from collections import MutableMapping
-from random import randrange # used to pick MAD parameters
-
-class HashMapBase(MapBase):
- """Abstract base class for map using hash-table with MAD compression.
+from random import randrange # used to pick MAD parameters
- Keys must be hashable and non-None.
- """
- def __init__(self, cap=11, p=109345121):
- """Create an empty hash-table map.
-
- cap initial table size (default 11)
- p positive prime used for MAD (default 109345121)
+class HashMapBase(MapBase):
+ """Abstract base class for map using hash-table with MAD compression.
+ Keys must be hashable and non-None.
"""
- self._table = cap * [ None ]
- self._n = 0 # number of entries in the map
- self._prime = p # prime for MAD compression
- self._scale = 1 + randrange(p-1) # scale from 1 to p-1 for MAD
- self._shift = randrange(p) # shift from 0 to p-1 for MAD
-
- def _hash_function(self, k):
- return (hash(k)*self._scale + self._shift) % self._prime % len(self._table)
-
- def __len__(self):
- return self._n
-
- def __getitem__(self, k):
- j = self._hash_function(k)
- return self._bucket_getitem(j, k) # may raise KeyError
-
- def __setitem__(self, k, v):
- j = self._hash_function(k)
- self._bucket_setitem(j, k, v) # subroutine maintains self._n
- if self._n > len(self._table) // 2: # keep load factor <= 0.5
- self._resize(2 * len(self._table) - 1) # number 2^x - 1 is often prime
-
- def __delitem__(self, k):
- j = self._hash_function(k)
- self._bucket_delitem(j, k) # may raise KeyError
- self._n -= 1
- def _resize(self, c):
- """Resize bucket array to capacity c and rehash all items."""
- old = list(self.items()) # use iteration to record existing items
- self._table = c * [None] # then reset table to desired capacity
- self._n = 0 # n recomputed during subsequent adds
- for (k,v) in old:
- self[k] = v # reinsert old key-value pair
+ def __init__(self, cap=11, p=109345121):
+ """Create an empty hash-table map.
+ cap initial table size (default 11)
+ p positive prime used for MAD (default 109345121)
+ """
+ self._table = cap * [None]
+ self._n = 0 # number of entries in the map
+ self._prime = p # prime for MAD compression
+ self._scale = 1 + randrange(p - 1) # scale from 1 to p-1 for MAD
+ self._shift = randrange(p) # shift from 0 to p-1 for MAD
+
+ def _hash_function(self, k):
+ return (hash(k) * self._scale + self._shift) % self._prime % len(self._table)
+
+ def __len__(self):
+ return self._n
+
+ def __getitem__(self, k):
+ j = self._hash_function(k)
+ return self._bucket_getitem(j, k) # may raise KeyError
+
+ def __setitem__(self, k, v):
+ j = self._hash_function(k)
+ self._bucket_setitem(j, k, v) # subroutine maintains self._n
+ if self._n > len(self._table) // 2: # keep load factor <= 0.5
+ self._resize(2 * len(self._table) - 1) # number 2^x - 1 is often prime
+
+ def __delitem__(self, k):
+ j = self._hash_function(k)
+ self._bucket_delitem(j, k) # may raise KeyError
+ self._n -= 1
+
+ def _resize(self, c):
+ """Resize bucket array to capacity c and rehash all items."""
+ old = list(self.items()) # use iteration to record existing items
+ self._table = c * [None] # then reset table to desired capacity
+ self._n = 0 # n recomputed during subsequent adds
+ for (k, v) in old:
+ self[k] = v # reinsert old key-value pair
diff --git a/ch10/map_base.py b/ch10/map_base.py
index 804357e..97528e9 100644
--- a/ch10/map_base.py
+++ b/ch10/map_base.py
@@ -1,43 +1,23 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
from collections import MutableMapping
+
class MapBase(MutableMapping):
- """Our own abstract base class that includes a nonpublic _Item class."""
+ """Our own abstract base class that includes a nonpublic _Item class."""
- #------------------------------- nested _Item class -------------------------------
- class _Item:
- """Lightweight composite to store key-value pairs as map items."""
- __slots__ = '_key', '_value'
+ # ------------------------------- nested _Item class -------------------------------
+ class _Item:
+ """Lightweight composite to store key-value pairs as map items."""
+ __slots__ = '_key', '_value'
- def __init__(self, k, v):
- self._key = k
- self._value = v
+ def __init__(self, k, v):
+ self._key = k
+ self._value = v
- def __eq__(self, other):
- return self._key == other._key # compare items based on their keys
+ def __eq__(self, other):
+ return self._key == other._key # compare items based on their keys
- def __ne__(self, other):
- return not (self == other) # opposite of __eq__
+ def __ne__(self, other):
+ return not (self == other) # opposite of __eq__
- def __lt__(self, other):
- return self._key < other._key # compare items based on their keys
+ def __lt__(self, other):
+ return self._key < other._key # compare items based on their keys
diff --git a/ch10/multi_map.py b/ch10/multi_map.py
index a34adae..63c384d 100644
--- a/ch10/multi_map.py
+++ b/ch10/multi_map.py
@@ -1,76 +1,53 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
class MultiMap:
- """
- A multimap class built upon use of an underlying map for storage.
-
- This uses dict for default storage.
-
- Subclasses can override class variable _MapType to change the default.
- That catalog class must have a default constructor that produces an empty map.
- As an example, one might define the following subclass to use a SortedTableMap
-
- class SortedTableMultimap(MultiMap):
- _MapType = SortedTableMap
- """
- _MapType = dict # Map type; can be redefined by subclass
-
- def __init__(self):
- """Create a new empty multimap instance."""
- self._map = self._MapType() # create map instance for storage
- self._n = 0
-
- def __len__(self):
- """Return number of (k,v) pairs in multimap."""
- return self._n
-
- def __iter__(self):
- """Iterate through all (k,v) pairs in multimap."""
- for k,secondary in self._map.items():
- for v in secondary:
- yield (k,v)
-
- def add(self, k, v):
- """Add pair (k,v) to multimap."""
- container = self._map.setdefault(k, []) # create empty list, if needed
- container.append(v)
- self._n += 1
-
- def pop(self, k):
- """Remove and return arbitrary (k,v) pair with key k (or raise KeyError)."""
- secondary = self._map[k] # may raise KeyError
- v = secondary.pop()
- if len(secondary) == 0:
- del self._map[k] # no pairs left
- self._n -= 1
- return (k, v)
-
- def find(self, k):
- """Return arbitrary (k,v) pair with given key (or raise KeyError)."""
- secondary = self._map[k] # may raise KeyError
- return (k, secondary[0])
-
- def find_all(self, k):
- """Generate iteration of all (k,v) pairs with given key."""
- secondary = self._map.get(k, []) # empty list, by default
- for v in secondary:
- yield (k,v)
+ """
+ A multimap class built upon use of an underlying map for storage.
+ This uses dict for default storage.
+ Subclasses can override class variable _MapType to change the default.
+ That catalog class must have a default constructor that produces an empty map.
+ As an example, one might define the following subclass to use a SortedTableMap
+
+ class SortedTableMultimap(MultiMap):
+ _MapType = SortedTableMap
+ """
+ _MapType = dict # Map type; can be redefined by subclass
+
+ def __init__(self):
+ """Create a new empty multimap instance."""
+ self._map = self._MapType() # create map instance for storage
+ self._n = 0
+
+ def __len__(self):
+ """Return number of (k,v) pairs in multimap."""
+ return self._n
+
+ def __iter__(self):
+ """Iterate through all (k,v) pairs in multimap."""
+ for k, secondary in self._map.items():
+ for v in secondary:
+ yield (k, v)
+
+ def add(self, k, v):
+ """Add pair (k,v) to multimap."""
+ container = self._map.setdefault(k, []) # create empty list, if needed
+ container.append(v)
+ self._n += 1
+
+ def pop(self, k):
+ """Remove and return arbitrary (k,v) pair with key k (or raise KeyError)."""
+ secondary = self._map[k] # may raise KeyError
+ v = secondary.pop()
+ if len(secondary) == 0:
+ del self._map[k] # no pairs left
+ self._n -= 1
+ return (k, v)
+
+ def find(self, k):
+ """Return arbitrary (k,v) pair with given key (or raise KeyError)."""
+ secondary = self._map[k] # may raise KeyError
+ return (k, secondary[0])
+
+ def find_all(self, k):
+ """Generate iteration of all (k,v) pairs with given key."""
+ secondary = self._map.get(k, []) # empty list, by default
+ for v in secondary:
+ yield (k, v)
diff --git a/ch10/probe_hash_map.py b/ch10/probe_hash_map.py
index d5485bc..e010c64 100644
--- a/ch10/probe_hash_map.py
+++ b/ch10/probe_hash_map.py
@@ -1,73 +1,52 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
from .hash_map_base import HashMapBase
-class ProbeHashMap(HashMapBase):
- """Hash map implemented with linear probing for collision resolution."""
- _AVAIL = object() # sentinal marks locations of previous deletions
-
- def _is_available(self, j):
- """Return True if index j is available in table."""
- return self._table[j] is None or self._table[j] is ProbeHashMap._AVAIL
-
- def _find_slot(self, j, k):
- """Search for key k in bucket at index j.
-
- Return (success, index) tuple, described as follows:
- If match was found, success is True and index denotes its location.
- If no match found, success is False and index denotes first available slot.
- """
- firstAvail = None
- while True:
- if self._is_available(j):
- if firstAvail is None:
- firstAvail = j # mark this as first avail
- if self._table[j] is None:
- return (False, firstAvail) # search has failed
- elif k == self._table[j]._key:
- return (True, j) # found a match
- j = (j + 1) % len(self._table) # keep looking (cyclically)
- def _bucket_getitem(self, j, k):
- found, s = self._find_slot(j, k)
- if not found:
- raise KeyError('Key Error: ' + repr(k)) # no match found
- return self._table[s]._value
-
- def _bucket_setitem(self, j, k, v):
- found, s = self._find_slot(j, k)
- if not found:
- self._table[s] = self._Item(k,v) # insert new item
- self._n += 1 # size has increased
- else:
- self._table[s]._value = v # overwrite existing
-
- def _bucket_delitem(self, j, k):
- found, s = self._find_slot(j, k)
- if not found:
- raise KeyError('Key Error: ' + repr(k)) # no match found
- self._table[s] = ProbeHashMap._AVAIL # mark as vacated
-
- def __iter__(self):
- for j in range(len(self._table)): # scan entire table
- if not self._is_available(j):
- yield self._table[j]._key
+class ProbeHashMap(HashMapBase):
+ """Hash map implemented with linear probing for collision resolution."""
+ _AVAIL = object() # sentinal marks locations of previous deletions
+
+ def _is_available(self, j):
+ """Return True if index j is available in table."""
+ return self._table[j] is None or self._table[j] is ProbeHashMap._AVAIL
+
+ def _find_slot(self, j, k):
+ """Search for key k in bucket at index j.
+ Return (success, index) tuple, described as follows:
+ If match was found, success is True and index denotes its location.
+ If no match found, success is False and index denotes first available slot.
+ """
+ firstAvail = None
+ while True:
+ if self._is_available(j):
+ if firstAvail is None:
+ firstAvail = j # mark this as first avail
+ if self._table[j] is None:
+ return (False, firstAvail) # search has failed
+ elif k == self._table[j]._key:
+ return (True, j) # found a match
+ j = (j + 1) % len(self._table) # keep looking (cyclically)
+
+ def _bucket_getitem(self, j, k):
+ found, s = self._find_slot(j, k)
+ if not found:
+ raise KeyError('Key Error: ' + repr(k)) # no match found
+ return self._table[s]._value
+
+ def _bucket_setitem(self, j, k, v):
+ found, s = self._find_slot(j, k)
+ if not found:
+ self._table[s] = self._Item(k, v) # insert new item
+ self._n += 1 # size has increased
+ else:
+ self._table[s]._value = v # overwrite existing
+
+ def _bucket_delitem(self, j, k):
+ found, s = self._find_slot(j, k)
+ if not found:
+ raise KeyError('Key Error: ' + repr(k)) # no match found
+ self._table[s] = ProbeHashMap._AVAIL # mark as vacated
+
+ def __iter__(self):
+ for j in range(len(self._table)): # scan entire table
+ if not self._is_available(j):
+ yield self._table[j]._key
diff --git a/ch10/sorted_table_map.py b/ch10/sorted_table_map.py
index c0c8a78..dd59b3a 100644
--- a/ch10/sorted_table_map.py
+++ b/ch10/sorted_table_map.py
@@ -1,163 +1,136 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
from .map_base import MapBase
+
class SortedTableMap(MapBase):
- """Map implementation using a sorted table."""
-
- #----------------------------- nonpublic behaviors -----------------------------
- def _find_index(self, k, low, high):
- """Return index of the leftmost item with key greater than or equal to k.
-
- Return high + 1 if no such item qualifies.
-
- That is, j will be returned such that:
- all items of slice table[low:j] have key < k
- all items of slice table[j:high+1] have key >= k
- """
- if high < low:
- return high + 1 # no element qualifies
- else:
- mid = (low + high) // 2
- if k == self._table[mid]._key:
- return mid # found exact match
- elif k < self._table[mid]._key:
- return self._find_index(k, low, mid - 1) # Note: may return mid
- else:
- return self._find_index(k, mid + 1, high) # answer is right of mid
-
- #----------------------------- public behaviors -----------------------------
- def __init__(self):
- """Create an empty map."""
- self._table = []
-
- def __len__(self):
- """Return number of items in the map."""
- return len(self._table)
-
- def __getitem__(self, k):
- """Return value associated with key k (raise KeyError if not found)."""
- j = self._find_index(k, 0, len(self._table) - 1)
- if j == len(self._table) or self._table[j]._key != k:
- raise KeyError('Key Error: ' + repr(k))
- return self._table[j]._value
-
- def __setitem__(self, k, v):
- """Assign value v to key k, overwriting existing value if present."""
- j = self._find_index(k, 0, len(self._table) - 1)
- if j < len(self._table) and self._table[j]._key == k:
- self._table[j]._value = v # reassign value
- else:
- self._table.insert(j, self._Item(k,v)) # adds new item
-
- def __delitem__(self, k):
- """Remove item associated with key k (raise KeyError if not found)."""
- j = self._find_index(k, 0, len(self._table) - 1)
- if j == len(self._table) or self._table[j]._key != k:
- raise KeyError('Key Error: ' + repr(k))
- self._table.pop(j) # delete item
-
- def __iter__(self):
- """Generate keys of the map ordered from minimum to maximum."""
- for item in self._table:
- yield item._key
-
- def __reversed__(self):
- """Generate keys of the map ordered from maximum to minimum."""
- for item in reversed(self._table):
- yield item._key
-
- def find_min(self):
- """Return (key,value) pair with minimum key (or None if empty)."""
- if len(self._table) > 0:
- return (self._table[0]._key, self._table[0]._value)
- else:
- return None
-
- def find_max(self):
- """Return (key,value) pair with maximum key (or None if empty)."""
- if len(self._table) > 0:
- return (self._table[-1]._key, self._table[-1]._value)
- else:
- return None
-
- def find_le(self, k):
- """Return (key,value) pair with greatest key less than or equal to k.
-
- Return None if there does not exist such a key.
- """
- j = self._find_index(k, 0, len(self._table) - 1) # j's key >= k
- if j < len(self._table) and self._table[j]._key == k:
- return (self._table[j]._key, self._table[j]._value) # exact match
- elif j > 0:
- return (self._table[j-1]._key, self._table[j-1]._value) # Note use of j-1
- else:
- return None
-
- def find_ge(self, k):
- """Return (key,value) pair with least key greater than or equal to k.
-
- Return None if there does not exist such a key.
- """
- j = self._find_index(k, 0, len(self._table) - 1) # j's key >= k
- if j < len(self._table):
- return (self._table[j]._key, self._table[j]._value)
- else:
- return None
-
- def find_lt(self, k):
- """Return (key,value) pair with greatest key strictly less than k.
-
- Return None if there does not exist such a key.
- """
- j = self._find_index(k, 0, len(self._table) - 1) # j's key >= k
- if j > 0:
- return (self._table[j-1]._key, self._table[j-1]._value) # Note use of j-1
- else:
- return None
-
- def find_gt(self, k):
- """Return (key,value) pair with least key strictly greater than k.
-
- Return None if there does not exist such a key.
- """
- j = self._find_index(k, 0, len(self._table) - 1) # j's key >= k
- if j < len(self._table) and self._table[j]._key == k:
- j += 1 # advanced past match
- if j < len(self._table):
- return (self._table[j]._key, self._table[j]._value)
- else:
- return None
-
- def find_range(self, start, stop):
- """Iterate all (key,value) pairs such that start <= key < stop.
-
- If start is None, iteration begins with minimum key of map.
- If stop is None, iteration continues through the maximum key of map.
- """
- if start is None:
- j = 0
- else:
- j = self._find_index(start, 0, len(self._table)-1) # find first result
- while j < len(self._table) and (stop is None or self._table[j]._key < stop):
- yield (self._table[j]._key, self._table[j]._value)
- j += 1
+ """Map implementation using a sorted table."""
+
+ # ----------------------------- nonpublic behaviors -----------------------------
+ def _find_index(self, k, low, high):
+ """Return index of the leftmost item with key greater than or equal to k.
+ Return high + 1 if no such item qualifies.
+ That is, j will be returned such that:
+ all items of slice table[low:j] have key < k
+ all items of slice table[j:high+1] have key >= k
+ """
+ if high < low:
+ return high + 1 # no element qualifies
+ else:
+ mid = (low + high) // 2
+ if k == self._table[mid]._key:
+ return mid # found exact match
+ elif k < self._table[mid]._key:
+ return self._find_index(k, low, mid - 1) # Note: may return mid
+ else:
+ return self._find_index(k, mid + 1, high) # answer is right of mid
+
+ # ----------------------------- public behaviors -----------------------------
+ def __init__(self):
+ """Create an empty map."""
+ self._table = []
+
+ def __len__(self):
+ """Return number of items in the map."""
+ return len(self._table)
+
+ def __getitem__(self, k):
+ """Return value associated with key k (raise KeyError if not found)."""
+ j = self._find_index(k, 0, len(self._table) - 1)
+ if j == len(self._table) or self._table[j]._key != k:
+ raise KeyError('Key Error: ' + repr(k))
+ return self._table[j]._value
+
+ def __setitem__(self, k, v):
+ """Assign value v to key k, overwriting existing value if present."""
+ j = self._find_index(k, 0, len(self._table) - 1)
+ if j < len(self._table) and self._table[j]._key == k:
+ self._table[j]._value = v # reassign value
+ else:
+ self._table.insert(j, self._Item(k, v)) # adds new item
+
+ def __delitem__(self, k):
+ """Remove item associated with key k (raise KeyError if not found)."""
+ j = self._find_index(k, 0, len(self._table) - 1)
+ if j == len(self._table) or self._table[j]._key != k:
+ raise KeyError('Key Error: ' + repr(k))
+ self._table.pop(j) # delete item
+
+ def __iter__(self):
+ """Generate keys of the map ordered from minimum to maximum."""
+ for item in self._table:
+ yield item._key
+
+ def __reversed__(self):
+ """Generate keys of the map ordered from maximum to minimum."""
+ for item in reversed(self._table):
+ yield item._key
+
+ def find_min(self):
+ """Return (key,value) pair with minimum key (or None if empty)."""
+ if len(self._table) > 0:
+ return (self._table[0]._key, self._table[0]._value)
+ else:
+ return None
+
+ def find_max(self):
+ """Return (key,value) pair with maximum key (or None if empty)."""
+ if len(self._table) > 0:
+ return (self._table[-1]._key, self._table[-1]._value)
+ else:
+ return None
+
+ def find_le(self, k):
+ """Return (key,value) pair with greatest key less than or equal to k.
+ Return None if there does not exist such a key.
+ """
+ j = self._find_index(k, 0, len(self._table) - 1) # j's key >= k
+ if j < len(self._table) and self._table[j]._key == k:
+ return (self._table[j]._key, self._table[j]._value) # exact match
+ elif j > 0:
+ return (self._table[j - 1]._key, self._table[j - 1]._value) # Note use of j-1
+ else:
+ return None
+
+ def find_ge(self, k):
+ """Return (key,value) pair with least key greater than or equal to k.
+ Return None if there does not exist such a key.
+ """
+ j = self._find_index(k, 0, len(self._table) - 1) # j's key >= k
+ if j < len(self._table):
+ return (self._table[j]._key, self._table[j]._value)
+ else:
+ return None
+
+ def find_lt(self, k):
+ """Return (key,value) pair with greatest key strictly less than k.
+ Return None if there does not exist such a key.
+ """
+ j = self._find_index(k, 0, len(self._table) - 1) # j's key >= k
+ if j > 0:
+ return (self._table[j - 1]._key, self._table[j - 1]._value) # Note use of j-1
+ else:
+ return None
+
+ def find_gt(self, k):
+ """Return (key,value) pair with least key strictly greater than k.
+ Return None if there does not exist such a key.
+ """
+ j = self._find_index(k, 0, len(self._table) - 1) # j's key >= k
+ if j < len(self._table) and self._table[j]._key == k:
+ j += 1 # advanced past match
+ if j < len(self._table):
+ return (self._table[j]._key, self._table[j]._value)
+ else:
+ return None
+
+ def find_range(self, start, stop):
+ """Iterate all (key,value) pairs such that start <= key < stop.
+ If start is None, iteration begins with minimum key of map.
+ If stop is None, iteration continues through the maximum key of map.
+ """
+ if start is None:
+ j = 0
+ else:
+ j = self._find_index(start, 0, len(self._table) - 1) # find first result
+ while j < len(self._table) and (stop is None or self._table[j]._key < stop):
+ yield (self._table[j]._key, self._table[j]._value)
+ j += 1
diff --git a/ch10/unsorted_table_map.py b/ch10/unsorted_table_map.py
index daec299..bc88035 100644
--- a/ch10/unsorted_table_map.py
+++ b/ch10/unsorted_table_map.py
@@ -1,62 +1,42 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
from .map_base import MapBase
+
class UnsortedTableMap(MapBase):
- """Map implementation using an unordered list."""
-
- def __init__(self):
- """Create an empty map."""
- self._table = [] # list of _Item's
-
- def __getitem__(self, k):
- """Return value associated with key k (raise KeyError if not found)."""
- for item in self._table:
- if k == item._key:
- return item._value
- raise KeyError('Key Error: ' + repr(k))
-
- def __setitem__(self, k, v):
- """Assign value v to key k, overwriting existing value if present."""
- for item in self._table:
- if k == item._key: # Found a match:
- item._value = v # reassign value
- return # and quit
- # did not find match for key
- self._table.append(self._Item(k,v))
-
- def __delitem__(self, k):
- """Remove item associated with key k (raise KeyError if not found)."""
- for j in range(len(self._table)):
- if k == self._table[j]._key: # Found a match:
- self._table.pop(j) # remove item
- return # and quit
- raise KeyError('Key Error: ' + repr(k))
-
- def __len__(self):
- """Return number of items in the map."""
- return len(self._table)
-
- def __iter__(self):
- """Generate iteration of the map's keys."""
- for item in self._table:
- yield item._key # yield the KEY
+ """Map implementation using an unordered list."""
+
+ def __init__(self):
+ """Create an empty map."""
+ self._table = [] # list of _Item's
+
+ def __getitem__(self, k):
+ """Return value associated with key k (raise KeyError if not found)."""
+ for item in self._table:
+ if k == item._key:
+ return item._value
+ raise KeyError('Key Error: ' + repr(k))
+
+ def __setitem__(self, k, v):
+ """Assign value v to key k, overwriting existing value if present."""
+ for item in self._table:
+ if k == item._key: # Found a match:
+ item._value = v # reassign value
+ return # and quit
+ # did not find match for key
+ self._table.append(self._Item(k, v))
+
+ def __delitem__(self, k):
+ """Remove item associated with key k (raise KeyError if not found)."""
+ for j in range(len(self._table)):
+ if k == self._table[j]._key: # Found a match:
+ self._table.pop(j) # remove item
+ return # and quit
+ raise KeyError('Key Error: ' + repr(k))
+
+ def __len__(self):
+ """Return number of items in the map."""
+ return len(self._table)
+
+ def __iter__(self):
+ """Generate iteration of the map's keys."""
+ for item in self._table:
+ yield item._key # yield the KEY
diff --git a/ch10/word_frequency.py b/ch10/word_frequency.py
index 1f9f0b8..af45d2c 100644
--- a/ch10/word_frequency.py
+++ b/ch10/word_frequency.py
@@ -1,39 +1,17 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
import sys
-filename = sys.argv[1]
+filename = sys.argv[1]
freq = {}
for piece in open(filename).read().lower().split():
- # only consider alphabetic characters within this piece
- word = ''.join(c for c in piece if c.isalpha())
- if word: # require at least one alphabetic character
- freq[word] = 1 + freq.get(word, 0)
-
+ # only consider alphabetic characters within this piece
+ word = ''.join(c for c in piece if c.isalpha())
+ if word: # require at least one alphabetic character
+ freq[word] = 1 + freq.get(word, 0)
max_word = ''
max_count = 0
-for (w,c) in freq.items(): # (key, value) tuples represent (word, count)
- if c > max_count:
- max_word = w
- max_count = c
+for (w, c) in freq.items(): # (key, value) tuples represent (word, count)
+ if c > max_count:
+ max_word = w
+ max_count = c
print('The most frequent word is', max_word)
print('Its number of occurrences is', max_count)
diff --git a/ch11/avl_tree.py b/ch11/avl_tree.py
index 68c0dea..8e072cb 100644
--- a/ch11/avl_tree.py
+++ b/ch11/avl_tree.py
@@ -1,84 +1,63 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
from .binary_search_tree import TreeMap
-class AVLTreeMap(TreeMap):
- """Sorted map implementation using an AVL tree."""
-
- #-------------------------- nested _Node class --------------------------
- class _Node(TreeMap._Node):
- """Node class for AVL maintains height value for balancing.
-
- We use convention that a "None" child has height 0, thus a leaf has height 1.
- """
- __slots__ = '_height' # additional data member to store height
-
- def __init__(self, element, parent=None, left=None, right=None):
- super().__init__(element, parent, left, right)
- self._height = 0 # will be recomputed during balancing
-
- def left_height(self):
- return self._left._height if self._left is not None else 0
-
- def right_height(self):
- return self._right._height if self._right is not None else 0
-
- #------------------------- positional-based utility methods -------------------------
- def _recompute_height(self, p):
- p._node._height = 1 + max(p._node.left_height(), p._node.right_height())
- def _isbalanced(self, p):
- return abs(p._node.left_height() - p._node.right_height()) <= 1
-
- def _tall_child(self, p, favorleft=False): # parameter controls tiebreaker
- if p._node.left_height() + (1 if favorleft else 0) > p._node.right_height():
- return self.left(p)
- else:
- return self.right(p)
-
- def _tall_grandchild(self, p):
- child = self._tall_child(p)
- # if child is on left, favor left grandchild; else favor right grandchild
- alignment = (child == self.left(p))
- return self._tall_child(child, alignment)
-
- def _rebalance(self, p):
- while p is not None:
- old_height = p._node._height # trivially 0 if new node
- if not self._isbalanced(p): # imbalance detected!
- # perform trinode restructuring, setting p to resulting root,
- # and recompute new local heights after the restructuring
- p = self._restructure(self._tall_grandchild(p))
- self._recompute_height(self.left(p))
- self._recompute_height(self.right(p))
- self._recompute_height(p) # adjust for recent changes
- if p._node._height == old_height: # has height changed?
- p = None # no further changes needed
- else:
- p = self.parent(p) # repeat with parent
-
- #---------------------------- override balancing hooks ----------------------------
- def _rebalance_insert(self, p):
- self._rebalance(p)
-
- def _rebalance_delete(self, p):
- self._rebalance(p)
+class AVLTreeMap(TreeMap):
+ """Sorted map implementation using an AVL tree."""
+
+ # -------------------------- nested _Node class --------------------------
+ class _Node(TreeMap._Node):
+ """Node class for AVL maintains height value for balancing.
+ We use convention that a "None" child has height 0, thus a leaf has height 1.
+ """
+ __slots__ = '_height' # additional data member to store height
+
+ def __init__(self, element, parent=None, left=None, right=None):
+ super().__init__(element, parent, left, right)
+ self._height = 0 # will be recomputed during balancing
+
+ def left_height(self):
+ return self._left._height if self._left is not None else 0
+
+ def right_height(self):
+ return self._right._height if self._right is not None else 0
+
+ # ------------------------- positional-based utility methods -------------------------
+ def _recompute_height(self, p):
+ p._node._height = 1 + max(p._node.left_height(), p._node.right_height())
+
+ def _isbalanced(self, p):
+ return abs(p._node.left_height() - p._node.right_height()) <= 1
+
+ def _tall_child(self, p, favorleft=False): # parameter controls tiebreaker
+ if p._node.left_height() + (1 if favorleft else 0) > p._node.right_height():
+ return self.left(p)
+ else:
+ return self.right(p)
+
+ def _tall_grandchild(self, p):
+ child = self._tall_child(p)
+ # if child is on left, favor left grandchild; else favor right grandchild
+ alignment = (child == self.left(p))
+ return self._tall_child(child, alignment)
+
+ def _rebalance(self, p):
+ while p is not None:
+ old_height = p._node._height # trivially 0 if new node
+ if not self._isbalanced(p): # imbalance detected!
+ # perform trinode restructuring, setting p to resulting root,
+ # and recompute new local heights after the restructuring
+ p = self._restructure(self._tall_grandchild(p))
+ self._recompute_height(self.left(p))
+ self._recompute_height(self.right(p))
+ self._recompute_height(p) # adjust for recent changes
+ if p._node._height == old_height: # has height changed?
+ p = None # no further changes needed
+ else:
+ p = self.parent(p) # repeat with parent
+
+ # ---------------------------- override balancing hooks ----------------------------
+ def _rebalance_insert(self, p):
+ self._rebalance(p)
+
+ def _rebalance_delete(self, p):
+ self._rebalance(p)
diff --git a/ch11/binary_search_tree.py b/ch11/binary_search_tree.py
index 52d25a7..443b7f1 100644
--- a/ch11/binary_search_tree.py
+++ b/ch11/binary_search_tree.py
@@ -1,357 +1,320 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
from ..ch08.linked_binary_tree import LinkedBinaryTree
from ..ch10.map_base import MapBase
-class TreeMap(LinkedBinaryTree, MapBase):
- """Sorted map implementation using a binary search tree."""
-
- #---------------------------- override Position class ----------------------------
- class Position(LinkedBinaryTree.Position):
- def key(self):
- """Return key of map's key-value pair."""
- return self.element()._key
-
- def value(self):
- """Return value of map's key-value pair."""
- return self.element()._value
-
- #------------------------------- nonpublic utilities -------------------------------
- def _subtree_search(self, p, k):
- """Return Position of p's subtree having key k, or last node searched."""
- if k == p.key(): # found match
- return p
- elif k < p.key(): # search left subtree
- if self.left(p) is not None:
- return self._subtree_search(self.left(p), k)
- else: # search right subtree
- if self.right(p) is not None:
- return self._subtree_search(self.right(p), k)
- return p # unsucessful search
-
- def _subtree_first_position(self, p):
- """Return Position of first item in subtree rooted at p."""
- walk = p
- while self.left(walk) is not None: # keep walking left
- walk = self.left(walk)
- return walk
-
- def _subtree_last_position(self, p):
- """Return Position of last item in subtree rooted at p."""
- walk = p
- while self.right(walk) is not None: # keep walking right
- walk = self.right(walk)
- return walk
-
- #--------------------- public methods providing "positional" support ---------------------
- def first(self):
- """Return the first Position in the tree (or None if empty)."""
- return self._subtree_first_position(self.root()) if len(self) > 0 else None
-
- def last(self):
- """Return the last Position in the tree (or None if empty)."""
- return self._subtree_last_position(self.root()) if len(self) > 0 else None
-
- def before(self, p):
- """Return the Position just before p in the natural order.
-
- Return None if p is the first position.
- """
- self._validate(p) # inherited from LinkedBinaryTree
- if self.left(p):
- return self._subtree_last_position(self.left(p))
- else:
- # walk upward
- walk = p
- above = self.parent(walk)
- while above is not None and walk == self.left(above):
- walk = above
- above = self.parent(walk)
- return above
-
- def after(self, p):
- """Return the Position just after p in the natural order.
-
- Return None if p is the last position.
- """
- self._validate(p) # inherited from LinkedBinaryTree
- if self.right(p):
- return self._subtree_first_position(self.right(p))
- else:
- walk = p
- above = self.parent(walk)
- while above is not None and walk == self.right(above):
- walk = above
- above = self.parent(walk)
- return above
-
- def find_position(self, k):
- """Return position with key k, or else neighbor (or None if empty)."""
- if self.is_empty():
- return None
- else:
- p = self._subtree_search(self.root(), k)
- self._rebalance_access(p) # hook for balanced tree subclasses
- return p
-
- def delete(self, p):
- """Remove the item at given Position."""
- self._validate(p) # inherited from LinkedBinaryTree
- if self.left(p) and self.right(p): # p has two children
- replacement = self._subtree_last_position(self.left(p))
- self._replace(p, replacement.element()) # from LinkedBinaryTree
- p = replacement
- # now p has at most one child
- parent = self.parent(p)
- self._delete(p) # inherited from LinkedBinaryTree
- self._rebalance_delete(parent) # if root deleted, parent is None
-
- #--------------------- public methods for (standard) map interface ---------------------
- def __getitem__(self, k):
- """Return value associated with key k (raise KeyError if not found)."""
- if self.is_empty():
- raise KeyError('Key Error: ' + repr(k))
- else:
- p = self._subtree_search(self.root(), k)
- self._rebalance_access(p) # hook for balanced tree subclasses
- if k != p.key():
- raise KeyError('Key Error: ' + repr(k))
- return p.value()
- def __setitem__(self, k, v):
- """Assign value v to key k, overwriting existing value if present."""
- if self.is_empty():
- leaf = self._add_root(self._Item(k,v)) # from LinkedBinaryTree
- else:
- p = self._subtree_search(self.root(), k)
- if p.key() == k:
- p.element()._value = v # replace existing item's value
- self._rebalance_access(p) # hook for balanced tree subclasses
- return
- else:
- item = self._Item(k,v)
- if p.key() < k:
- leaf = self._add_right(p, item) # inherited from LinkedBinaryTree
+class TreeMap(LinkedBinaryTree, MapBase):
+ """Sorted map implementation using a binary search tree."""
+
+ # ---------------------------- override Position class ----------------------------
+ class Position(LinkedBinaryTree.Position):
+ def key(self):
+ """Return key of map's key-value pair."""
+ return self.element()._key
+
+ def value(self):
+ """Return value of map's key-value pair."""
+ return self.element()._value
+
+ # ------------------------------- nonpublic utilities -------------------------------
+ def _subtree_search(self, p, k):
+ """Return Position of p's subtree having key k, or last node searched."""
+ if k == p.key(): # found match
+ return p
+ elif k < p.key(): # search left subtree
+ if self.left(p) is not None:
+ return self._subtree_search(self.left(p), k)
+ else: # search right subtree
+ if self.right(p) is not None:
+ return self._subtree_search(self.right(p), k)
+ return p # unsucessful search
+
+ def _subtree_first_position(self, p):
+ """Return Position of first item in subtree rooted at p."""
+ walk = p
+ while self.left(walk) is not None: # keep walking left
+ walk = self.left(walk)
+ return walk
+
+ def _subtree_last_position(self, p):
+ """Return Position of last item in subtree rooted at p."""
+ walk = p
+ while self.right(walk) is not None: # keep walking right
+ walk = self.right(walk)
+ return walk
+
+ # --------------------- public methods providing "positional" support ---------------------
+ def first(self):
+ """Return the first Position in the tree (or None if empty)."""
+ return self._subtree_first_position(self.root()) if len(self) > 0 else None
+
+ def last(self):
+ """Return the last Position in the tree (or None if empty)."""
+ return self._subtree_last_position(self.root()) if len(self) > 0 else None
+
+ def before(self, p):
+ """Return the Position just before p in the natural order.
+ Return None if p is the first position.
+ """
+ self._validate(p) # inherited from LinkedBinaryTree
+ if self.left(p):
+ return self._subtree_last_position(self.left(p))
else:
- leaf = self._add_left(p, item) # inherited from LinkedBinaryTree
- self._rebalance_insert(leaf) # hook for balanced tree subclasses
-
- def __delitem__(self, k):
- """Remove item associated with key k (raise KeyError if not found)."""
- if not self.is_empty():
- p = self._subtree_search(self.root(), k)
- if k == p.key():
- self.delete(p) # rely on positional version
- return # successful deletion complete
- self._rebalance_access(p) # hook for balanced tree subclasses
- raise KeyError('Key Error: ' + repr(k))
-
- def __iter__(self):
- """Generate an iteration of all keys in the map in order."""
- p = self.first()
- while p is not None:
- yield p.key()
- p = self.after(p)
-
- #--------------------- public methods for sorted map interface ---------------------
- def __reversed__(self):
- """Generate an iteration of all keys in the map in reverse order."""
- p = self.last()
- while p is not None:
- yield p.key()
- p = self.before(p)
-
- def find_min(self):
- """Return (key,value) pair with minimum key (or None if empty)."""
- if self.is_empty():
- return None
- else:
- p = self.first()
- return (p.key(), p.value())
-
- def find_max(self):
- """Return (key,value) pair with maximum key (or None if empty)."""
- if self.is_empty():
- return None
- else:
- p = self.last()
- return (p.key(), p.value())
-
- def find_le(self, k):
- """Return (key,value) pair with greatest key less than or equal to k.
-
- Return None if there does not exist such a key.
- """
- if self.is_empty():
- return None
- else:
- p = self.find_position(k)
- if k < p.key():
- p = self.before(p)
- return (p.key(), p.value()) if p is not None else None
-
- def find_lt(self, k):
- """Return (key,value) pair with greatest key strictly less than k.
-
- Return None if there does not exist such a key.
- """
- if self.is_empty():
- return None
- else:
- p = self.find_position(k)
- if not p.key() < k:
- p = self.before(p)
- return (p.key(), p.value()) if p is not None else None
-
- def find_ge(self, k):
- """Return (key,value) pair with least key greater than or equal to k.
-
- Return None if there does not exist such a key.
- """
- if self.is_empty():
- return None
- else:
- p = self.find_position(k) # may not find exact match
- if p.key() < k: # p's key is too small
- p = self.after(p)
- return (p.key(), p.value()) if p is not None else None
-
- def find_gt(self, k):
- """Return (key,value) pair with least key strictly greater than k.
-
- Return None if there does not exist such a key.
- """
- if self.is_empty():
- return None
- else:
- p = self.find_position(k)
- if not k < p.key():
- p = self.after(p)
- return (p.key(), p.value()) if p is not None else None
-
- def find_range(self, start, stop):
- """Iterate all (key,value) pairs such that start <= key < stop.
+ # walk upward
+ walk = p
+ above = self.parent(walk)
+ while above is not None and walk == self.left(above):
+ walk = above
+ above = self.parent(walk)
+ return above
+
+ def after(self, p):
+ """Return the Position just after p in the natural order.
+ Return None if p is the last position.
+ """
+ self._validate(p) # inherited from LinkedBinaryTree
+ if self.right(p):
+ return self._subtree_first_position(self.right(p))
+ else:
+ walk = p
+ above = self.parent(walk)
+ while above is not None and walk == self.right(above):
+ walk = above
+ above = self.parent(walk)
+ return above
+
+ def find_position(self, k):
+ """Return position with key k, or else neighbor (or None if empty)."""
+ if self.is_empty():
+ return None
+ else:
+ p = self._subtree_search(self.root(), k)
+ self._rebalance_access(p) # hook for balanced tree subclasses
+ return p
+
+ def delete(self, p):
+ """Remove the item at given Position."""
+ self._validate(p) # inherited from LinkedBinaryTree
+ if self.left(p) and self.right(p): # p has two children
+ replacement = self._subtree_last_position(self.left(p))
+ self._replace(p, replacement.element()) # from LinkedBinaryTree
+ p = replacement
+ # now p has at most one child
+ parent = self.parent(p)
+ self._delete(p) # inherited from LinkedBinaryTree
+ self._rebalance_delete(parent) # if root deleted, parent is None
+
+ # --------------------- public methods for (standard) map interface ---------------------
+ def __getitem__(self, k):
+ """Return value associated with key k (raise KeyError if not found)."""
+ if self.is_empty():
+ raise KeyError('Key Error: ' + repr(k))
+ else:
+ p = self._subtree_search(self.root(), k)
+ self._rebalance_access(p) # hook for balanced tree subclasses
+ if k != p.key():
+ raise KeyError('Key Error: ' + repr(k))
+ return p.value()
+
+ def __setitem__(self, k, v):
+ """Assign value v to key k, overwriting existing value if present."""
+ if self.is_empty():
+ leaf = self._add_root(self._Item(k, v)) # from LinkedBinaryTree
+ else:
+ p = self._subtree_search(self.root(), k)
+ if p.key() == k:
+ p.element()._value = v # replace existing item's value
+ self._rebalance_access(p) # hook for balanced tree subclasses
+ return
+ else:
+ item = self._Item(k, v)
+ if p.key() < k:
+ leaf = self._add_right(p, item) # inherited from LinkedBinaryTree
+ else:
+ leaf = self._add_left(p, item) # inherited from LinkedBinaryTree
+ self._rebalance_insert(leaf) # hook for balanced tree subclasses
+
+ def __delitem__(self, k):
+ """Remove item associated with key k (raise KeyError if not found)."""
+ if not self.is_empty():
+ p = self._subtree_search(self.root(), k)
+ if k == p.key():
+ self.delete(p) # rely on positional version
+ return # successful deletion complete
+ self._rebalance_access(p) # hook for balanced tree subclasses
+ raise KeyError('Key Error: ' + repr(k))
- If start is None, iteration begins with minimum key of map.
- If stop is None, iteration continues through the maximum key of map.
- """
- if not self.is_empty():
- if start is None:
+ def __iter__(self):
+ """Generate an iteration of all keys in the map in order."""
p = self.first()
- else:
- # we initialize p with logic similar to find_ge
- p = self.find_position(start)
- if p.key() < start:
- p = self.after(p)
- while p is not None and (stop is None or p.key() < stop):
- yield (p.key(), p.value())
- p = self.after(p)
-
- #--------------------- hooks used by subclasses to balance a tree ---------------------
- def _rebalance_insert(self, p):
- """Call to indicate that position p is newly added."""
- pass
-
- def _rebalance_delete(self, p):
- """Call to indicate that a child of p has been removed."""
- pass
-
- def _rebalance_access(self, p):
- """Call to indicate that position p was recently accessed."""
- pass
-
- #--------------------- nonpublic methods to support tree balancing ---------------------
-
- def _relink(self, parent, child, make_left_child):
- """Relink parent node with child node (we allow child to be None)."""
- if make_left_child: # make it a left child
- parent._left = child
- else: # make it a right child
- parent._right = child
- if child is not None: # make child point to parent
- child._parent = parent
-
- def _rotate(self, p):
- """Rotate Position p above its parent.
-
- Switches between these configurations, depending on whether p==a or p==b.
-
- b a
- / \ / \
- a t2 t0 b
- / \ / \
- t0 t1 t1 t2
-
- Caller should ensure that p is not the root.
- """
- """Rotate Position p above its parent."""
- x = p._node
- y = x._parent # we assume this exists
- z = y._parent # grandparent (possibly None)
- if z is None:
- self._root = x # x becomes root
- x._parent = None
- else:
- self._relink(z, x, y == z._left) # x becomes a direct child of z
- # now rotate x and y, including transfer of middle subtree
- if x == y._left:
- self._relink(y, x._right, True) # x._right becomes left child of y
- self._relink(x, y, False) # y becomes right child of x
- else:
- self._relink(y, x._left, False) # x._left becomes right child of y
- self._relink(x, y, True) # y becomes left child of x
-
- def _restructure(self, x):
- """Perform a trinode restructure among Position x, its parent, and its grandparent.
-
- Return the Position that becomes root of the restructured subtree.
-
- Assumes the nodes are in one of the following configurations:
-
- z=a z=c z=a z=c
- / \ / \ / \ / \
- t0 y=b y=b t3 t0 y=c y=a t3
- / \ / \ / \ / \
- t1 x=c x=a t2 x=b t3 t0 x=b
- / \ / \ / \ / \
- t2 t3 t0 t1 t1 t2 t1 t2
-
- The subtree will be restructured so that the node with key b becomes its root.
-
- b
- / \
- a c
- / \ / \
- t0 t1 t2 t3
+ while p is not None:
+ yield p.key()
+ p = self.after(p)
+
+ # --------------------- public methods for sorted map interface ---------------------
+ def __reversed__(self):
+ """Generate an iteration of all keys in the map in reverse order."""
+ p = self.last()
+ while p is not None:
+ yield p.key()
+ p = self.before(p)
+
+ def find_min(self):
+ """Return (key,value) pair with minimum key (or None if empty)."""
+ if self.is_empty():
+ return None
+ else:
+ p = self.first()
+ return (p.key(), p.value())
- Caller should ensure that x has a grandparent.
- """
- """Perform trinode restructure of Position x with parent/grandparent."""
- y = self.parent(x)
- z = self.parent(y)
- if (x == self.right(y)) == (y == self.right(z)): # matching alignments
- self._rotate(y) # single rotation (of y)
- return y # y is new subtree root
- else: # opposite alignments
- self._rotate(x) # double rotation (of x)
- self._rotate(x)
- return x # x is new subtree root
+ def find_max(self):
+ """Return (key,value) pair with maximum key (or None if empty)."""
+ if self.is_empty():
+ return None
+ else:
+ p = self.last()
+ return (p.key(), p.value())
+
+ def find_le(self, k):
+ """Return (key,value) pair with greatest key less than or equal to k.
+ Return None if there does not exist such a key.
+ """
+ if self.is_empty():
+ return None
+ else:
+ p = self.find_position(k)
+ if k < p.key():
+ p = self.before(p)
+ return (p.key(), p.value()) if p is not None else None
+
+ def find_lt(self, k):
+ """Return (key,value) pair with greatest key strictly less than k.
+ Return None if there does not exist such a key.
+ """
+ if self.is_empty():
+ return None
+ else:
+ p = self.find_position(k)
+ if not p.key() < k:
+ p = self.before(p)
+ return (p.key(), p.value()) if p is not None else None
+
+ def find_ge(self, k):
+ """Return (key,value) pair with least key greater than or equal to k.
+ Return None if there does not exist such a key.
+ """
+ if self.is_empty():
+ return None
+ else:
+ p = self.find_position(k) # may not find exact match
+ if p.key() < k: # p's key is too small
+ p = self.after(p)
+ return (p.key(), p.value()) if p is not None else None
+
+ def find_gt(self, k):
+ """Return (key,value) pair with least key strictly greater than k.
+ Return None if there does not exist such a key.
+ """
+ if self.is_empty():
+ return None
+ else:
+ p = self.find_position(k)
+ if not k < p.key():
+ p = self.after(p)
+ return (p.key(), p.value()) if p is not None else None
+
+ def find_range(self, start, stop):
+ """Iterate all (key,value) pairs such that start <= key < stop.
+ If start is None, iteration begins with minimum key of map.
+ If stop is None, iteration continues through the maximum key of map.
+ """
+ if not self.is_empty():
+ if start is None:
+ p = self.first()
+ else:
+ # we initialize p with logic similar to find_ge
+ p = self.find_position(start)
+ if p.key() < start:
+ p = self.after(p)
+ while p is not None and (stop is None or p.key() < stop):
+ yield (p.key(), p.value())
+ p = self.after(p)
+
+ # --------------------- hooks used by subclasses to balance a tree ---------------------
+ def _rebalance_insert(self, p):
+ """Call to indicate that position p is newly added."""
+ pass
+
+ def _rebalance_delete(self, p):
+ """Call to indicate that a child of p has been removed."""
+ pass
+
+ def _rebalance_access(self, p):
+ """Call to indicate that position p was recently accessed."""
+ pass
+
+ # --------------------- nonpublic methods to support tree balancing ---------------------
+ def _relink(self, parent, child, make_left_child):
+ """Relink parent node with child node (we allow child to be None)."""
+ if make_left_child: # make it a left child
+ parent._left = child
+ else: # make it a right child
+ parent._right = child
+ if child is not None: # make child point to parent
+ child._parent = parent
+
+ def _rotate(self, p):
+ """Rotate Position p above its parent.
+ Switches between these configurations, depending on whether p==a or p==b.
+ b a
+ / \ / \
+ a t2 t0 b
+ / \ / \
+ t0 t1 t1 t2
+ Caller should ensure that p is not the root.
+ """
+ """Rotate Position p above its parent."""
+ x = p._node
+ y = x._parent # we assume this exists
+ z = y._parent # grandparent (possibly None)
+ if z is None:
+ self._root = x # x becomes root
+ x._parent = None
+ else:
+ self._relink(z, x, y == z._left) # x becomes a direct child of z
+ # now rotate x and y, including transfer of middle subtree
+ if x == y._left:
+ self._relink(y, x._right, True) # x._right becomes left child of y
+ self._relink(x, y, False) # y becomes right child of x
+ else:
+ self._relink(y, x._left, False) # x._left becomes right child of y
+ self._relink(x, y, True) # y becomes left child of x
+
+ def _restructure(self, x):
+ """Perform a trinode restructure among Position x, its parent, and its grandparent.
+ Return the Position that becomes root of the restructured subtree.
+ Assumes the nodes are in one of the following configurations:
+ z=a z=c z=a z=c
+ / \ / \ / \ / \
+ t0 y=b y=b t3 t0 y=c y=a t3
+ / \ / \ / \ / \
+ t1 x=c x=a t2 x=b t3 t0 x=b
+ / \ / \ / \ / \
+ t2 t3 t0 t1 t1 t2 t1 t2
+ The subtree will be restructured so that the node with key b becomes its root.
+ b
+ / \
+ a c
+ / \ / \
+ t0 t1 t2 t3
+ Caller should ensure that x has a grandparent.
+ """
+ """Perform trinode restructure of Position x with parent/grandparent."""
+ y = self.parent(x)
+ z = self.parent(y)
+ if (x == self.right(y)) == (y == self.right(z)): # matching alignments
+ self._rotate(y) # single rotation (of y)
+ return y # y is new subtree root
+ else: # opposite alignments
+ self._rotate(x) # double rotation (of x)
+ self._rotate(x)
+ return x # x is new subtree root
diff --git a/ch11/red_black_tree.py b/ch11/red_black_tree.py
index a8bbed9..611e87e 100644
--- a/ch11/red_black_tree.py
+++ b/ch11/red_black_tree.py
@@ -1,113 +1,102 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
from .binary_search_tree import TreeMap
+
class RedBlackTreeMap(TreeMap):
- """Sorted map implementation using a red-black tree."""
+ """Sorted map implementation using a red-black tree."""
+
+ # -------------------------- nested _Node class --------------------------
+ class _Node(TreeMap._Node):
+ """Node class for red-black tree maintains bit that denotes color."""
+ __slots__ = '_red' # add additional data member to the Node class
+
+ def __init__(self, element, parent=None, left=None, right=None):
+ super().__init__(element, parent, left, right)
+ self._red = True # new node red by default
- #-------------------------- nested _Node class --------------------------
- class _Node(TreeMap._Node):
- """Node class for red-black tree maintains bit that denotes color."""
- __slots__ = '_red' # add additional data member to the Node class
+ # ------------------------- positional-based utility methods -------------------------
+ # we consider a nonexistent child to be trivially black
+ def _set_red(self, p):
+ p._node._red = True
- def __init__(self, element, parent=None, left=None, right=None):
- super().__init__(element, parent, left, right)
- self._red = True # new node red by default
+ def _set_black(self, p):
+ p._node._red = False
- #------------------------- positional-based utility methods -------------------------
- # we consider a nonexistent child to be trivially black
- def _set_red(self, p): p._node._red = True
- def _set_black(self, p): p._node._red = False
- def _set_color(self, p, make_red): p._node._red = make_red
- def _is_red(self, p): return p is not None and p._node._red
- def _is_red_leaf(self, p): return self._is_red(p) and self.is_leaf(p)
+ def _set_color(self, p, make_red):
+ p._node._red = make_red
- def _get_red_child(self, p):
- """Return a red child of p (or None if no such child)."""
- for child in (self.left(p), self.right(p)):
- if self._is_red(child):
- return child
- return None
-
- #------------------------- support for insertions -------------------------
- def _rebalance_insert(self, p):
- self._resolve_red(p) # new node is always red
+ def _is_red(self, p):
+ return p is not None and p._node._red
- def _resolve_red(self, p):
- if self.is_root(p):
- self._set_black(p) # make root black
- else:
- parent = self.parent(p)
- if self._is_red(parent): # double red problem
- uncle = self.sibling(parent)
- if not self._is_red(uncle): # Case 1: misshapen 4-node
- middle = self._restructure(p) # do trinode restructuring
- self._set_black(middle) # and then fix colors
- self._set_red(self.left(middle))
- self._set_red(self.right(middle))
- else: # Case 2: overfull 5-node
- grand = self.parent(parent)
- self._set_red(grand) # grandparent becomes red
- self._set_black(self.left(grand)) # its children become black
- self._set_black(self.right(grand))
- self._resolve_red(grand) # recur at red grandparent
-
- #------------------------- support for deletions -------------------------
- def _rebalance_delete(self, p):
- if len(self) == 1:
- self._set_black(self.root()) # special case: ensure that root is black
- elif p is not None:
- n = self.num_children(p)
- if n == 1: # deficit exists unless child is a red leaf
- c = next(self.children(p))
- if not self._is_red_leaf(c):
- self._fix_deficit(p, c)
- elif n == 2: # removed black node with red child
- if self._is_red_leaf(self.left(p)):
- self._set_black(self.left(p))
+ def _is_red_leaf(self, p):
+ return self._is_red(p) and self.is_leaf(p)
+
+ def _get_red_child(self, p):
+ """Return a red child of p (or None if no such child)."""
+ for child in (self.left(p), self.right(p)):
+ if self._is_red(child):
+ return child
+ return None
+
+ # ------------------------- support for insertions -------------------------
+ def _rebalance_insert(self, p):
+ self._resolve_red(p) # new node is always red
+
+ def _resolve_red(self, p):
+ if self.is_root(p):
+ self._set_black(p) # make root black
else:
- self._set_black(self.right(p))
+ parent = self.parent(p)
+ if self._is_red(parent): # double red problem
+ uncle = self.sibling(parent)
+ if not self._is_red(uncle): # Case 1: misshapen 4-node
+ middle = self._restructure(p) # do trinode restructuring
+ self._set_black(middle) # and then fix colors
+ self._set_red(self.left(middle))
+ self._set_red(self.right(middle))
+ else: # Case 2: overfull 5-node
+ grand = self.parent(parent)
+ self._set_red(grand) # grandparent becomes red
+ self._set_black(self.left(grand)) # its children become black
+ self._set_black(self.right(grand))
+ self._resolve_red(grand) # recur at red grandparent
+
+ # ------------------------- support for deletions -------------------------
+ def _rebalance_delete(self, p):
+ if len(self) == 1:
+ self._set_black(self.root()) # special case: ensure that root is black
+ elif p is not None:
+ n = self.num_children(p)
+ if n == 1: # deficit exists unless child is a red leaf
+ c = next(self.children(p))
+ if not self._is_red_leaf(c):
+ self._fix_deficit(p, c)
+ elif n == 2: # removed black node with red child
+ if self._is_red_leaf(self.left(p)):
+ self._set_black(self.left(p))
+ else:
+ self._set_black(self.right(p))
- def _fix_deficit(self, z, y):
- """Resolve black deficit at z, where y is the root of z's heavier subtree."""
- if not self._is_red(y): # y is black; will apply Case 1 or 2
- x = self._get_red_child(y)
- if x is not None: # Case 1: y is black and has red child x; do "transfer"
- old_color = self._is_red(z)
- middle = self._restructure(x)
- self._set_color(middle, old_color) # middle gets old color of z
- self._set_black(self.left(middle)) # children become black
- self._set_black(self.right(middle))
- else: # Case 2: y is black, but no red children; recolor as "fusion"
- self._set_red(y)
- if self._is_red(z):
- self._set_black(z) # this resolves the problem
- elif not self.is_root(z):
- self._fix_deficit(self.parent(z), self.sibling(z)) # recur upward
- else: # Case 3: y is red; rotate misaligned 3-node and repeat
- self._rotate(y)
- self._set_black(y)
- self._set_red(z)
- if z == self.right(y):
- self._fix_deficit(z, self.left(z))
- else:
- self._fix_deficit(z, self.right(z))
+ def _fix_deficit(self, z, y):
+ """Resolve black deficit at z, where y is the root of z's heavier subtree."""
+ if not self._is_red(y): # y is black; will apply Case 1 or 2
+ x = self._get_red_child(y)
+ if x is not None: # Case 1: y is black and has red child x; do "transfer"
+ old_color = self._is_red(z)
+ middle = self._restructure(x)
+ self._set_color(middle, old_color) # middle gets old color of z
+ self._set_black(self.left(middle)) # children become black
+ self._set_black(self.right(middle))
+ else: # Case 2: y is black, but no red children; recolor as "fusion"
+ self._set_red(y)
+ if self._is_red(z):
+ self._set_black(z) # this resolves the problem
+ elif not self.is_root(z):
+ self._fix_deficit(self.parent(z), self.sibling(z)) # recur upward
+ else: # Case 3: y is red; rotate misaligned 3-node and repeat
+ self._rotate(y)
+ self._set_black(y)
+ self._set_red(z)
+ if z == self.right(y):
+ self._fix_deficit(z, self.left(z))
+ else:
+ self._fix_deficit(z, self.right(z))
diff --git a/ch11/splay_tree.py b/ch11/splay_tree.py
index f969aff..88bd7f4 100644
--- a/ch11/splay_tree.py
+++ b/ch11/splay_tree.py
@@ -1,53 +1,33 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
from .binary_search_tree import TreeMap
+
class SplayTreeMap(TreeMap):
- """Sorted map implementation using a splay tree."""
+ """Sorted map implementation using a splay tree."""
- #--------------------------------- splay operation --------------------------------
- def _splay(self, p):
- while p != self.root():
- parent = self.parent(p)
- grand = self.parent(parent)
- if grand is None:
- # zig case
- self._rotate(p)
- elif (parent == self.left(grand)) == (p == self.left(parent)):
- # zig-zig case
- self._rotate(parent) # move PARENT up
- self._rotate(p) # then move p up
- else:
- # zig-zag case
- self._rotate(p) # move p up
- self._rotate(p) # move p up again
+ # --------------------------------- splay operation --------------------------------
+ def _splay(self, p):
+ while p != self.root():
+ parent = self.parent(p)
+ grand = self.parent(parent)
+ if grand is None:
+ # zig case
+ self._rotate(p)
+ elif (parent == self.left(grand)) == (p == self.left(parent)):
+ # zig-zig case
+ self._rotate(parent) # move PARENT up
+ self._rotate(p) # then move p up
+ else:
+ # zig-zag case
+ self._rotate(p) # move p up
+ self._rotate(p) # move p up again
- #---------------------------- override balancing hooks ----------------------------
- def _rebalance_insert(self, p):
- self._splay(p)
+ # ---------------------------- override balancing hooks ----------------------------
+ def _rebalance_insert(self, p):
+ self._splay(p)
- def _rebalance_delete(self, p):
- if p is not None:
- self._splay(p)
+ def _rebalance_delete(self, p):
+ if p is not None:
+ self._splay(p)
- def _rebalance_access(self, p):
- self._splay(p)
+ def _rebalance_access(self, p):
+ self._splay(p)
diff --git a/ch12/__init__.py b/ch12/__init__.py
index c64a11d..4a51aeb 100644
--- a/ch12/__init__.py
+++ b/ch12/__init__.py
@@ -1 +1,2 @@
-__all__ = ['decorated_merge_sort', 'merge_array', 'merge_nonrecur', 'merge_queue', 'quick_inplace', 'quick_queue', 'quick_select']
+__all__ = ['decorated_merge_sort', 'merge_array', 'merge_nonrecur', 'merge_queue', 'quick_inplace', 'quick_queue',
+ 'quick_select']
diff --git a/ch12/decorated_merge_sort.py b/ch12/decorated_merge_sort.py
index a6bb716..8259d85 100644
--- a/ch12/decorated_merge_sort.py
+++ b/ch12/decorated_merge_sort.py
@@ -1,43 +1,24 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
from .merge_array import merge_sort
+
class _Item:
- """Lightweight composite to store decorated value for sorting."""
- __slots__ = '_key', '_value'
+ """Lightweight composite to store decorated value for sorting."""
+ __slots__ = '_key', '_value'
+
+ def __init__(self, k, v):
+ self._key = k
+ self._value = v
- def __init__(self, k, v):
- self._key = k
- self._value = v
+ def __lt__(self, other):
+ return self._key < other._key # compare items based on their keys
- def __lt__(self, other):
- return self._key < other._key # compare items based on their keys
def decorated_merge_sort(data, key=None):
- """Demonstration of the decorate-sort-undecorate pattern."""
- if key is not None:
- for j in range(len(data)):
- data[j] = _Item(key(data[j]), data[j]) # decorate each element
- merge_sort(data) # sort with existing algorithm
- if key is not None:
- for j in range(len(data)):
- data[j] = data[j]._value # undecorate each element
+ """Demonstration of the decorate-sort-undecorate pattern."""
+ if key is not None:
+ for j in range(len(data)):
+ data[j] = _Item(key(data[j]), data[j]) # decorate each element
+ merge_sort(data) # sort with existing algorithm
+ if key is not None:
+ for j in range(len(data)):
+ data[j] = data[j]._value # undecorate each element
diff --git a/ch12/merge_array.py b/ch12/merge_array.py
index 248dbcc..c62f000 100644
--- a/ch12/merge_array.py
+++ b/ch12/merge_array.py
@@ -1,46 +1,26 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
def merge(S1, S2, S):
- """Merge two sorted Python lists S1 and S2 into properly sized list S."""
- i = j = 0
- while i + j < len(S):
- if j == len(S2) or (i < len(S1) and S1[i] < S2[j]):
- S[i+j] = S1[i] # copy ith element of S1 as next item of S
- i += 1
- else:
- S[i+j] = S2[j] # copy jth element of S2 as next item of S
- j += 1
+ """Merge two sorted Python lists S1 and S2 into properly sized list S."""
+ i = j = 0
+ while i + j < len(S):
+ if j == len(S2) or (i < len(S1) and S1[i] < S2[j]):
+ S[i + j] = S1[i] # copy ith element of S1 as next item of S
+ i += 1
+ else:
+ S[i + j] = S2[j] # copy jth element of S2 as next item of S
+ j += 1
+
def merge_sort(S):
- """Sort the elements of Python list S using the merge-sort algorithm."""
- n = len(S)
- if n < 2:
- return # list is already sorted
- # divide
- mid = n // 2
- S1 = S[0:mid] # copy of first half
- S2 = S[mid:n] # copy of second half
- # conquer (with recursion)
- merge_sort(S1) # sort copy of first half
- merge_sort(S2) # sort copy of second half
- # merge results
- merge(S1, S2, S) # merge sorted halves back into S
+ """Sort the elements of Python list S using the merge-sort algorithm."""
+ n = len(S)
+ if n < 2:
+ return # list is already sorted
+ # divide
+ mid = n // 2
+ S1 = S[0:mid] # copy of first half
+ S2 = S[mid:n] # copy of second half
+ # conquer (with recursion)
+ merge_sort(S1) # sort copy of first half
+ merge_sort(S2) # sort copy of second half
+ # merge results
+ merge(S1, S2, S) # merge sorted halves back into S
diff --git a/ch12/merge_nonrecur.py b/ch12/merge_nonrecur.py
index dcbdbeb..861edda 100644
--- a/ch12/merge_nonrecur.py
+++ b/ch12/merge_nonrecur.py
@@ -1,52 +1,33 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
import math
+
def merge(src, result, start, inc):
- """Merge src[start:start+inc] and src[start+inc:start+2*inc] into result."""
- end1 = start+inc # boundary for run 1
- end2 = min(start+2*inc, len(src)) # boundary for run 2
- x, y, z = start, start+inc, start # index into run 1, run 2, result
- while x < end1 and y < end2:
- if src[x] < src[y]:
- result[z] = src[x]
- x += 1
- else:
- result[z] = src[y]
- y += 1
- z += 1 # increment z to reflect new result
- if x < end1:
- result[z:end2] = src[x:end1] # copy remainder of run 1 to output
- elif y < end2:
- result[z:end2] = src[y:end2] # copy remainder of run 2 to output
+ """Merge src[start:start+inc] and src[start+inc:start+2*inc] into result."""
+ end1 = start + inc # boundary for run 1
+ end2 = min(start + 2 * inc, len(src)) # boundary for run 2
+ x, y, z = start, start + inc, start # index into run 1, run 2, result
+ while x < end1 and y < end2:
+ if src[x] < src[y]:
+ result[z] = src[x]
+ x += 1
+ else:
+ result[z] = src[y]
+ y += 1
+ z += 1 # increment z to reflect new result
+ if x < end1:
+ result[z:end2] = src[x:end1] # copy remainder of run 1 to output
+ elif y < end2:
+ result[z:end2] = src[y:end2] # copy remainder of run 2 to output
+
def merge_sort(S):
- """Sort the elements of Python list S using the merge-sort algorithm."""
- n = len(S)
- logn = math.ceil(math.log(n,2))
- src, dest = S, [None] * n # make temporary storage for dest
- for i in (2**k for k in range(logn)): # pass i creates all runs of length 2i
- for j in range(0, n, 2*i): # each pass merges two length i runs
- merge(src, dest, j, i)
- src, dest = dest, src # reverse roles of lists
- if S is not src:
- S[0:n] = src[0:n] # additional copy to get results to S
+ """Sort the elements of Python list S using the merge-sort algorithm."""
+ n = len(S)
+ logn = math.ceil(math.log(n, 2))
+ src, dest = S, [None] * n # make temporary storage for dest
+ for i in (2 ** k for k in range(logn)): # pass i creates all runs of length 2i
+ for j in range(0, n, 2 * i): # each pass merges two length i runs
+ merge(src, dest, j, i)
+ src, dest = dest, src # reverse roles of lists
+ if S is not src:
+ S[0:n] = src[0:n] # additional copy to get results to S
diff --git a/ch12/merge_queue.py b/ch12/merge_queue.py
index f318611..36eb3d1 100644
--- a/ch12/merge_queue.py
+++ b/ch12/merge_queue.py
@@ -1,52 +1,33 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
from ..ch07.linked_queue import LinkedQueue
+
def merge(S1, S2, S):
- """Merge two sorted queue instances S1 and S2 into empty queue S."""
- while not S1.is_empty() and not S2.is_empty():
- if S1.first() < S2.first():
- S.enqueue(S1.dequeue())
- else:
- S.enqueue(S2.dequeue())
- while not S1.is_empty(): # move remaining elements of S1 to S
- S.enqueue(S1.dequeue())
- while not S2.is_empty(): # move remaining elements of S2 to S
- S.enqueue(S2.dequeue())
+ """Merge two sorted queue instances S1 and S2 into empty queue S."""
+ while not S1.is_empty() and not S2.is_empty():
+ if S1.first() < S2.first():
+ S.enqueue(S1.dequeue())
+ else:
+ S.enqueue(S2.dequeue())
+ while not S1.is_empty(): # move remaining elements of S1 to S
+ S.enqueue(S1.dequeue())
+ while not S2.is_empty(): # move remaining elements of S2 to S
+ S.enqueue(S2.dequeue())
+
def merge_sort(S):
- """Sort the elements of queue S using the merge-sort algorithm."""
- n = len(S)
- if n < 2:
- return # list is already sorted
- # divide
- S1 = LinkedQueue() # or any other queue implementation
- S2 = LinkedQueue()
- while len(S1) < n // 2: # move the first n//2 elements to S1
- S1.enqueue(S.dequeue())
- while not S.is_empty(): # move the rest to S2
- S2.enqueue(S.dequeue())
- # conquer (with recursion)
- merge_sort(S1) # sort first half
- merge_sort(S2) # sort second half
- # merge results
- merge(S1, S2, S) # merge sorted halves back into S
+ """Sort the elements of queue S using the merge-sort algorithm."""
+ n = len(S)
+ if n < 2:
+ return # list is already sorted
+ # divide
+ S1 = LinkedQueue() # or any other queue implementation
+ S2 = LinkedQueue()
+ while len(S1) < n // 2: # move the first n//2 elements to S1
+ S1.enqueue(S.dequeue())
+ while not S.is_empty(): # move the rest to S2
+ S2.enqueue(S.dequeue())
+ # conquer (with recursion)
+ merge_sort(S1) # sort first half
+ merge_sort(S2) # sort second half
+ # merge results
+ merge(S1, S2, S) # merge sorted halves back into S
diff --git a/ch12/quick_inplace.py b/ch12/quick_inplace.py
index fd81ba3..7a883d5 100644
--- a/ch12/quick_inplace.py
+++ b/ch12/quick_inplace.py
@@ -1,43 +1,21 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
def inplace_quick_sort(S, a, b):
- """Sort the list from S[a] to S[b] inclusive using the quick-sort algorithm."""
- if a >= b: return # range is trivially sorted
- pivot = S[b] # last element of range is pivot
- left = a # will scan rightward
- right = b-1 # will scan leftward
- while left <= right:
- # scan until reaching value equal or larger than pivot (or right marker)
- while left <= right and S[left] < pivot:
- left += 1
- # scan until reaching value equal or smaller than pivot (or left marker)
- while left <= right and pivot < S[right]:
- right -= 1
- if left <= right: # scans did not strictly cross
- S[left], S[right] = S[right], S[left] # swap values
- left, right = left + 1, right - 1 # shrink range
-
- # put pivot into its final place (currently marked by left index)
- S[left], S[b] = S[b], S[left]
- # make recursive calls
- inplace_quick_sort(S, a, left - 1)
- inplace_quick_sort(S, left + 1, b)
+ """Sort the list from S[a] to S[b] inclusive using the quick-sort algorithm."""
+ if a >= b: return # range is trivially sorted
+ pivot = S[b] # last element of range is pivot
+ left = a # will scan rightward
+ right = b - 1 # will scan leftward
+ while left <= right:
+ # scan until reaching value equal or larger than pivot (or right marker)
+ while left <= right and S[left] < pivot:
+ left += 1
+ # scan until reaching value equal or smaller than pivot (or left marker)
+ while left <= right and pivot < S[right]:
+ right -= 1
+ if left <= right: # scans did not strictly cross
+ S[left], S[right] = S[right], S[left] # swap values
+ left, right = left + 1, right - 1 # shrink range
+ # put pivot into its final place (currently marked by left index)
+ S[left], S[b] = S[b], S[left]
+ # make recursive calls
+ inplace_quick_sort(S, a, left - 1)
+ inplace_quick_sort(S, left + 1, b)
diff --git a/ch12/quick_queue.py b/ch12/quick_queue.py
index 4a4b2d2..695dc91 100644
--- a/ch12/quick_queue.py
+++ b/ch12/quick_queue.py
@@ -1,50 +1,30 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
from ..ch07.linked_queue import LinkedQueue
+
def quick_sort(S):
- """Sort the elements of queue S using the quick-sort algorithm."""
- n = len(S)
- if n < 2:
- return # list is already sorted
- # divide
- p = S.first() # using first as arbitrary pivot
- L = LinkedQueue()
- E = LinkedQueue()
- G = LinkedQueue()
- while not S.is_empty(): # divide S into L, E, and G
- if S.first() < p:
- L.enqueue(S.dequeue())
- elif p < S.first():
- G.enqueue(S.dequeue())
- else: # S.first() must equal pivot
- E.enqueue(S.dequeue())
- # conquer (with recursion)
- quick_sort(L) # sort elements less than p
- quick_sort(G) # sort elements greater than p
- # concatenate results
- while not L.is_empty():
- S.enqueue(L.dequeue())
- while not E.is_empty():
- S.enqueue(E.dequeue())
- while not G.is_empty():
- S.enqueue(G.dequeue())
+ """Sort the elements of queue S using the quick-sort algorithm."""
+ n = len(S)
+ if n < 2:
+ return # list is already sorted
+ # divide
+ p = S.first() # using first as arbitrary pivot
+ L = LinkedQueue()
+ E = LinkedQueue()
+ G = LinkedQueue()
+ while not S.is_empty(): # divide S into L, E, and G
+ if S.first() < p:
+ L.enqueue(S.dequeue())
+ elif p < S.first():
+ G.enqueue(S.dequeue())
+ else: # S.first() must equal pivot
+ E.enqueue(S.dequeue())
+ # conquer (with recursion)
+ quick_sort(L) # sort elements less than p
+ quick_sort(G) # sort elements greater than p
+ # concatenate results
+ while not L.is_empty():
+ S.enqueue(L.dequeue())
+ while not E.is_empty():
+ S.enqueue(E.dequeue())
+ while not G.is_empty():
+ S.enqueue(G.dequeue())
diff --git a/ch12/quick_select.py b/ch12/quick_select.py
index c562010..057578a 100644
--- a/ch12/quick_select.py
+++ b/ch12/quick_select.py
@@ -1,38 +1,18 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
import random
+
def quick_select(S, k):
- """Return the kth smallest element of list S, for k from 1 to len(S)."""
- if len(S) == 1:
- return S[0]
- pivot = random.choice(S) # pick random pivot element from S
- L = [x for x in S if x < pivot] # elements less than pivot
- E = [x for x in S if x == pivot] # elements equal to pivot
- G = [x for x in S if pivot < x] # elements greater than pivot
- if k <= len(L):
- return quick_select(L, k) # kth smallest lies in L
- elif k <= len(L) + len(E):
- return pivot # kth smallest equal to pivot
- else:
- j = k - len(L) - len(E) # new selection parameter
- return quick_select(G, j) # kth smallest is jth in G
+ """Return the kth smallest element of list S, for k from 1 to len(S)."""
+ if len(S) == 1:
+ return S[0]
+ pivot = random.choice(S) # pick random pivot element from S
+ L = [x for x in S if x < pivot] # elements less than pivot
+ E = [x for x in S if x == pivot] # elements equal to pivot
+ G = [x for x in S if pivot < x] # elements greater than pivot
+ if k <= len(L):
+ return quick_select(L, k) # kth smallest lies in L
+ elif k <= len(L) + len(E):
+ return pivot # kth smallest equal to pivot
+ else:
+ j = k - len(L) - len(E) # new selection parameter
+ return quick_select(G, j) # kth smallest is jth in G
diff --git a/ch13/find_boyer_moore.py b/ch13/find_boyer_moore.py
index f6391fd..fb5184f 100644
--- a/ch13/find_boyer_moore.py
+++ b/ch13/find_boyer_moore.py
@@ -1,43 +1,22 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
def find_boyer_moore(T, P):
- """Return the lowest index of T at which substring P begins (or else -1)."""
- n, m = len(T), len(P) # introduce convenient notations
- if m == 0: return 0 # trivial search for empty string
- last = {} # build 'last' dictionary
- for k in range(m):
- last[ P[k] ] = k # later occurrence overwrites
- # align end of pattern at index m-1 of text
- i = m-1 # an index into T
- k = m-1 # an index into P
- while i < n:
- if T[i] == P[k]: # a matching character
- if k == 0:
- return i # pattern begins at index i of text
- else:
- i -= 1 # examine previous character
- k -= 1 # of both T and P
- else:
- j = last.get(T[i], -1) # last(T[i]) is -1 if not found
- i += m - min(k, j + 1) # case analysis for jump step
- k = m - 1 # restart at end of pattern
- return -1
+ """Return the lowest index of T at which substring P begins (or else -1)."""
+ n, m = len(T), len(P) # introduce convenient notations
+ if m == 0: return 0 # trivial search for empty string
+ last = {} # build 'last' dictionary
+ for k in range(m):
+ last[P[k]] = k # later occurrence overwrites
+ # align end of pattern at index m-1 of text
+ i = m - 1 # an index into T
+ k = m - 1 # an index into P
+ while i < n:
+ if T[i] == P[k]: # a matching character
+ if k == 0:
+ return i # pattern begins at index i of text
+ else:
+ i -= 1 # examine previous character
+ k -= 1 # of both T and P
+ else:
+ j = last.get(T[i], -1) # last(T[i]) is -1 if not found
+ i += m - min(k, j + 1) # case analysis for jump step
+ k = m - 1 # restart at end of pattern
+ return -1
diff --git a/ch13/find_brute.py b/ch13/find_brute.py
index a6c84f1..a9dcabc 100644
--- a/ch13/find_brute.py
+++ b/ch13/find_brute.py
@@ -1,31 +1,10 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
def find_brute(T, P):
- """Return the lowest index of T at which substring P begins (or else -1)."""
- n, m = len(T), len(P) # introduce convenient notations
- for i in range(n-m+1): # try every potential starting index within T
- k = 0 # an index into pattern P
- while k < m and T[i + k] == P[k]: # kth character of P matches
- k += 1
- if k == m: # if we reached the end of pattern,
- return i # substring T[i:i+m] matches P
- return -1 # failed to find a match starting with any i
+ """Return the lowest index of T at which substring P begins (or else -1)."""
+ n, m = len(T), len(P) # introduce convenient notations
+ for i in range(n - m + 1): # try every potential starting index within T
+ k = 0 # an index into pattern P
+ while k < m and T[i + k] == P[k]: # kth character of P matches
+ k += 1
+ if k == m: # if we reached the end of pattern,
+ return i # substring T[i:i+m] matches P
+ return -1 # failed to find a match starting with any i
diff --git a/ch13/find_kmp.py b/ch13/find_kmp.py
index ed973ed..2dbca8e 100644
--- a/ch13/find_kmp.py
+++ b/ch13/find_kmp.py
@@ -1,56 +1,36 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
def find_kmp(T, P):
- """Return the lowest index of T at which substring P begins (or else -1)."""
- n, m = len(T), len(P) # introduce convenient notations
- if m == 0: return 0 # trivial search for empty string
- fail = compute_kmp_fail(P) # rely on utility to precompute
- j = 0 # index into text
- k = 0 # index into pattern
- while j < n:
- if T[j] == P[k]: # P[0:1+k] matched thus far
- if k == m - 1: # match is complete
- return j - m + 1
- j += 1 # try to extend match
- k += 1
- elif k > 0:
- k = fail[k-1] # reuse suffix of P[0:k]
- else:
- j += 1
- return -1 # reached end without match
+ """Return the lowest index of T at which substring P begins (or else -1)."""
+ n, m = len(T), len(P) # introduce convenient notations
+ if m == 0: return 0 # trivial search for empty string
+ fail = compute_kmp_fail(P) # rely on utility to precompute
+ j = 0 # index into text
+ k = 0 # index into pattern
+ while j < n:
+ if T[j] == P[k]: # P[0:1+k] matched thus far
+ if k == m - 1: # match is complete
+ return j - m + 1
+ j += 1 # try to extend match
+ k += 1
+ elif k > 0:
+ k = fail[k - 1] # reuse suffix of P[0:k]
+ else:
+ j += 1
+ return -1 # reached end without match
+
def compute_kmp_fail(P):
- """Utility that computes and returns KMP 'fail' list."""
- m = len(P)
- fail = [0] * m # by default, presume overlap of 0 everywhere
- j = 1
- k = 0
- while j < m: # compute f(j) during this pass, if nonzero
- if P[j] == P[k]: # k + 1 characters match thus far
- fail[j] = k + 1
- j += 1
- k += 1
- elif k > 0: # k follows a matching prefix
- k = fail[k-1]
- else: # no match found starting at j
- j += 1
- return fail
+ """Utility that computes and returns KMP 'fail' list."""
+ m = len(P)
+ fail = [0] * m # by default, presume overlap of 0 everywhere
+ j = 1
+ k = 0
+ while j < m: # compute f(j) during this pass, if nonzero
+ if P[j] == P[k]: # k + 1 characters match thus far
+ fail[j] = k + 1
+ j += 1
+ k += 1
+ elif k > 0: # k follows a matching prefix
+ k = fail[k - 1]
+ else: # no match found starting at j
+ j += 1
+ return fail
diff --git a/ch13/lcs.py b/ch13/lcs.py
index 774e03a..8a59091 100644
--- a/ch13/lcs.py
+++ b/ch13/lcs.py
@@ -1,47 +1,27 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
def LCS(X, Y):
- """Return table such that L[j][k] is length of LCS for X[0:j] and Y[0:k]."""
- n, m = len(X), len(Y) # introduce convenient notations
- L = [[0] * (m+1) for k in range(n+1)] # (n+1) x (m+1) table
- for j in range(n):
- for k in range(m):
- if X[j] == Y[k]: # align this match
- L[j+1][k+1] = L[j][k] + 1
- else: # choose to ignore one character
- L[j+1][k+1] = max(L[j][k+1], L[j+1][k])
- return L
+ """Return table such that L[j][k] is length of LCS for X[0:j] and Y[0:k]."""
+ n, m = len(X), len(Y) # introduce convenient notations
+ L = [[0] * (m + 1) for k in range(n + 1)] # (n+1) x (m+1) table
+ for j in range(n):
+ for k in range(m):
+ if X[j] == Y[k]: # align this match
+ L[j + 1][k + 1] = L[j][k] + 1
+ else: # choose to ignore one character
+ L[j + 1][k + 1] = max(L[j][k + 1], L[j + 1][k])
+ return L
+
def LCS_solution(X, Y, L):
- """Return the longest common substring of X and Y, given LCS table L."""
- solution = []
- j,k = len(X), len(Y)
- while L[j][k] > 0: # common characters remain
- if X[j-1] == Y[k-1]:
- solution.append(X[j-1])
- j -= 1
- k -= 1
- elif L[j-1][k] >= L[j][k-1]:
- j -=1
- else:
- k -= 1
- return ''.join(reversed(solution)) # return left-to-right version
+ """Return the longest common substring of X and Y, given LCS table L."""
+ solution = []
+ j, k = len(X), len(Y)
+ while L[j][k] > 0: # common characters remain
+ if X[j - 1] == Y[k - 1]:
+ solution.append(X[j - 1])
+ j -= 1
+ k -= 1
+ elif L[j - 1][k] >= L[j][k - 1]:
+ j -= 1
+ else:
+ k -= 1
+ return ''.join(reversed(solution)) # return left-to-right version
diff --git a/ch13/matrix_chain.py b/ch13/matrix_chain.py
index 00393b5..6bb5ae5 100644
--- a/ch13/matrix_chain.py
+++ b/ch13/matrix_chain.py
@@ -1,37 +1,15 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
def matrix_chain(d):
- """Return solution to the matrix chain problem.
+ """Return solution to the matrix chain problem.
+ d is a list of n+1 numbers describing the dimensions of a chain of
+ n matrices such that kth matrix has dimensions d[k]-by-d[k+1].
- d is a list of n+1 numbers describing the dimensions of a chain of
- n matrices such that kth matrix has dimensions d[k]-by-d[k+1].
-
- Return an n-by-n table such that N[i][j] represents the minimum number of
- multiplications needed to compute the product of Ai through Aj inclusive.
- """
- n = len(d) - 1 # number of matrices
- N = [[0] * n for i in range(n)] # initialize n-by-n result to zero
- for b in range(1, n): # number of products in subchain
- for i in range(n-b): # start of subchain
- j = i + b # end of subchain
- N[i][j] = min(N[i][k] + N[k+1][j] + d[i]*d[k+1]*d[j+1] for k in range(i,j))
- return N
+ Return an n-by-n table such that N[i][j] represents the minimum number of
+ multiplications needed to compute the product of Ai through Aj inclusive.
+ """
+ n = len(d) - 1 # number of matrices
+ N = [[0] * n for i in range(n)] # initialize n-by-n result to zero
+ for b in range(1, n): # number of products in subchain
+ for i in range(n - b): # start of subchain
+ j = i + b # end of subchain
+ N[i][j] = min(N[i][k] + N[k + 1][j] + d[i] * d[k + 1] * d[j + 1] for k in range(i, j))
+ return N
diff --git a/ch14/__init__.py b/ch14/__init__.py
index f156b8c..a76854b 100644
--- a/ch14/__init__.py
+++ b/ch14/__init__.py
@@ -1 +1,2 @@
-__all__ = ['bfs', 'dfs', 'graph', 'graph_examples', 'mst', 'partition', 'shortest_paths', 'topological_sort', 'transitive_closure']
+__all__ = ['bfs', 'dfs', 'graph', 'graph_examples', 'mst', 'partition', 'shortest_paths', 'topological_sort',
+ 'transitive_closure']
diff --git a/ch14/bfs.py b/ch14/bfs.py
index ca8bfb2..e291a9b 100644
--- a/ch14/bfs.py
+++ b/ch14/bfs.py
@@ -1,51 +1,29 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
def BFS(g, s, discovered):
- """Perform BFS of the undiscovered portion of Graph g starting at Vertex s.
+ """Perform BFS of the undiscovered portion of Graph g starting at Vertex s.
+ discovered is a dictionary mapping each vertex to the edge that was used to
+ discover it during the BFS (s should be mapped to None prior to the call).
+ Newly discovered vertices will be added to the dictionary as a result.
+ """
+ level = [s] # first level includes only s
+ while len(level) > 0:
+ next_level = [] # prepare to gather newly found vertices
+ for u in level:
+ for e in g.incident_edges(u): # for every outgoing edge from u
+ v = e.opposite(u)
+ if v not in discovered: # v is an unvisited vertex
+ discovered[v] = e # e is the tree edge that discovered v
+ next_level.append(v) # v will be further considered in next pass
+ level = next_level # relabel 'next' level to become current
- discovered is a dictionary mapping each vertex to the edge that was used to
- discover it during the BFS (s should be mapped to None prior to the call).
- Newly discovered vertices will be added to the dictionary as a result.
- """
- level = [s] # first level includes only s
- while len(level) > 0:
- next_level = [] # prepare to gather newly found vertices
- for u in level:
- for e in g.incident_edges(u): # for every outgoing edge from u
- v = e.opposite(u)
- if v not in discovered: # v is an unvisited vertex
- discovered[v] = e # e is the tree edge that discovered v
- next_level.append(v) # v will be further considered in next pass
- level = next_level # relabel 'next' level to become current
def BFS_complete(g):
- """Perform BFS for entire graph and return forest as a dictionary.
-
- Result maps each vertex v to the edge that was used to discover it.
- (vertices that are roots of a BFS tree are mapped to None).
- """
- forest = {}
- for u in g.vertices():
- if u not in forest:
- forest[u] = None # u will be a root of a tree
- BFS(g, u, forest)
- return forest
+ """Perform BFS for entire graph and return forest as a dictionary.
+ Result maps each vertex v to the edge that was used to discover it.
+ (vertices that are roots of a BFS tree are mapped to None).
+ """
+ forest = {}
+ for u in g.vertices():
+ if u not in forest:
+ forest[u] = None # u will be a root of a tree
+ BFS(g, u, forest)
+ return forest
diff --git a/ch14/dfs.py b/ch14/dfs.py
index 76f2013..d042ff8 100644
--- a/ch14/dfs.py
+++ b/ch14/dfs.py
@@ -1,66 +1,44 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
def DFS(g, u, discovered):
- """Perform DFS of the undiscovered portion of Graph g starting at Vertex u.
+ """Perform DFS of the undiscovered portion of Graph g starting at Vertex u.
+ discovered is a dictionary mapping each vertex to the edge that was used to
+ discover it during the DFS. (u should be "discovered" prior to the call.)
+ Newly discovered vertices will be added to the dictionary as a result.
+ """
+ for e in g.incident_edges(u): # for every outgoing edge from u
+ v = e.opposite(u)
+ if v not in discovered: # v is an unvisited vertex
+ discovered[v] = e # e is the tree edge that discovered v
+ DFS(g, v, discovered) # recursively explore from v
- discovered is a dictionary mapping each vertex to the edge that was used to
- discover it during the DFS. (u should be "discovered" prior to the call.)
- Newly discovered vertices will be added to the dictionary as a result.
- """
- for e in g.incident_edges(u): # for every outgoing edge from u
- v = e.opposite(u)
- if v not in discovered: # v is an unvisited vertex
- discovered[v] = e # e is the tree edge that discovered v
- DFS(g, v, discovered) # recursively explore from v
def construct_path(u, v, discovered):
- """
- Return a list of vertices comprising the directed path from u to v,
- or an empty list if v is not reachable from u.
+ """
+ Return a list of vertices comprising the directed path from u to v,
+ or an empty list if v is not reachable from u.
+ discovered is a dictionary resulting from a previous call to DFS started at u.
+ """
+ path = [] # empty path by default
+ if v in discovered:
+ # we build list from v to u and then reverse it at the end
+ path.append(v)
+ walk = v
+ while walk is not u:
+ e = discovered[walk] # find edge leading to walk
+ parent = e.opposite(walk)
+ path.append(parent)
+ walk = parent
+ path.reverse() # reorient path from u to v
+ return path
- discovered is a dictionary resulting from a previous call to DFS started at u.
- """
- path = [] # empty path by default
- if v in discovered:
- # we build list from v to u and then reverse it at the end
- path.append(v)
- walk = v
- while walk is not u:
- e = discovered[walk] # find edge leading to walk
- parent = e.opposite(walk)
- path.append(parent)
- walk = parent
- path.reverse() # reorient path from u to v
- return path
def DFS_complete(g):
- """Perform DFS for entire graph and return forest as a dictionary.
-
- Result maps each vertex v to the edge that was used to discover it.
- (Vertices that are roots of a DFS tree are mapped to None.)
- """
- forest = {}
- for u in g.vertices():
- if u not in forest:
- forest[u] = None # u will be the root of a tree
- DFS(g, u, forest)
- return forest
+ """Perform DFS for entire graph and return forest as a dictionary.
+ Result maps each vertex v to the edge that was used to discover it.
+ (Vertices that are roots of a DFS tree are mapped to None.)
+ """
+ forest = {}
+ for u in g.vertices():
+ if u not in forest:
+ forest[u] = None # u will be the root of a tree
+ DFS(g, u, forest)
+ return forest
diff --git a/ch14/graph.py b/ch14/graph.py
index d92ea66..a6e2579 100644
--- a/ch14/graph.py
+++ b/ch14/graph.py
@@ -1,164 +1,138 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
class Graph:
- """Representation of a simple graph using an adjacency map."""
-
- #------------------------- nested Vertex class -------------------------
- class Vertex:
- """Lightweight vertex structure for a graph."""
- __slots__ = '_element'
-
- def __init__(self, x):
- """Do not call constructor directly. Use Graph's insert_vertex(x)."""
- self._element = x
-
- def element(self):
- """Return element associated with this vertex."""
- return self._element
-
- def __hash__(self): # will allow vertex to be a map/set key
- return hash(id(self))
-
- def __str__(self):
- return str(self._element)
-
- #------------------------- nested Edge class -------------------------
- class Edge:
- """Lightweight edge structure for a graph."""
- __slots__ = '_origin', '_destination', '_element'
-
- def __init__(self, u, v, x):
- """Do not call constructor directly. Use Graph's insert_edge(u,v,x)."""
- self._origin = u
- self._destination = v
- self._element = x
-
- def endpoints(self):
- """Return (u,v) tuple for vertices u and v."""
- return (self._origin, self._destination)
-
- def opposite(self, v):
- """Return the vertex that is opposite v on this edge."""
- if not isinstance(v, Graph.Vertex):
- raise TypeError('v must be a Vertex')
- return self._destination if v is self._origin else self._origin
- raise ValueError('v not incident to edge')
-
- def element(self):
- """Return element associated with this edge."""
- return self._element
-
- def __hash__(self): # will allow edge to be a map/set key
- return hash( (self._origin, self._destination) )
-
- def __str__(self):
- return '({0},{1},{2})'.format(self._origin,self._destination,self._element)
-
- #------------------------- Graph methods -------------------------
- def __init__(self, directed=False):
- """Create an empty graph (undirected, by default).
-
- Graph is directed if optional paramter is set to True.
- """
- self._outgoing = {}
- # only create second map for directed graph; use alias for undirected
- self._incoming = {} if directed else self._outgoing
-
- def _validate_vertex(self, v):
- """Verify that v is a Vertex of this graph."""
- if not isinstance(v, self.Vertex):
- raise TypeError('Vertex expected')
- if v not in self._outgoing:
- raise ValueError('Vertex does not belong to this graph.')
-
- def is_directed(self):
- """Return True if this is a directed graph; False if undirected.
-
- Property is based on the original declaration of the graph, not its contents.
- """
- return self._incoming is not self._outgoing # directed if maps are distinct
-
- def vertex_count(self):
- """Return the number of vertices in the graph."""
- return len(self._outgoing)
-
- def vertices(self):
- """Return an iteration of all vertices of the graph."""
- return self._outgoing.keys()
-
- def edge_count(self):
- """Return the number of edges in the graph."""
- total = sum(len(self._outgoing[v]) for v in self._outgoing)
- # for undirected graphs, make sure not to double-count edges
- return total if self.is_directed() else total // 2
-
- def edges(self):
- """Return a set of all edges of the graph."""
- result = set() # avoid double-reporting edges of undirected graph
- for secondary_map in self._outgoing.values():
- result.update(secondary_map.values()) # add edges to resulting set
- return result
-
- def get_edge(self, u, v):
- """Return the edge from u to v, or None if not adjacent."""
- self._validate_vertex(u)
- self._validate_vertex(v)
- return self._outgoing[u].get(v) # returns None if v not adjacent
-
- def degree(self, v, outgoing=True):
- """Return number of (outgoing) edges incident to vertex v in the graph.
-
- If graph is directed, optional parameter used to count incoming edges.
- """
- self._validate_vertex(v)
- adj = self._outgoing if outgoing else self._incoming
- return len(adj[v])
-
- def incident_edges(self, v, outgoing=True):
- """Return all (outgoing) edges incident to vertex v in the graph.
-
- If graph is directed, optional parameter used to request incoming edges.
- """
- self._validate_vertex(v)
- adj = self._outgoing if outgoing else self._incoming
- for edge in adj[v].values():
- yield edge
-
- def insert_vertex(self, x=None):
- """Insert and return a new Vertex with element x."""
- v = self.Vertex(x)
- self._outgoing[v] = {}
- if self.is_directed():
- self._incoming[v] = {} # need distinct map for incoming edges
- return v
-
- def insert_edge(self, u, v, x=None):
- """Insert and return a new Edge from u to v with auxiliary element x.
-
- Raise a ValueError if u and v are not vertices of the graph.
- Raise a ValueError if u and v are already adjacent.
- """
- if self.get_edge(u, v) is not None: # includes error checking
- raise ValueError('u and v are already adjacent')
- e = self.Edge(u, v, x)
- self._outgoing[u][v] = e
- self._incoming[v][u] = e
+ """Representation of a simple graph using an adjacency map."""
+
+ # ------------------------- nested Vertex class -------------------------
+ class Vertex:
+ """Lightweight vertex structure for a graph."""
+ __slots__ = '_element'
+
+ def __init__(self, x):
+ """Do not call constructor directly. Use Graph's insert_vertex(x)."""
+ self._element = x
+
+ def element(self):
+ """Return element associated with this vertex."""
+ return self._element
+
+ def __hash__(self): # will allow vertex to be a map/set key
+ return hash(id(self))
+
+ def __str__(self):
+ return str(self._element)
+
+ # ------------------------- nested Edge class -------------------------
+ class Edge:
+ """Lightweight edge structure for a graph."""
+ __slots__ = '_origin', '_destination', '_element'
+
+ def __init__(self, u, v, x):
+ """Do not call constructor directly. Use Graph's insert_edge(u,v,x)."""
+ self._origin = u
+ self._destination = v
+ self._element = x
+
+ def endpoints(self):
+ """Return (u,v) tuple for vertices u and v."""
+ return (self._origin, self._destination)
+
+ def opposite(self, v):
+ """Return the vertex that is opposite v on this edge."""
+ if not isinstance(v, Graph.Vertex):
+ raise TypeError('v must be a Vertex')
+ return self._destination if v is self._origin else self._origin
+ raise ValueError('v not incident to edge')
+
+ def element(self):
+ """Return element associated with this edge."""
+ return self._element
+
+ def __hash__(self): # will allow edge to be a map/set key
+ return hash((self._origin, self._destination))
+
+ def __str__(self):
+ return '({0},{1},{2})'.format(self._origin, self._destination, self._element)
+
+ # ------------------------- Graph methods -------------------------
+ def __init__(self, directed=False):
+ """Create an empty graph (undirected, by default).
+ Graph is directed if optional paramter is set to True.
+ """
+ self._outgoing = {}
+ # only create second map for directed graph; use alias for undirected
+ self._incoming = {} if directed else self._outgoing
+
+ def _validate_vertex(self, v):
+ """Verify that v is a Vertex of this graph."""
+ if not isinstance(v, self.Vertex):
+ raise TypeError('Vertex expected')
+ if v not in self._outgoing:
+ raise ValueError('Vertex does not belong to this graph.')
+
+ def is_directed(self):
+ """Return True if this is a directed graph; False if undirected.
+ Property is based on the original declaration of the graph, not its contents.
+ """
+ return self._incoming is not self._outgoing # directed if maps are distinct
+
+ def vertex_count(self):
+ """Return the number of vertices in the graph."""
+ return len(self._outgoing)
+
+ def vertices(self):
+ """Return an iteration of all vertices of the graph."""
+ return self._outgoing.keys()
+
+ def edge_count(self):
+ """Return the number of edges in the graph."""
+ total = sum(len(self._outgoing[v]) for v in self._outgoing)
+ # for undirected graphs, make sure not to double-count edges
+ return total if self.is_directed() else total // 2
+
+ def edges(self):
+ """Return a set of all edges of the graph."""
+ result = set() # avoid double-reporting edges of undirected graph
+ for secondary_map in self._outgoing.values():
+ result.update(secondary_map.values()) # add edges to resulting set
+ return result
+
+ def get_edge(self, u, v):
+ """Return the edge from u to v, or None if not adjacent."""
+ self._validate_vertex(u)
+ self._validate_vertex(v)
+ return self._outgoing[u].get(v) # returns None if v not adjacent
+
+ def degree(self, v, outgoing=True):
+ """Return number of (outgoing) edges incident to vertex v in the graph.
+ If graph is directed, optional parameter used to count incoming edges.
+ """
+ self._validate_vertex(v)
+ adj = self._outgoing if outgoing else self._incoming
+ return len(adj[v])
+
+ def incident_edges(self, v, outgoing=True):
+ """Return all (outgoing) edges incident to vertex v in the graph.
+ If graph is directed, optional parameter used to request incoming edges.
+ """
+ self._validate_vertex(v)
+ adj = self._outgoing if outgoing else self._incoming
+ for edge in adj[v].values():
+ yield edge
+
+ def insert_vertex(self, x=None):
+ """Insert and return a new Vertex with element x."""
+ v = self.Vertex(x)
+ self._outgoing[v] = {}
+ if self.is_directed():
+ self._incoming[v] = {} # need distinct map for incoming edges
+ return v
+
+ def insert_edge(self, u, v, x=None):
+ """Insert and return a new Edge from u to v with auxiliary element x.
+ Raise a ValueError if u and v are not vertices of the graph.
+ Raise a ValueError if u and v are already adjacent.
+ """
+ if self.get_edge(u, v) is not None: # includes error checking
+ raise ValueError('u and v are already adjacent')
+ e = self.Edge(u, v, x)
+ self._outgoing[u][v] = e
+ self._incoming[v][u] = e
diff --git a/ch14/graph_examples.py b/ch14/graph_examples.py
index 693c676..72f5193 100644
--- a/ch14/graph_examples.py
+++ b/ch14/graph_examples.py
@@ -1,135 +1,116 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
from .graph import Graph
-def graph_from_edgelist(E, directed=False):
- """Make a graph instance based on a sequence of edge tuples.
-
- Edges can be either of from (origin,destination) or
- (origin,destination,element). Vertex set is presume to be those
- incident to at least one edge.
-
- vertex labels are assumed to be hashable.
- """
- g = Graph(directed)
- V = set()
- for e in E:
- V.add(e[0])
- V.add(e[1])
- verts = {} # map from vertex label to Vertex instance
- for v in V:
- verts[v] = g.insert_vertex(v)
-
- for e in E:
- src = e[0]
- dest = e[1]
- element = e[2] if len(e) > 2 else None
- g.insert_edge(verts[src],verts[dest],element)
+def graph_from_edgelist(E, directed=False):
+ """Make a graph instance based on a sequence of edge tuples.
+ Edges can be either of from (origin,destination) or
+ (origin,destination,element). Vertex set is presume to be those
+ incident to at least one edge.
+ vertex labels are assumed to be hashable.
+ """
+ g = Graph(directed)
+ V = set()
+ for e in E:
+ V.add(e[0])
+ V.add(e[1])
+ verts = {} # map from vertex label to Vertex instance
+ for v in V:
+ verts[v] = g.insert_vertex(v)
+ for e in E:
+ src = e[0]
+ dest = e[1]
+ element = e[2] if len(e) > 2 else None
+ g.insert_edge(verts[src], verts[dest], element)
+ return g
- return g
def figure_14_3():
- """Return the unweighted, directed graph from Figure 14.3 of DSAP."""
- E = (
- ('BOS','SFO'), ('BOS','JFK'), ('BOS','MIA'), ('JFK','BOS'),
- ('JFK','DFW'), ('JFK','MIA'), ('JFK','SFO'), ('ORD','DFW'),
- ('ORD','MIA'), ('LAX','ORD'), ('DFW','SFO'), ('DFW','ORD'),
- ('DFW','LAX'), ('MIA','DFW'), ('MIA','LAX'),
+ """Return the unweighted, directed graph from Figure 14.3 of DSAP."""
+ E = (
+ ('BOS', 'SFO'), ('BOS', 'JFK'), ('BOS', 'MIA'), ('JFK', 'BOS'),
+ ('JFK', 'DFW'), ('JFK', 'MIA'), ('JFK', 'SFO'), ('ORD', 'DFW'),
+ ('ORD', 'MIA'), ('LAX', 'ORD'), ('DFW', 'SFO'), ('DFW', 'ORD'),
+ ('DFW', 'LAX'), ('MIA', 'DFW'), ('MIA', 'LAX'),
)
- return graph_from_edgelist(E, True)
+ return graph_from_edgelist(E, True)
+
def figure_14_8():
- """Return the unweighted, directed graph from Figure 14.8 of DSAP."""
- E = (
- ('BOS','SFO'), ('BOS','JFK'), ('BOS','MIA'), ('JFK','BOS'),
- ('JFK','DFW'), ('JFK','MIA'), ('JFK','SFO'), ('ORD','DFW'),
- ('ORD','MIA'), ('LAX','ORD'), ('DFW','SFO'), ('DFW','ORD'),
- ('DFW','LAX'), ('MIA','DFW'), ('MIA','LAX'), ('SFO','LAX'),
+ """Return the unweighted, directed graph from Figure 14.8 of DSAP."""
+ E = (
+ ('BOS', 'SFO'), ('BOS', 'JFK'), ('BOS', 'MIA'), ('JFK', 'BOS'),
+ ('JFK', 'DFW'), ('JFK', 'MIA'), ('JFK', 'SFO'), ('ORD', 'DFW'),
+ ('ORD', 'MIA'), ('LAX', 'ORD'), ('DFW', 'SFO'), ('DFW', 'ORD'),
+ ('DFW', 'LAX'), ('MIA', 'DFW'), ('MIA', 'LAX'), ('SFO', 'LAX'),
)
- return graph_from_edgelist(E, True)
+ return graph_from_edgelist(E, True)
+
def figure_14_9():
- """Return the unweighted, undirected graph from Figure 14.9 of DSAP.
-
- This is the same graph as in Figure 14.10.
- """
- E = (
- ('A','B'), ('A','E'), ('A','F'), ('B','C'), ('B','F'),
- ('C','D'), ('C','G'), ('D','G'), ('D','H'), ('E','F'),
- ('E','I'), ('F' 'I'), ('G','J'), ('G','K'), ('G','L'),
- ('H','L'), ('I','J'), ('I','M'), ('I','N'), ('J','K'),
- ('K','N'), ('K','O'), ('L','P'), ('M','N'),
+ """Return the unweighted, undirected graph from Figure 14.9 of DSAP.
+ This is the same graph as in Figure 14.10.
+ """
+ E = (
+ ('A', 'B'), ('A', 'E'), ('A', 'F'), ('B', 'C'), ('B', 'F'),
+ ('C', 'D'), ('C', 'G'), ('D', 'G'), ('D', 'H'), ('E', 'F'),
+ ('E', 'I'), ('F' 'I'), ('G', 'J'), ('G', 'K'), ('G', 'L'),
+ ('H', 'L'), ('I', 'J'), ('I', 'M'), ('I', 'N'), ('J', 'K'),
+ ('K', 'N'), ('K', 'O'), ('L', 'P'), ('M', 'N'),
)
- return graph_from_edgelist(E, False)
+ return graph_from_edgelist(E, False)
+
+
+figure_14_10 = figure_14_9 # same graph
-figure_14_10 = figure_14_9 # same graph
def figure_14_11():
- """Return the unweighted, directed graph from Figure 14.11 of DSAP."""
- E = (
- ('BOS','JFK'), ('BOS','MIA'), ('JFK','BOS'), ('JFK','DFW'),
- ('JFK','MIA'), ('JFK','SFO'), ('ORD','DFW'),
- ('LAX','ORD'), ('DFW','SFO'), ('DFW','ORD'),
- ('DFW','LAX'), ('MIA','DFW'), ('MIA','LAX'),
+ """Return the unweighted, directed graph from Figure 14.11 of DSAP."""
+ E = (
+ ('BOS', 'JFK'), ('BOS', 'MIA'), ('JFK', 'BOS'), ('JFK', 'DFW'),
+ ('JFK', 'MIA'), ('JFK', 'SFO'), ('ORD', 'DFW'),
+ ('LAX', 'ORD'), ('DFW', 'SFO'), ('DFW', 'ORD'),
+ ('DFW', 'LAX'), ('MIA', 'DFW'), ('MIA', 'LAX'),
)
- return graph_from_edgelist(E, True)
+ return graph_from_edgelist(E, True)
-def figure_14_12():
- """Return the unweighted, directed graph from Figure 14.12 of DSAP.
- This is the same graph as Figure 14.13.
- """
- E = (
- ('A','C'), ('A','D'), ('B','D'), ('B', 'F'), ('C','D'), ('C','E'),
- ('C','H'), ('D','F'), ('E','G'), ('F','G'), ('F','H'), ('G','H')
+def figure_14_12():
+ """Return the unweighted, directed graph from Figure 14.12 of DSAP.
+ This is the same graph as Figure 14.13.
+ """
+ E = (
+ ('A', 'C'), ('A', 'D'), ('B', 'D'), ('B', 'F'), ('C', 'D'), ('C', 'E'),
+ ('C', 'H'), ('D', 'F'), ('E', 'G'), ('F', 'G'), ('F', 'H'), ('G', 'H')
)
- return graph_from_edgelist(E, True)
+ return graph_from_edgelist(E, True)
+
+
+figure_14_13 = figure_14_12 # same graph
-figure_14_13 = figure_14_12 # same graph
def figure_14_14():
- """Return the weighted, undirected graph from Figure 14.14 of DSAP."""
- E = (
- ('SFO', 'LAX', 337), ('SFO', 'BOS', 2704), ('SFO', 'ORD', 1846),
- ('SFO', 'DFW', 1464), ('LAX', 'DFW', 1235), ('LAX', 'MIA', 2342),
- ('DFW', 'ORD', 802), ('DFW', 'MIA', 1121), ('ORD', 'BOS', 867),
- ('ORD', 'JFK', 740), ('MIA', 'JFK', 1090), ('MIA', 'BOS', 1258),
- ('JFK', 'BOS', 187),
+ """Return the weighted, undirected graph from Figure 14.14 of DSAP."""
+ E = (
+ ('SFO', 'LAX', 337), ('SFO', 'BOS', 2704), ('SFO', 'ORD', 1846),
+ ('SFO', 'DFW', 1464), ('LAX', 'DFW', 1235), ('LAX', 'MIA', 2342),
+ ('DFW', 'ORD', 802), ('DFW', 'MIA', 1121), ('ORD', 'BOS', 867),
+ ('ORD', 'JFK', 740), ('MIA', 'JFK', 1090), ('MIA', 'BOS', 1258),
+ ('JFK', 'BOS', 187),
)
- return graph_from_edgelist(E, False)
+ return graph_from_edgelist(E, False)
+
def figure_14_15():
- """Return the weighted, undirected graph from Figure 14.15 of DSAP.
-
- This is the same graph as in Figures 14.16, 14.17, and and 14.20-14.24.
- """
- E = (
- ('SFO', 'LAX', 337), ('SFO', 'BOS', 2704), ('SFO', 'ORD', 1846),
- ('SFO', 'DFW', 1464), ('LAX', 'DFW', 1235), ('LAX', 'MIA', 2342),
- ('DFW', 'ORD', 802), ('DFW', 'JFK', 1391), ('DFW', 'MIA', 1121),
- ('ORD', 'BOS', 867), ('ORD', 'PVD', 849), ('ORD', 'JFK', 740),
- ('ORD', 'BWI', 621), ('MIA', 'BWI', 946), ('MIA', 'JFK', 1090),
- ('MIA', 'BOS', 1258), ('BWI', 'JFK', 184), ('JFK', 'PVD', 144),
- ('JFK', 'BOS', 187),
+ """Return the weighted, undirected graph from Figure 14.15 of DSAP.
+ This is the same graph as in Figures 14.16, 14.17, and and 14.20-14.24.
+ """
+ E = (
+ ('SFO', 'LAX', 337), ('SFO', 'BOS', 2704), ('SFO', 'ORD', 1846),
+ ('SFO', 'DFW', 1464), ('LAX', 'DFW', 1235), ('LAX', 'MIA', 2342),
+ ('DFW', 'ORD', 802), ('DFW', 'JFK', 1391), ('DFW', 'MIA', 1121),
+ ('ORD', 'BOS', 867), ('ORD', 'PVD', 849), ('ORD', 'JFK', 740),
+ ('ORD', 'BWI', 621), ('MIA', 'BWI', 946), ('MIA', 'JFK', 1090),
+ ('MIA', 'BOS', 1258), ('BWI', 'JFK', 184), ('JFK', 'PVD', 144),
+ ('JFK', 'BOS', 187),
)
- return graph_from_edgelist(E, False)
+ return graph_from_edgelist(E, False)
diff --git a/ch14/mst.py b/ch14/mst.py
index 9d8debe..c4361e0 100644
--- a/ch14/mst.py
+++ b/ch14/mst.py
@@ -1,90 +1,61 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
-from ..ch09.pq import HeapPriorityQueue,AdaptableHeapPriorityQueue
+from ..ch09.pq import HeapPriorityQueue, AdaptableHeapPriorityQueue
from .partition import Partition
-def MST_PrimJarnik(g):
- """Compute a minimum spanning tree of weighted graph g.
-
- Return a list of edges that comprise the MST (in arbitrary order).
- """
- d = {} # d[v] is bound on distance to tree
- tree = [] # list of edges in spanning tree
- pq = AdaptableHeapPriorityQueue() # d[v] maps to value (v, e=(u,v))
- pqlocator = {} # map from vertex to its pq locator
- # for each vertex v of the graph, add an entry to the priority queue, with
- # the source having distance 0 and all others having infinite distance
- for v in g.vertices():
- if len(d) == 0: # this is the first node
- d[v] = 0 # make it the root
- else:
- d[v] = float('inf') # positive infinity
- pqlocator[v] = pq.add(d[v], (v,None))
-
- while not pq.is_empty():
- key,value = pq.remove_min()
- u,edge = value # unpack tuple from pq
- del pqlocator[u] # u is no longer in pq
- if edge is not None:
- tree.append(edge) # add edge to tree
- for link in g.incident_edges(u):
- v = link.opposite(u)
- if v in pqlocator: # thus v not yet in tree
- # see if edge (u,v) better connects v to the growing tree
- wgt = link.element()
- if wgt < d[v]: # better edge to v?
- d[v] = wgt # update the distance
- pq.update(pqlocator[v], d[v], (v, link)) # update the pq entry
+def MST_PrimJarnik(g):
+ """Compute a minimum spanning tree of weighted graph g.
+ Return a list of edges that comprise the MST (in arbitrary order).
+ """
+ d = {} # d[v] is bound on distance to tree
+ tree = [] # list of edges in spanning tree
+ pq = AdaptableHeapPriorityQueue() # d[v] maps to value (v, e=(u,v))
+ pqlocator = {} # map from vertex to its pq locator
+ # for each vertex v of the graph, add an entry to the priority queue, with
+ # the source having distance 0 and all others having infinite distance
+ for v in g.vertices():
+ if len(d) == 0: # this is the first node
+ d[v] = 0 # make it the root
+ else:
+ d[v] = float('inf') # positive infinity
+ pqlocator[v] = pq.add(d[v], (v, None))
+ while not pq.is_empty():
+ key, value = pq.remove_min()
+ u, edge = value # unpack tuple from pq
+ del pqlocator[u] # u is no longer in pq
+ if edge is not None:
+ tree.append(edge) # add edge to tree
+ for link in g.incident_edges(u):
+ v = link.opposite(u)
+ if v in pqlocator: # thus v not yet in tree
+ # see if edge (u,v) better connects v to the growing tree
+ wgt = link.element()
+ if wgt < d[v]: # better edge to v?
+ d[v] = wgt # update the distance
+ pq.update(pqlocator[v], d[v], (v, link)) # update the pq entry
+ return tree
- return tree
def MST_Kruskal(g):
- """Compute a minimum spanning tree of a graph using Kruskal's algorithm.
-
- Return a list of edges that comprise the MST.
-
- The elements of the graph's edges are assumed to be weights.
- """
- tree = [] # list of edges in spanning tree
- pq = HeapPriorityQueue() # entries are edges in G, with weights as key
- forest = Partition() # keeps track of forest clusters
- position = {} # map each node to its Partition entry
-
- for v in g.vertices():
- position[v] = forest.make_group(v)
-
- for e in g.edges():
- pq.add(e.element(), e) # edge's element is assumed to be its weight
-
- size = g.vertex_count()
- while len(tree) != size - 1 and not pq.is_empty():
- # tree not spanning and unprocessed edges remain
- weight,edge = pq.remove_min()
- u,v = edge.endpoints()
- a = forest.find(position[u])
- b = forest.find(position[v])
- if a != b:
- tree.append(edge)
- forest.union(a,b)
-
- return tree
+ """Compute a minimum spanning tree of a graph using Kruskal's algorithm.
+ Return a list of edges that comprise the MST.
+ The elements of the graph's edges are assumed to be weights.
+ """
+ tree = [] # list of edges in spanning tree
+ pq = HeapPriorityQueue() # entries are edges in G, with weights as key
+ forest = Partition() # keeps track of forest clusters
+ position = {} # map each node to its Partition entry
+ for v in g.vertices():
+ position[v] = forest.make_group(v)
+ for e in g.edges():
+ pq.add(e.element(), e) # edge's element is assumed to be its weight
+ size = g.vertex_count()
+ while len(tree) != size - 1 and not pq.is_empty():
+ # tree not spanning and unprocessed edges remain
+ weight, edge = pq.remove_min()
+ u, v = edge.endpoints()
+ a = forest.find(position[u])
+ b = forest.find(position[v])
+ if a != b:
+ tree.append(edge)
+ forest.union(a, b)
+ return tree
diff --git a/ch14/partition.py b/ch14/partition.py
index 5ed9e4b..d27aa63 100644
--- a/ch14/partition.py
+++ b/ch14/partition.py
@@ -1,69 +1,48 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
class Partition:
- """Union-find structure for maintaining disjoint sets."""
-
- #------------------------- nested Position class -------------------------
- class Position:
- __slots__ = '_container', '_element', '_size', '_parent'
+ """Union-find structure for maintaining disjoint sets."""
+
+ # ------------------------- nested Position class -------------------------
+ class Position:
+ __slots__ = '_container', '_element', '_size', '_parent'
+
+ def __init__(self, container, e):
+ """Create a new position that is the leader of its own group."""
+ self._container = container # reference to Partition instance
+ self._element = e
+ self._size = 1
+ self._parent = self # convention for a group leader
+
+ def element(self):
+ """Return element stored at this position."""
+ return self._element
- def __init__(self, container, e):
- """Create a new position that is the leader of its own group."""
- self._container = container # reference to Partition instance
- self._element = e
- self._size = 1
- self._parent = self # convention for a group leader
+ # ------------------------- nonpublic utility -------------------------
+ def _validate(self, p):
+ if not isinstance(p, self.Position):
+ raise TypeError('p must be proper Position type')
+ if p._container is not self:
+ raise ValueError('p does not belong to this container')
- def element(self):
- """Return element stored at this position."""
- return self._element
+ # ------------------------- public Partition methods -------------------------
+ def make_group(self, e):
+ """Makes a new group containing element e, and returns its Position."""
+ return self.Position(self, e)
- #------------------------- nonpublic utility -------------------------
- def _validate(self, p):
- if not isinstance(p, self.Position):
- raise TypeError('p must be proper Position type')
- if p._container is not self:
- raise ValueError('p does not belong to this container')
-
- #------------------------- public Partition methods -------------------------
- def make_group(self, e):
- """Makes a new group containing element e, and returns its Position."""
- return self.Position(self, e)
+ def find(self, p):
+ """Finds the group containging p and return the position of its leader."""
+ self._validate(p)
+ if p._parent != p:
+ p._parent = self.find(p._parent) # overwrite p._parent after recursion
+ return p._parent
- def find(self, p):
- """Finds the group containging p and return the position of its leader."""
- self._validate(p)
- if p._parent != p:
- p._parent = self.find(p._parent) # overwrite p._parent after recursion
- return p._parent
-
- def union(self, p, q):
- """Merges the groups containg elements p and q (if distinct)."""
- a = self.find(p)
- b = self.find(q)
- if a is not b: # only merge if different groups
- if a._size > b._size:
- b._parent = a
- a._size += b._size
- else:
- a._parent = b
- b._size += a._size
+ def union(self, p, q):
+ """Merges the groups containg elements p and q (if distinct)."""
+ a = self.find(p)
+ b = self.find(q)
+ if a is not b: # only merge if different groups
+ if a._size > b._size:
+ b._parent = a
+ a._size += b._size
+ else:
+ a._parent = b
+ b._size += a._size
diff --git a/ch14/shortest_paths.py b/ch14/shortest_paths.py
index e9eef77..67276c8 100644
--- a/ch14/shortest_paths.py
+++ b/ch14/shortest_paths.py
@@ -1,75 +1,50 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
from ..ch09.adaptable_heap_priority_queue import AdaptableHeapPriorityQueue
-def shortest_path_lengths(g, src):
- """Compute shortest-path distances from src to reachable vertices of g.
-
- Graph g can be undirected or directed, but must be weighted such that
- e.element() returns a numeric weight for each edge e.
-
- Return dictionary mapping each reachable vertex to its distance from src.
- """
- d = {} # d[v] is upper bound from s to v
- cloud = {} # map reachable v to its d[v] value
- pq = AdaptableHeapPriorityQueue() # vertex v will have key d[v]
- pqlocator = {} # map from vertex to its pq locator
- # for each vertex v of the graph, add an entry to the priority queue, with
- # the source having distance 0 and all others having infinite distance
- for v in g.vertices():
- if v is src:
- d[v] = 0
- else:
- d[v] = float('inf') # syntax for positive infinity
- pqlocator[v] = pq.add(d[v], v) # save locator for future updates
-
- while not pq.is_empty():
- key, u = pq.remove_min()
- cloud[u] = key # its correct d[u] value
- del pqlocator[u] # u is no longer in pq
- for e in g.incident_edges(u): # outgoing edges (u,v)
- v = e.opposite(u)
- if v not in cloud:
- # perform relaxation step on edge (u,v)
- wgt = e.element()
- if d[u] + wgt < d[v]: # better path to v?
- d[v] = d[u] + wgt # update the distance
- pq.update(pqlocator[v], d[v], v) # update the pq entry
+def shortest_path_lengths(g, src):
+ """Compute shortest-path distances from src to reachable vertices of g.
+ Graph g can be undirected or directed, but must be weighted such that
+ e.element() returns a numeric weight for each edge e.
+ Return dictionary mapping each reachable vertex to its distance from src.
+ """
+ d = {} # d[v] is upper bound from s to v
+ cloud = {} # map reachable v to its d[v] value
+ pq = AdaptableHeapPriorityQueue() # vertex v will have key d[v]
+ pqlocator = {} # map from vertex to its pq locator
+ # for each vertex v of the graph, add an entry to the priority queue, with
+ # the source having distance 0 and all others having infinite distance
+ for v in g.vertices():
+ if v is src:
+ d[v] = 0
+ else:
+ d[v] = float('inf') # syntax for positive infinity
+ pqlocator[v] = pq.add(d[v], v) # save locator for future updates
+ while not pq.is_empty():
+ key, u = pq.remove_min()
+ cloud[u] = key # its correct d[u] value
+ del pqlocator[u] # u is no longer in pq
+ for e in g.incident_edges(u): # outgoing edges (u,v)
+ v = e.opposite(u)
+ if v not in cloud:
+ # perform relaxation step on edge (u,v)
+ wgt = e.element()
+ if d[u] + wgt < d[v]: # better path to v?
+ d[v] = d[u] + wgt # update the distance
+ pq.update(pqlocator[v], d[v], v) # update the pq entry
+ return cloud # only includes reachable vertices
- return cloud # only includes reachable vertices
def shortest_path_tree(g, s, d):
- """Reconstruct shortest-path tree rooted at vertex s, given distance map d.
-
- Return tree as a map from each reachable vertex v (other than s) to the
- edge e=(u,v) that is used to reach v from its parent u in the tree.
- """
- tree = {}
- for v in d:
- if v is not s:
- for e in g.incident_edges(v, False): # consider INCOMING edges
- u = e.opposite(v)
- wgt = e.element()
- if d[v] == d[u] + wgt:
- tree[v] = e # edge e is used to reach v
- return tree
+ """Reconstruct shortest-path tree rooted at vertex s, given distance map d.
+ Return tree as a map from each reachable vertex v (other than s) to the
+ edge e=(u,v) that is used to reach v from its parent u in the tree.
+ """
+ tree = {}
+ for v in d:
+ if v is not s:
+ for e in g.incident_edges(v, False): # consider INCOMING edges
+ u = e.opposite(v)
+ wgt = e.element()
+ if d[v] == d[u] + wgt:
+ tree[v] = e # edge e is used to reach v
+ return tree
diff --git a/ch14/topological_sort.py b/ch14/topological_sort.py
index cd74e76..2cbaf92 100644
--- a/ch14/topological_sort.py
+++ b/ch14/topological_sort.py
@@ -1,50 +1,30 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
def topological_sort(g):
- """Return a list of verticies of directed acyclic graph g in topological order.
+ """Return a list of verticies of directed acyclic graph g in topological order.
+ If graph g has a cycle, the result will be incomplete.
+ """
+ topo = [] # a list of vertices placed in topological order
+ ready = [] # list of vertices that have no remaining constraints
+ incount = {} # keep track of in-degree for each vertex
+ for u in g.vertices():
+ incount[u] = g.degree(u, False) # parameter requests incoming degree
+ if incount[u] == 0: # if u has no incoming edges,
+ ready.append(u) # it is free of constraints
+ while len(ready) > 0:
+ u = ready.pop() # u is free of constraints
+ topo.append(u) # add u to the topological order
+ for e in g.incident_edges(u): # consider all outgoing neighbors of u
+ v = e.opposite(u)
+ incount[v] -= 1 # v has one less constraint without u
+ if incount[v] == 0:
+ ready.append(v)
+ return topo
- If graph g has a cycle, the result will be incomplete.
- """
- topo = [] # a list of vertices placed in topological order
- ready = [] # list of vertices that have no remaining constraints
- incount = {} # keep track of in-degree for each vertex
- for u in g.vertices():
- incount[u] = g.degree(u, False) # parameter requests incoming degree
- if incount[u] == 0: # if u has no incoming edges,
- ready.append(u) # it is free of constraints
- while len(ready) > 0:
- u = ready.pop() # u is free of constraints
- topo.append(u) # add u to the topological order
- for e in g.incident_edges(u): # consider all outgoing neighbors of u
- v = e.opposite(u)
- incount[v] -= 1 # v has one less constraint without u
- if incount[v] == 0:
- ready.append(v)
- return topo
if __name__ == '__main__':
- from .graph_examples import figure_14_12 as example
- g = example()
- print("Number of vertices is", g.vertex_count())
- print("Number of edges is", g.edge_count())
- topo = topological_sort(g)
- print("Topo order", [str(v) for v in topo])
+ from .graph_examples import figure_14_12 as example
+
+ g = example()
+ print("Number of vertices is", g.vertex_count())
+ print("Number of edges is", g.edge_count())
+ topo = topological_sort(g)
+ print("Topo order", [str(v) for v in topo])
diff --git a/ch14/transitive_closure.py b/ch14/transitive_closure.py
index 7627a88..f6353c5 100644
--- a/ch14/transitive_closure.py
+++ b/ch14/transitive_closure.py
@@ -1,47 +1,29 @@
-# Copyright 2013, Michael H. Goldwasser
-#
-# Developed for use with the book:
-#
-# Data Structures and Algorithms in Python
-# Michael T. Goodrich, Roberto Tamassia, and Michael H. Goldwasser
-# John Wiley & Sons, 2013
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
from copy import deepcopy
+
def floyd_warshall(g):
- """Return a new graph that is the transitive closure of g."""
- closure = deepcopy(g) # imported from copy module
- verts = list(closure.vertices()) # make indexable list
- n = len(verts)
- for k in range(n):
- for i in range(n):
- # verify that edge (i,k) exists in the partial closure
- if i != k and closure.get_edge(verts[i],verts[k]) is not None:
- for j in range(n):
- # verify that edge (k,j) exists in the partial closure
- if i != j != k and closure.get_edge(verts[k],verts[j]) is not None:
- # if (i,j) not yet included, add it to the closure
- if closure.get_edge(verts[i],verts[j]) is None:
- closure.insert_edge(verts[i],verts[j])
- return closure
+ """Return a new graph that is the transitive closure of g."""
+ closure = deepcopy(g) # imported from copy module
+ verts = list(closure.vertices()) # make indexable list
+ n = len(verts)
+ for k in range(n):
+ for i in range(n):
+ # verify that edge (i,k) exists in the partial closure
+ if i != k and closure.get_edge(verts[i], verts[k]) is not None:
+ for j in range(n):
+ # verify that edge (k,j) exists in the partial closure
+ if i != j != k and closure.get_edge(verts[k], verts[j]) is not None:
+ # if (i,j) not yet included, add it to the closure
+ if closure.get_edge(verts[i], verts[j]) is None:
+ closure.insert_edge(verts[i], verts[j])
+ return closure
+
if __name__ == '__main__':
- from graph_examples import figure_14_11 as example
- g = example()
- print("Number of vertices is", g.vertex_count())
- print("Number of edges is", g.edge_count())
- closure = floyd_warshall(g)
- print("Number of edges in closure is", closure.edge_count())
+ from graph_examples import figure_14_11 as example
+
+ g = example()
+ print("Number of vertices is", g.vertex_count())
+ print("Number of edges is", g.edge_count())
+ closure = floyd_warshall(g)
+ print("Number of edges in closure is", closure.edge_count())
From 7af063eb52d05020c594c13af8d11d12d554e89f Mon Sep 17 00:00:00 2001
From: liushengpeng <328232234@qq.com>
Date: Tue, 24 Apr 2018 09:18:35 +0800
Subject: [PATCH 2/8] save
---
ch06/array_stack.py | 5 +++--
ch06/match_delimiters.py | 3 +++
ch06/match_html.py | 3 +++
3 files changed, 9 insertions(+), 2 deletions(-)
diff --git a/ch06/array_stack.py b/ch06/array_stack.py
index 9b8f5f7..c32d248 100644
--- a/ch06/array_stack.py
+++ b/ch06/array_stack.py
@@ -1,5 +1,6 @@
-"""Basic example of an adapter class to provide a stack interface to Python's list."""
-from ..exceptions import Empty
+class Empty(Exception):
+ """Basic example of an adapter class to provide a stack interface to Python's list."""
+ pass
class ArrayStack:
diff --git a/ch06/match_delimiters.py b/ch06/match_delimiters.py
index f7fa941..5c59a43 100644
--- a/ch06/match_delimiters.py
+++ b/ch06/match_delimiters.py
@@ -1,3 +1,6 @@
+from array_stack import ArrayStack
+
+
def is_matched(expr):
"""Return True if all delimiters are properly match; False otherwise."""
lefty = '({[' # opening delimiters
diff --git a/ch06/match_html.py b/ch06/match_html.py
index ab79fc7..284e010 100644
--- a/ch06/match_html.py
+++ b/ch06/match_html.py
@@ -1,3 +1,6 @@
+from array_stack import ArrayStack
+
+
def is_matched_html(raw):
"""Return True if all HTML tags are properly match; False otherwise."""
S = ArrayStack()
From b5461da3759cc3bb473c6aa921a6dbc5ce5d053e Mon Sep 17 00:00:00 2001
From: liushengpeng <328232234@qq.com>
Date: Tue, 24 Apr 2018 10:27:21 +0800
Subject: [PATCH 3/8] save
---
ch06/array_queue.py | 6 ++++--
ch06/reverse_file.py | 3 +++
2 files changed, 7 insertions(+), 2 deletions(-)
diff --git a/ch06/array_queue.py b/ch06/array_queue.py
index 1154816..6156f9f 100644
--- a/ch06/array_queue.py
+++ b/ch06/array_queue.py
@@ -1,4 +1,6 @@
-from ..exceptions import Empty
+class Empty(Exception):
+ """Basic example of an adapter class to provide a stack interface to Python's list."""
+ pass
class ArrayQueue:
@@ -42,7 +44,7 @@ def dequeue(self):
def enqueue(self, e):
"""Add an element to the back of queue."""
if self._size == len(self._data):
- self._resize(2 * len(self.data)) # double the array size
+ self._resize(2 * len(self._data)) # double the array size
avail = (self._front + self._size) % len(self._data)
self._data[avail] = e
self._size += 1
diff --git a/ch06/reverse_file.py b/ch06/reverse_file.py
index 8d31202..c71e658 100644
--- a/ch06/reverse_file.py
+++ b/ch06/reverse_file.py
@@ -1,3 +1,6 @@
+from array_stack import ArrayStack
+
+
def reverse_file(filename):
"""Overwrite given file with its contents line-by-line reversed."""
S = ArrayStack()
From 399926aa95775f5b71799a6d1330718c4b8b5bbe Mon Sep 17 00:00:00 2001
From: liushengpeng <328232234@qq.com>
Date: Tue, 24 Apr 2018 10:35:41 +0800
Subject: [PATCH 4/8] asve
---
ch06/array_queue.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/ch06/array_queue.py b/ch06/array_queue.py
index 6156f9f..d71f5c5 100644
--- a/ch06/array_queue.py
+++ b/ch06/array_queue.py
@@ -39,6 +39,8 @@ def dequeue(self):
self._data[self._front] = None # help garbage collection
self._front = (self._front + 1) % len(self._data)
self._size -= 1
+ if 0 < self._size < len(self._data) // 4:
+ self._resize(len(self._data) // 2)
return answer
def enqueue(self, e):
From efd87b304988da60826ad867ae7f3548c4422373 Mon Sep 17 00:00:00 2001
From: liushengpeng <328232234@qq.com>
Date: Wed, 25 Apr 2018 16:06:23 +0800
Subject: [PATCH 5/8] save
---
ch07/favorites_list.py | 4 ++--
ch07/favorites_list_mtf.py | 6 +++---
ch07/positional_list.py | 2 +-
exceptions.py | 0
4 files changed, 6 insertions(+), 6 deletions(-)
create mode 100644 exceptions.py
diff --git a/ch07/favorites_list.py b/ch07/favorites_list.py
index 5f9dcaa..ffb72a4 100644
--- a/ch07/favorites_list.py
+++ b/ch07/favorites_list.py
@@ -1,4 +1,4 @@
-from .positional_list import PositionalList
+from positional_list import PositionalList
class FavoritesList:
@@ -78,4 +78,4 @@ def __repr__(self):
for c in 'hello. this is a test of mtf': # well, not the mtf part...
fav.access(c)
k = min(5, len(fav))
- print('Top {0}) {1:25} {2}'.format(k, [x for x in fav.top(k)], fav))
+ print('Top {0}) {1:25} {2}'.format(k, str([x for x in fav.top(k)]), fav))
diff --git a/ch07/favorites_list_mtf.py b/ch07/favorites_list_mtf.py
index 1d2008a..80af5ec 100644
--- a/ch07/favorites_list_mtf.py
+++ b/ch07/favorites_list_mtf.py
@@ -1,5 +1,5 @@
-from .favorites_list import FavoritesList
-from .positional_list import PositionalList
+from favorites_list import FavoritesList
+from positional_list import PositionalList
class FavoritesListMTF(FavoritesList):
@@ -39,4 +39,4 @@ def top(self, k):
for c in 'hello. this is a test of mtf':
fav.access(c)
k = min(5, len(fav))
- print('Top {0}) {1:25} {2}'.format(k, [x for x in fav.top(k)], fav))
+ print('Top {0}) {1:25} {2}'.format(k, str([x for x in fav.top(k)]), fav))
diff --git a/ch07/positional_list.py b/ch07/positional_list.py
index 0deee32..4a7b3af 100644
--- a/ch07/positional_list.py
+++ b/ch07/positional_list.py
@@ -1,4 +1,4 @@
-from .doubly_linked_base import _DoublyLinkedBase
+from doubly_linked_base import _DoublyLinkedBase
class PositionalList(_DoublyLinkedBase):
diff --git a/exceptions.py b/exceptions.py
new file mode 100644
index 0000000..e69de29
From 71db46123c00396564f0fee6915258555bbf4820 Mon Sep 17 00:00:00 2001
From: liushengpeng <328232234@qq.com>
Date: Wed, 25 Apr 2018 16:09:22 +0800
Subject: [PATCH 6/8] save
---
ch02/sequence_iterator.py | 2 +-
ch05/tic_tac_toe.py | 8 ++++----
ch08/binary_tree.py | 2 +-
ch08/tree.py | 1 -
exceptions.py | 3 +++
5 files changed, 9 insertions(+), 7 deletions(-)
diff --git a/ch02/sequence_iterator.py b/ch02/sequence_iterator.py
index dbe33ab..f18178e 100644
--- a/ch02/sequence_iterator.py
+++ b/ch02/sequence_iterator.py
@@ -10,7 +10,7 @@ def __next__(self):
"""Return the next element, or else raise StopIteration error."""
self._k += 1 # advance to next index
if self._k < len(self._seq):
- return (self._seq[self._k]) # return the data element
+ return self._seq[self._k] # return the data element
else:
raise StopIteration() # there are no more elements
diff --git a/ch05/tic_tac_toe.py b/ch05/tic_tac_toe.py
index 0f8291f..30a0e09 100644
--- a/ch05/tic_tac_toe.py
+++ b/ch05/tic_tac_toe.py
@@ -48,13 +48,13 @@ def __str__(self):
if __name__ == '__main__':
game = TicTacToe()
# X moves: # O moves:
- game.mark(1, 1);
+ game.mark(1, 1)
game.mark(0, 2)
- game.mark(2, 2);
+ game.mark(2, 2)
game.mark(0, 0)
- game.mark(0, 1);
+ game.mark(0, 1)
game.mark(2, 1)
- game.mark(1, 2);
+ game.mark(1, 2)
game.mark(1, 0)
game.mark(2, 0)
print(game)
diff --git a/ch08/binary_tree.py b/ch08/binary_tree.py
index c13dce1..c3ded71 100644
--- a/ch08/binary_tree.py
+++ b/ch08/binary_tree.py
@@ -1,4 +1,4 @@
-from .tree import Tree
+from tree import Tree
class BinaryTree(Tree):
diff --git a/ch08/tree.py b/ch08/tree.py
index 21de665..4e86a1e 100644
--- a/ch08/tree.py
+++ b/ch08/tree.py
@@ -1,5 +1,4 @@
from ..ch07.linked_queue import LinkedQueue
-import collections
class Tree:
diff --git a/exceptions.py b/exceptions.py
index e69de29..28fc4d0 100644
--- a/exceptions.py
+++ b/exceptions.py
@@ -0,0 +1,3 @@
+class Empty(Exception):
+ """Basic example of an adapter class to provide a stack interface to Python's list."""
+ pass
From aa7730be6fcaf03d85688cc4f2562087e8e54069 Mon Sep 17 00:00:00 2001
From: liushengpeng <328232234@qq.com>
Date: Wed, 25 Apr 2018 17:07:08 +0800
Subject: [PATCH 7/8] save
---
.gitignore | 1 +
ch07/linked_queue.py | 2 +-
ch08/binary_tree.py | 2 +-
ch08/tree.py | 2 +-
4 files changed, 4 insertions(+), 3 deletions(-)
diff --git a/.gitignore b/.gitignore
index e0e9321..fa447e9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -62,3 +62,4 @@ venv.bak/
.mypy_cache/
.gitignore
.idea/
+test.py
diff --git a/ch07/linked_queue.py b/ch07/linked_queue.py
index e604b89..dbfa6f8 100644
--- a/ch07/linked_queue.py
+++ b/ch07/linked_queue.py
@@ -1,4 +1,4 @@
-from ..exceptions import Empty
+from exceptions import Empty
class LinkedQueue:
diff --git a/ch08/binary_tree.py b/ch08/binary_tree.py
index c3ded71..c13dce1 100644
--- a/ch08/binary_tree.py
+++ b/ch08/binary_tree.py
@@ -1,4 +1,4 @@
-from tree import Tree
+from .tree import Tree
class BinaryTree(Tree):
diff --git a/ch08/tree.py b/ch08/tree.py
index 4e86a1e..ef44075 100644
--- a/ch08/tree.py
+++ b/ch08/tree.py
@@ -1,4 +1,4 @@
-from ..ch07.linked_queue import LinkedQueue
+from ch07.linked_queue import LinkedQueue
class Tree:
From 91e186a423f239117d48d6f9342fef0960418586 Mon Sep 17 00:00:00 2001
From: liushengpeng <328232234@qq.com>
Date: Fri, 27 Apr 2018 08:53:16 +0800
Subject: [PATCH 8/8] save
---
ch08/expression_tree.py | 12 ++++++++++--
ch08/traversal_examples.py | 1 +
2 files changed, 11 insertions(+), 2 deletions(-)
diff --git a/ch08/expression_tree.py b/ch08/expression_tree.py
index 56e213f..94c769f 100644
--- a/ch08/expression_tree.py
+++ b/ch08/expression_tree.py
@@ -56,8 +56,10 @@ def _evaluate_recur(self, p):
return left_val - right_val
elif op == '/':
return left_val / right_val
- else: # treat 'x' or '*' as multiplication
+ elif op in ['*', 'x']: # treat 'x' or '*' as multiplication
return left_val * right_val
+ else:
+ raise ValueError('token must be valid operator')
def tokenize(raw):
@@ -102,5 +104,11 @@ def build_expression_tree(tokens):
if __name__ == '__main__':
- big = build_expression_tree(tokenize('((((3+1)x3)/((9-5)+2))-((3x(7-4))+6))'))
+ token = tokenize('((((3+1)x3)/((9-5)+2))-((3x(7-4))+6))')
+ big = build_expression_tree(token)
print(big, '=', big.evaluate())
+ # et1 = ExpressionTree('444')
+ # et2 = ExpressionTree('555')
+ # et3 = ExpressionTree('*', et1, et2)
+ # a = next(et3.children(et3.root()))
+ # print(et3._evaluate_recur(a))
diff --git a/ch08/traversal_examples.py b/ch08/traversal_examples.py
index 8b66cc5..cbabf85 100644
--- a/ch08/traversal_examples.py
+++ b/ch08/traversal_examples.py
@@ -23,6 +23,7 @@ def preorder_label(T, p, d, path):
for c in T.children(p):
preorder_label(T, c, d + 1, path) # child depth is d+1
path[-1] += 1
+ # print(2 * d * ' ' + 'path: ', path)
path.pop()