├── .gitattributes
├── .gitignore
├── README.md
├── data-structures-algorithms-learning-plan.ods
├── efficiency_big_O.md
├── mit_license.md
├── practice
├── assets
│ ├── comparison_computational_complexity.png
│ └── two_runners_circular.png
├── daysBetweenDates.py
├── efficiency_practice.ipynb
├── ex1-class-person.py
├── graphs
│ ├── Longest Palindromic Sequence.ipynb
│ ├── Stock Prices.ipynb
│ ├── coin_change.ipynb
│ ├── connecting_islands.py
│ ├── dijkstra.ipynb
│ ├── dijkstra.py
│ ├── graph_bfs_iteration.ipynb
│ ├── graph_bfs_iteration.py
│ ├── graph_dfs_iteration.ipynb
│ ├── graph_dfs_recursion.ipynb
│ ├── knapsack.py
│ ├── knapsack_problem.ipynb
│ ├── longest_common_subsequence.odt
│ └── longest_common_subsequence.py
├── kadane_algorithm.py
├── linked_list_practice.py
├── linked_lists
│ ├── 1_implement_linked_list.ipynb
│ ├── 2_types_linked_lists.ipynb
│ ├── 3_linked_list_practice.ipynb
│ ├── 4_reverse_linked_list.ipynb
│ ├── 5_detecting_loops.ipynb
│ ├── 6_flattening_linked_list.ipynb
│ ├── even-after-odd.py
│ └── linked_list_practice.py
├── maps_hashing
│ ├── Caching.ipynb
│ ├── HashMap.ipynb
│ ├── count-stair-hops.jpg
│ ├── hash_map.py
│ ├── longest_consecutive_subsequence.py
│ ├── maps_dict.py
│ ├── pair_sum_to_target.py
│ └── string_hash_table.py
├── pascals_triangle.py
├── queues
│ ├── priority_queue.py
│ ├── priority_queue_test01.py
│ ├── priority_queue_test02.py
│ ├── queue_array.py
│ └── queue_linked_list.py
├── recursion
│ ├── Recurrence Relations.ipynb
│ ├── binary_search.py
│ ├── last_index.py
│ ├── recursion1_simple.py
│ ├── recursion2_reverse_string.py
│ ├── recursion3_palindrome.py
│ ├── recursion4_permutations.py
│ └── tower_of_hanoi.py
├── stacks
│ ├── balanced_paranthesis.py
│ ├── reversed_polish_notation.py
│ ├── stack1_simple.py
│ ├── stack2_linked_list.py
│ ├── stack3_linked_list.py
│ └── stack_reverse.py
└── trees
│ ├── 03 traverse_a_tree_bfs_solution.ipynb
│ ├── 04 binary_search_tree_solution.ipynb
│ ├── binary_tree.py
│ ├── binary_tree.py.txt
│ ├── binary_tree_diameter.py
│ ├── binary_tree_root_to_node_path.py
│ ├── binary_tree_state.py
│ ├── binary_tree_state.py.txt
│ ├── bst-delete-case3.png
│ ├── bst_01.png
│ ├── bst_delete.py
│ └── tree-simple-example.png
├── project1
├── P0.zip
├── Task0.py
├── Task1.py
├── Task2.py
├── Task3.py
├── Task4.py
├── analysis.txt
├── calls.csv
├── project1-check.ods
└── texts.csv
├── project2
├── 1_lru_cache.py
├── 2_file_recursion.py
├── 3_huffman.py
├── 4_active_dir.py
├── 5_blockchain.py
├── 6_union_inter.py
├── ex.py
├── explanation.txt
└── testdir
│ ├── subdir1
│ ├── a.c
│ └── a.h
│ ├── subdir2
│ └── .gitkeep
│ ├── subdir3
│ └── subsubdir1
│ │ ├── b.c
│ │ └── b.h
│ ├── subdir4
│ └── .gitkeep
│ ├── subdir5
│ ├── a.c
│ └── a.h
│ ├── t1.c
│ └── t1.h
├── project3
├── 1_problem.py
├── 2_problem.py
├── 3_problem.py
├── 4_problem.py
├── 5_problem.ipynb
├── 6_problem.py
├── 7_problem.py
├── auto_complete.py
└── explain.md
├── project4
├── graph_data.py
├── helpers.py
├── map-10.pickle
├── map-40.pickle
├── project_notebook.ipynb
├── student_code.py
└── test.py
└── solving_problems.md
/.gitattributes:
--------------------------------------------------------------------------------
1 | practice/* linguist-documentation
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # .gitignore
2 | *.pdf
3 | **/practice/.ipynb_checkpoints
4 | **/practice/graphs/.ipynb_checkpoints
5 | **/practice/maps_hashing/.ipynb_checkpoints
6 | **/practice/queues/__pycache__
7 | **/practice/linked_lists/.ipynb_checkpoints
8 | #practice/.ipynb_checkpoints/*
9 | #practice/.ipynb_checkpoints
10 | #practice/trees/.ipynb_checkpoints
11 | #practice/maps_hashing/.ipynb_checkpoints
12 | project1/*.zip
13 | project1/*.ods
14 | project1/.~*
15 | project1/__pycache__
16 | project2/*.zip
17 | project2/__pycache__
18 | wip
19 | project3/.ipynb_checkpoints
20 | project3/*.zip
21 | project4/__pycache__
22 | **/project4/.ipynb_checkpoints
23 | project4/__pycache__
24 | *.ods
25 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Udacity's Data Structure and Algorithm Nanodegree
2 |
3 | In this course you will learn data structures and algorithms by solving 80+ practice problems. You will begin each course by learning to solve defined problems related to a particular data structure and algorithm. By the end of each course, you would be able to evaluate and assess different data structures and algorithms for any open-ended problem and implement a solution based on your design choices.
4 |
5 | Course Outline
6 |
7 | **Intro**
8 |
9 | * Welcome
10 | * Getting Help and Support
11 | * Python Refresher
12 | * How to Solve Problems
13 | * A systematic way of approaching and breaking down problems
14 | * Efficiency
15 | * Understanding the importance of efficiency when working with data structures and algorithms.
16 | * Project 1 - Unscramble CS Problems
17 | * Complete five tasks based on a fabricated set of calls and texts exchanged during September 2016
18 | * Use Python to analyze and answer questions about the texts and calls contained in the dataset
19 | * Perform run time analysis of your solution and determine its efficiency
20 |
21 |
22 | **Data Structures**
23 |
24 | * Arrays and Linked Lists
25 | * Build Stacks and Queues
26 | * Apply Recursion to Problems
27 | * Trees - basic trees, traversal & binary search trees
28 | * Maps and Hashing
29 | * Project 2 - Show Me the Data Structures: implement appropriate data structures and corresponding methods
30 | 1. Least Recently Used (LRU) Cache
31 | 2. File Recursion
32 | 3. Huffman Coding
33 | 4. Active Directory
34 | 5. Blockchain
35 | 6. Union and Intersection
36 | 7. Show Me the Data Structures
37 | * Include three test cases for each solution
38 | * In separate text file, write explanation for using given data structure and explain the time and speed efficiency for each solution.
39 |
40 |
41 | **Basic Algorithms**
42 |
43 | * Basic Algorithms
44 | * Sorting Algorithms
45 | * Faster Divide & Conquer
46 | * Project 3 - Problems vs. Algorithms
47 | 1. Square Root of an Integer
48 | 2. Search in a Rotated, Sorted Array
49 | 3. Rearrange Array Digits
50 | 4. Dutch National Flag Problem
51 | 5. Autocomplete with Tries
52 | 6. Unsorted Integer Array
53 | 7. Request Routing in a Web Server with a Trie
54 |
55 |
56 | **Advanced Algorithms**
57 |
58 | * Greedy Algorithms
59 | * Graph Algorithms
60 | * Dynamic Programming
61 | * A-Star (A*) Algorithm
62 | * Project 4 - Route Planner **
63 | * In this project, you will build a route-planning algorithm like the one used in Google Maps to calculate the shortest path between two points on a map.
64 |
65 |
66 | #### ** Project 4 Notes:
67 |
68 | Project #4 notebook requires [plotly](https://plot.ly/python/getting-started/). However, trying to run the notebook locally resulted in this error:
69 |
70 | ```
71 | ImportError:
72 | The plotly.plotly module is deprecated,
73 | please install the chart-studio package and use the
74 | chart_studio.plotly module instead.
75 | ```
76 |
77 | Also just changing `import plotly.plotly as py` in the helpers.py to `import chart_studio.plotly as py` does not work.
78 |
79 | Throws an error `AttributeError: 'Graph' object has no attribute '_node'`
80 |
81 | **_Possible Solution_**
82 |
83 | * https://stackoverflow.com/questions/49016596/networkx-digraph-attribute-error-self-succ/49016885#49016885
84 |
85 | **Additional Info:**
86 |
87 | * https://networkx.github.io/documentation/stable/release/migration_guide_from_1.x_to_2.0.html
88 |
89 | **UPDATE NOTE**
90 | It appears registration and an API key are required to use chart_studio.plotly.
91 |
92 | ## License
93 |
94 | The contents of this repository are covered under the [MIT License](mit_license.md)
95 |
96 |
97 |
--------------------------------------------------------------------------------
/data-structures-algorithms-learning-plan.ods:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ssi112/data-structures-algorithms/e9bdfa4f41b5dd68b3227151084a8c0115bac8b8/data-structures-algorithms-learning-plan.ods
--------------------------------------------------------------------------------
/efficiency_big_O.md:
--------------------------------------------------------------------------------
1 | ##Lesson 6 Efficiency
2 |
3 | **Input size _sometimes_ affects the run-time of an algorithm**
4 |
5 | Especially in a loop if the size determines how many times to run it
6 |
7 | Simple Example (_thinking of an "operation" as a single line of Python code - not exactly accurate, but illustrates a point_):
8 |
9 | ```
10 | def say_hello(n):
11 | for i in range(n):
12 | print('hello')
13 | ```
14 |
15 | Three lines of code to execute if n = 1. As the input increases, the number of lines executed also increases.
16 |
17 | As the input increases, the number of lines executed increases by a _proportional amount_. Increasing the input by 1 will cause 1 more line to get run. Increasing the input by 10 will cause 10 more lines to get run. Any change in the input is tied to a consistent, proportional change in the number of lines executed. This type of relationship is called a **linear relationship**.
18 |
19 | **Quadratic Rate of Increase**
20 |
21 | When the input goes up by a certain amount, the number of operations goes up by the square of that amount. If the input is 2, the number of operations is 2^2 or 4. If the input is 3, the number of operations is 3^2 or 9.
22 |
23 | Simple Example:
24 | ```
25 | def say_hello(n):
26 | for i in range(n):
27 | for i in range(n):
28 | print("Hello!")
29 | ```
30 |
31 | As the input to an algorithm increases, the time required to run the algorithm may also increase, _and different algorithms may increase at different rates_.
32 |
33 | The _order_ or _rate of increase_ is important when designing algorithms.
34 |
35 | **The rate of increase of an algorithm is also referred to as the order of the algorithm.**
36 |
37 | The O in Big O Notation refers to the **o**rder of the rate of increase.
38 |
39 |
40 |
41 | #### Big O Notation
42 |
43 | Big O notation is used to describe the _order_, or _rate of increase_, in the run-time of an algorithm, in terms of the input size (n).
44 |
45 | Written as: **O(n)** n = an algebraic expression using variable n
46 |
47 | O(1) = O(0n+1)
48 |
49 | O(2n + 2) n = length or amount of data input
50 |
51 | In terms of efficiency, while counting lines of code is okay for the basics, the amount of run-time an algorithm requires is based on additional factors. Such as how fast is the processor and how many operations it can perform. Different code can require different opertations for the processor to perform.
52 |
53 | Higher level languages may require fewer lines of code to write, but they typically result in more instructions executing in the background than what is written.
54 |
55 | Also, the type of data structure used and how its elements are accessed can influence the efficiency of an algorithm.
56 |
57 | O(n^2 + 5)
58 |
59 | Input | Number of Operations
60 | ----- | -------------------------------
61 | 5 | 30
62 | 10 | 105
63 | 25 | 630
64 | 100 | 10,005
65 |
66 | In this case, the input has little input on the actual operation. It is the n^2 that has the largest impact. The order or rate of increase is what is _typically_ most important.
67 |
68 | **Approximation**
69 | "Some number of computations must be performed for EACH element in the input."
70 |
71 | **Worst / Best Case**
72 |
73 | - Worst Case = upper bounds
74 | - Best Case = lower bounds
75 | - Average Case = generalize about the mean
76 |
77 |
78 | **Refer to Jupyter notebook tmp/efficiency_practice.ipynb for examples and some practice problems.**
79 |
80 |
81 | #### Space Complexity
82 |
83 | Use Big O Notation as well
84 |
85 | How efficient algorithm is in terms of memory usage. Depends on datatypes and internal space requirements. Many highlevel languages wrap their basic datatype with housekeeping functions to make them easier to use and this can lead to them taking more space than expected.
86 |
87 | **Basic Datatypes and Storage**
88 |
89 | Type | Storage size
90 | -| -
91 | char | 1 byte
92 | bool | 1 byte
93 | int | 4 bytes
94 | float | 4 bytes
95 | double | 8 bytes
96 |
97 | **Additional links:**
98 |
99 | - [Python Time Complexity](https://wiki.python.org/moin/TimeComplexity)
100 | - [Big O Cheatsheet](https://www.bigocheatsheet.com/)
101 |
102 |
103 |
104 |
--------------------------------------------------------------------------------
/mit_license.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) [2019] [S. S. Isenberg]
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/practice/assets/comparison_computational_complexity.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ssi112/data-structures-algorithms/e9bdfa4f41b5dd68b3227151084a8c0115bac8b8/practice/assets/comparison_computational_complexity.png
--------------------------------------------------------------------------------
/practice/assets/two_runners_circular.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ssi112/data-structures-algorithms/e9bdfa4f41b5dd68b3227151084a8c0115bac8b8/practice/assets/two_runners_circular.png
--------------------------------------------------------------------------------
/practice/daysBetweenDates.py:
--------------------------------------------------------------------------------
1 |
2 | ###### ##### ##### ##### ##### ##### ##### ##### ##### ##### ##### ##### ##### #####
3 | # Given your birthday and the current date, calculate your age in days.
4 | # Compensate for lead days. Assume that the birthday and current date
5 | # are correct dates and there is no time travle involved. Simply put,
6 | # if you were bon 1 Jan 2012 and todays date is 2 Jan 2012 you are 1 day old.
7 | ###### ##### ##### ##### ##### ##### ##### ##### ##### ##### ##### ##### ##### #####
8 |
9 |
10 | # not used but here for documentation or if needed
11 | months = ["January", "February", "March", "April", "May",
12 | "June", "July", "August", "September", "October", "November", "December"]
13 |
14 | daysInMonths = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
15 |
16 | # helper functions
17 | # check for leap year
18 | def isLeapYear(year):
19 | # returns True or False
20 | # https://en.wikipedia.org/wiki/Leap_year#Algorithm
21 | return ((year % 4 == 0) and (year % 100 != 0)) or (year % 400 == 0)
22 |
23 |
24 | # returns the number of days in the month
25 | def daysInMonth(year, month):
26 | if (isLeapYear(year) and month == 2):
27 | return 29
28 | else:
29 | return daysInMonths[month - 1]
30 |
31 |
32 | def nextDay(year, month, day):
33 | """simple function to increment date to the next day
34 | """
35 | if day < daysInMonth(year, month):
36 | return year, month, day + 1
37 | else:
38 | if month == 12:
39 | return year + 1, 1, 1
40 | else:
41 | return year, month + 1, 1
42 |
43 |
44 | def dateIsBefore(year1, month1, day1, year2, month2, day2):
45 | """Returns True if year1, month1, and day1 is before
46 | year2, month2 and day2. Otherwise returns False
47 | """
48 | if year1 < year2:
49 | return True
50 | if year1 == year2:
51 | if month1 < month2:
52 | return True
53 | if month1 == month2:
54 | return day1 < day2
55 | return False
56 |
57 |
58 | def daysBetweenDates(year1, month1, day1, year2, month2, day2):
59 | """ Calculates the number of days between two dates.
60 | Returns the number of days between year1/month1/day1
61 | and year2/month2/day2. Assumes inputs are valid dates
62 | in Gregorian calendar, and the first date is not after
63 | the second.
64 |
65 | Accounts for leap years if month is February
66 | """
67 | days = 0
68 |
69 | assert not dateIsBefore(year2, month2, day2, year1, month1, day1) == True
70 |
71 | # original code to test 1st date is less than 2nd
72 | # while ((year1 != year2) or (month1 != month2) or (day1 != day2)):
73 |
74 | # new code using helper function to test
75 | while dateIsBefore(year1, month1, day1, year2, month2, day2):
76 | year1, month1, day1 = nextDay(year1, month1, day1)
77 | days += 1
78 | return days
79 |
80 |
81 | def test():
82 | test_cases = [((2012,9,30, 2012,10,30) ,30),
83 | ((2012,1,1, 2013,1,1) ,366),
84 | ((2012,9,1, 2012,9,4) ,3),
85 | ((2013,1,1, 1999,12,31), "AssertionError"),
86 | ((2017, 12, 30, 2017, 12, 30), 0),
87 | ((2017, 12, 30, 2017, 12, 31), 1),
88 | ((2017, 12, 30, 2018, 1, 1), 2),
89 | ((2012, 6, 29, 2013, 6, 29), 365),
90 | ((2012, 12, 8, 2012, 12, 7), "AssertionError"),
91 | ((2012, 1, 1, 2012, 2, 28), 58)]
92 |
93 | for (args, answer) in test_cases:
94 | try:
95 | result = daysBetweenDates(*args)
96 | if result == answer and answer != "AssertionError":
97 | print( "Test case passed!" )
98 | else:
99 | print( "Test with data:", args, "failed" )
100 |
101 | except AssertionError:
102 | if answer == "AssertionError":
103 | print("Nice job! Test case {0} correctly raises AssertionError!\n".format(args) )
104 | else:
105 | print( "Check your work! Test case {0} should not raise AssertionError!\n".format(args) )
106 |
107 |
108 | def main():
109 | # print(isLeapYear(1932)) # true
110 | # print(isLeapYear(2012)) # true
111 | # print(isLeapYear(2100)) # false
112 | # print(isLeapYear(2064)) # true
113 | print( daysBetweenDates(2013,1,24, 2013,6,29) )
114 | print( daysBetweenDates(1912,12,12, 2012,12,12) )
115 | print( daysBetweenDates(1932,2,29, 1932,2,29) )
116 | test()
117 |
118 | if __name__ == '__main__':
119 | main()
120 |
121 |
--------------------------------------------------------------------------------
/practice/ex1-class-person.py:
--------------------------------------------------------------------------------
1 | # import operator # not used - for loop instead
2 |
3 | class Person:
4 | def __init__(self, name, age, month):
5 | self.name = name
6 | self.age = age
7 | self.birthday_month = month
8 |
9 | def birthday(self):
10 | self.age += 1
11 |
12 | def create_person_objects(names, ages, months):
13 | my_data = zip(names, ages, months)
14 | person_objects = []
15 | for item in my_data:
16 | person_objects.append(Person(*item))
17 | # print(item)
18 | return person_objects
19 |
20 | def get_april_birthdays(people):
21 | # TODO:
22 | # Increment "age" for all people with birthdays in April.
23 | # Return a dictionary "april_birthdays" with the names of
24 | # all people with April birthdays as keys, and their updated ages
25 | # as values. See the test below for an example expected output.
26 | april_birthdays = {}
27 | for p in people:
28 | if p.birthday_month == 'April':
29 | p.birthday()
30 | april_birthdays[p.name] = p.age # add key-val pair to new dict
31 | # TODO: Modify the return statement
32 | return april_birthdays
33 |
34 | def get_most_common_month(people):
35 | # TODO:
36 | # Use the "months" dictionary to record counts of birthday months
37 | # for persons in the "people" data.
38 | # Return the month with the largest number of birthdays.
39 | months = {'January':0, 'February':0, 'March':0, 'April':0, 'May':0,
40 | 'June':0, 'July':0, 'August':0, 'September':0, 'October':0,
41 | 'November':0, 'December':0}
42 | for p in people:
43 | if p.birthday_month in months:
44 | months[p.birthday_month] += 1
45 | # print(months[p.birthday_month])
46 | # TODO: Modify the return statement.
47 | # https://stackoverflow.com/questions/268272/getting-key-with-maximum-value-in-dictionary
48 | # print(max(months.items(), key = operator.itemgetter(1))[0])
49 | max_val = -1
50 | max_key = None
51 | for month_key, month_value in months.items():
52 | if month_value > max_val:
53 | max_val = month_value
54 | max_key = month_key
55 | # print(max_key, max_val)
56 | return max_key
57 |
58 |
59 | def test():
60 | # Here is the data for the test. Assume there is a single most common month.
61 | names = ['Howard', 'Richard', 'Jules', 'Trula', 'Michael', 'Elizabeth', 'Richard', 'Shirley', 'Mark', 'Brianna', 'Kenneth', 'Gwen', 'William', 'Rosa', 'Denver', 'Shelly', 'Sammy', 'Maryann', 'Kathleen', 'Andrew', 'Joseph', 'Kathleen', 'Lisa', 'Viola', 'George', 'Bonnie', 'Robert', 'William', 'Sabrina', 'John', 'Robert', 'Gil', 'Calvin', 'Robert', 'Dusty', 'Dario', 'Joeann', 'Terry', 'Alan', 'Rosa', 'Jeane', 'James', 'Rachel', 'Tu', 'Chelsea', 'Andrea', 'Ernest', 'Erica', 'Priscilla', 'Carol', 'Michael', 'Dale', 'Arthur', 'Helen', 'James', 'Donna', 'Patricia', 'Betty', 'Patricia', 'Mollie', 'Nicole', 'Ernest', 'Wendy', 'Graciela', 'Teresa', 'Nicole', 'Trang', 'Caleb', 'Robert', 'Paul', 'Nieves', 'Arleen', 'Milton', 'James', 'Lawrence', 'Edward', 'Susan', 'Patricia', 'Tana', 'Jessica', 'Suzanne', 'Darren', 'Arthur', 'Holly', 'Mary', 'Randal', 'John', 'Laura', 'Betty', 'Chelsea', 'Margaret', 'Angel', 'Jeffrey', 'Mary', 'Donald', 'David', 'Roger', 'Evan', 'Danny', 'William']
62 | ages = [17, 58, 79, 8, 10, 57, 4, 98, 19, 47, 81, 68, 48, 13, 39, 21, 98, 51, 49, 12, 24, 78, 36, 59, 3, 87, 94, 85, 43, 69, 15, 52, 57, 36, 52, 5, 52, 5, 33, 10, 71, 28, 70, 9, 25, 28, 76, 71, 22, 35, 35, 100, 9, 95, 69, 52, 66, 91, 39, 84, 65, 29, 20, 98, 30, 83, 30, 15, 88, 89, 24, 98, 62, 94, 86, 63, 34, 23, 23, 19, 10, 80, 88, 67, 17, 91, 85, 97, 29, 7, 34, 38, 92, 29, 14, 52, 94, 62, 70, 22]
63 | months = ['January', 'March', 'January', 'October', 'April', 'February', 'August', 'January', 'June', 'August', 'February', 'May', 'March', 'June', 'February', 'August', 'June', 'March', 'August', 'April', 'April', 'June', 'April', 'June', 'February', 'September', 'March', 'July', 'September', 'December', 'June', 'June', 'August', 'November', 'April', 'November', 'August', 'June', 'January', 'August', 'May', 'March', 'March', 'March', 'May', 'September', 'August', 'April', 'February', 'April', 'May', 'March', 'March', 'January', 'August', 'October', 'February', 'November', 'August', 'June', 'September', 'September', 'January', 'September', 'July', 'July', 'December', 'June', 'April', 'February', 'August', 'September', 'August', 'February', 'April', 'July', 'May', 'November', 'December', 'February', 'August', 'August', 'September', 'December', 'February', 'March', 'June', 'December', 'February', 'May', 'April', 'July', 'March', 'June', 'December', 'March', 'July', 'May', 'September', 'November']
64 | people = create_person_objects(names, ages, months)
65 |
66 | # Calls to the two functions you have completed.
67 | print(get_april_birthdays(people))
68 | print(get_most_common_month(people))
69 |
70 |
71 |
72 | test()
73 | # Expected result:
74 | # {'Michael': 11, 'Erica': 72, 'Carol': 36, 'Lisa': 37, 'Lawrence': 87, 'Joseph': 25, 'Margaret': 35, 'Andrew': 13, 'Dusty': 53, 'Robert': 89}
75 | # August
76 |
--------------------------------------------------------------------------------
/practice/graphs/connecting_islands.py:
--------------------------------------------------------------------------------
1 | """
2 | connecting_islands.py
3 |
4 | In an ocean, there are n islands some of which are connected via bridges.
5 | Travelling over a bridge has some cost attached with it. Find bridges in
6 | such a way that all islands are connected with minimum cost of travelling.
7 |
8 | You can assume that there is at least one possible way in which all
9 | islands are connected with each other.
10 |
11 | You will be provided with two input parameters:
12 |
13 | num_islands = number of islands
14 |
15 | bridge_config = list of lists. Each inner list will have 3 elements:
16 |
17 | a. island A
18 | b. island B
19 | c. cost of bridge connecting both islands
20 |
21 | Each island is represented using a number
22 |
23 | Example:
24 |
25 | num_islands = 4
26 | bridge_config = [[1, 2, 1], [2, 3, 4], [1, 4, 3], [4, 3, 2], [1, 3, 10]]
27 |
28 | Input parameters explanation:
29 |
30 | 1. Number of islands = 4
31 | 2. Island 1 and 2 are connected via a bridge with cost = 1
32 | Island 2 and 3 are connected via a bridge with cost = 4
33 | Island 1 and 4 are connected via a bridge with cost = 3
34 | Island 4 and 3 are connected via a bridge with cost = 2
35 | Island 1 and 3 are connected via a bridge with cost = 10
36 |
37 | In this example if we are connecting bridges like this...
38 |
39 | between 1 and 2 with cost = 1
40 | between 1 and 4 with cost = 3
41 | between 4 and 3 with cost = 2
42 |
43 | ...then we connect all 4 islands with cost = 6 which is the minimum traveling cost.
44 | """
45 |
46 | import heapq
47 |
48 | heap = []
49 |
50 | print("the heap! {}".format(heap))
51 |
52 | bridge_config = [[1, 2, 1], [2, 3, 4], [1, 4, 3], [4, 3, 2], [1, 3, 10]]
53 | print("\nbridge_config (before)", bridge_config)
54 |
55 | # sort in place on index 0 of each list
56 | bridge_config.sort(key=lambda x: x[0])
57 | print("bridge_config (after)", bridge_config)
58 |
59 | for bridges in bridge_config:
60 | heapq.heappush(heap, bridges)
61 |
62 | print("\nthe new and improved heap! {}".format(heap))
63 |
64 |
65 | # ------------------------------------------------------------------------
66 | # Makes use of one of Python's PriorityQueue implementation (heapq)
67 | # For more details -
68 | # https://thomas-cokelaer.info/tutorials/python/module_heapq.html
69 | # ------------------------------------------------------------------------
70 |
71 | def create_graph(num_islands, bridge_config):
72 | """
73 | Helper function to create graph using adjacency list implementation
74 | """
75 | adjacency_list = [list() for _ in range(num_islands + 1)]
76 |
77 | for config in bridge_config:
78 | source = config[0]
79 | destination = config[1]
80 | cost = config[2]
81 | adjacency_list[source].append((destination, cost))
82 | adjacency_list[destination].append((source, cost))
83 |
84 | #print("adjacency_list",adjacency_list)
85 | return adjacency_list
86 |
87 |
88 |
89 | def minimum_cost(graph):
90 | """
91 | Helper function to find minimum cost of connecting all islands
92 | """
93 |
94 | # start with vertex 1 (any vertex can be chosen)
95 | start_vertex = 1
96 |
97 | # initialize a list to keep track of vertices that are visited
98 | visited = [False for _ in range(len(graph) + 1)]
99 |
100 | # initialize starting list - (edge_cost, neighbor)
101 | heap = [(0, start_vertex)]
102 | total_cost = 0
103 |
104 | while len(heap) > 0:
105 | cost, current_vertex = heapq.heappop(heap)
106 |
107 | # check if current_vertex is already visited
108 | if visited[current_vertex]:
109 | continue
110 |
111 | # else add cost to total-cost
112 | total_cost += cost
113 |
114 | for neighbor, edge_cost in graph[current_vertex]:
115 | heapq.heappush(heap, (edge_cost, neighbor))
116 |
117 | # mark current vertex as visited
118 | visited[current_vertex] = True
119 | return total_cost
120 |
121 |
122 | def get_minimum_cost_of_connecting(num_islands, bridge_config):
123 | """
124 | :param: num_islands - number of islands
125 | :param: bridge_config - bridge configuration as explained in the
126 | problem statement
127 | return: cost (int) minimum cost of connecting all islands
128 | """
129 | graph = create_graph(num_islands, bridge_config)
130 | return minimum_cost(graph)
131 |
132 |
133 | # >>>>> TEST IT <<<<<
134 | num_islands = 4
135 | bridge_config = [[1, 2, 1], [2, 3, 4], [1, 4, 3], [4, 3, 2], [1, 3, 10]]
136 | solution = 6
137 |
138 | result = get_minimum_cost_of_connecting(num_islands, bridge_config)
139 | print("\nNumber of islands =", num_islands)
140 | print("Bridge configuration:", bridge_config)
141 | print("Solution should = {} Result = {}".format(solution, result))
142 |
143 | num_islands = 5
144 | bridge_config = [[1, 2, 5], [1, 3, 8], [2, 3, 9]]
145 | solution = 13
146 |
147 | result = get_minimum_cost_of_connecting(num_islands, bridge_config)
148 | print("\nNumber of islands =", num_islands)
149 | print("Bridge configuration:", bridge_config)
150 | print("Solution should = {} Result = {}".format(solution, result))
151 |
152 | num_islands = 5
153 | bridge_config = [[1, 2, 3], [1, 5, 9], [2, 3, 10], [4, 3, 9]]
154 | solution = 31
155 |
156 | result = get_minimum_cost_of_connecting(num_islands, bridge_config)
157 | print("\nNumber of islands =", num_islands)
158 | print("Bridge configuration:", bridge_config)
159 | print("Solution should = {} Result = {}".format(solution, result))
160 |
--------------------------------------------------------------------------------
/practice/graphs/dijkstra.py:
--------------------------------------------------------------------------------
1 | """
2 | dijkstra.py
3 |
4 | Algorithm: Shortest Path (G, s):
5 |
6 | Input: a weighted graph G and a vertix s of G
7 | Output: length of shortest path from s to v for each vertex v of G
8 | initialize distance D|s| = 0 and distance D|v| = infinity
9 | priority queue Q will contain all vertices of G having D labels
10 | as key
11 |
12 | while Q is not empty:
13 | <_push vertex u into queue_>
14 | u = value returned from Q.remove_min()
15 | for each vertex v adjacent to u such that v is in Q:
16 |
17 |
18 | if D|u| + w(u,v) < D|v| :
19 | D|v| = D|u| = w(u,v)
20 |
21 | return the label D|v| of each vertex v
22 |
23 | D|v| will always store the length of the best path we found so far.
24 |
25 | Edges must be non-negative weight values.
26 | """
27 |
28 | import math
29 |
30 |
31 | class GraphEdge(object):
32 | """
33 | In order to run Dijkstra's Algorithm, we need to add distance
34 | to each edge.
35 | """
36 | def __init__(self, node, distance):
37 | self.node = node
38 | self.distance = distance
39 |
40 |
41 | class GraphNode(object):
42 | def __init__(self, val):
43 | self.value = val
44 | self.edges = []
45 |
46 | def add_child(self, node, distance):
47 | self.edges.append(GraphEdge(node, distance))
48 |
49 | def remove_child(self, del_node):
50 | if del_node in self.edges:
51 | self.edges.remove(del_node)
52 |
53 |
54 | class Graph(object):
55 | def __init__(self, node_list):
56 | self.nodes = node_list
57 |
58 | def add_edge(self, node1, node2, distance):
59 | if node1 in self.nodes and node2 in self.nodes:
60 | node1.add_child(node2, distance)
61 | node2.add_child(node1, distance)
62 |
63 | def remove_edge(self, node1, node2):
64 | if node1 in self.nodes and node2 in self.nodes:
65 | node1.remove_child(node2)
66 | node2.remove_child(node1)
67 |
68 |
69 | node_u = GraphNode('U')
70 | node_d = GraphNode('D')
71 | node_a = GraphNode('A')
72 | node_c = GraphNode('C')
73 | node_i = GraphNode('I')
74 | node_t = GraphNode('T')
75 | node_y = GraphNode('Y')
76 |
77 | graph = Graph([node_u, node_d, node_a, node_c, node_i, node_t, node_y])
78 | graph.add_edge(node_u, node_a, 4)
79 | graph.add_edge(node_u, node_c, 6)
80 | graph.add_edge(node_u, node_d, 3)
81 | graph.add_edge(node_d, node_u, 3)
82 | graph.add_edge(node_d, node_c, 4)
83 | graph.add_edge(node_a, node_u, 4)
84 | graph.add_edge(node_a, node_i, 7)
85 | graph.add_edge(node_c, node_d, 4)
86 | graph.add_edge(node_c, node_u, 6)
87 | graph.add_edge(node_c, node_i, 4)
88 | graph.add_edge(node_c, node_t, 5)
89 | graph.add_edge(node_i, node_a, 7)
90 | graph.add_edge(node_i, node_c, 4)
91 | graph.add_edge(node_i, node_y, 4)
92 | graph.add_edge(node_t, node_c, 5)
93 | graph.add_edge(node_t, node_y, 5)
94 | graph.add_edge(node_y, node_i, 4)
95 | graph.add_edge(node_y, node_t, 5)
96 |
97 |
98 |
99 | """
100 | math.inf is useful as an initial value in optimisation problems,
101 | because it works correctly with min,
102 | eg. min(5, math.inf) == 5.
103 |
104 | For example, in shortest path algorithms, you can set unknown
105 | distances to math.inf without needing to special case None or
106 | assume an upper bound 9999999.
107 |
108 | Similarly, you can use -math.inf as a starting value for maximisation
109 | problems.
110 | """
111 |
112 | def dijkstra(start_node, end_node):
113 | distance_dict = {node: math.inf for node in graph.nodes}
114 | shortest_path_to_node = {}
115 |
116 | distance_dict[start_node] = 0
117 | while distance_dict:
118 | # Pop the shorest path
119 | current_node, node_distance = sorted(distance_dict.items(), key=lambda x: x[1])[0]
120 | shortest_path_to_node[current_node] = distance_dict.pop(current_node)
121 |
122 | for edge in current_node.edges:
123 | if edge.node in distance_dict:
124 | new_node_distance = node_distance + edge.distance
125 | if distance_dict[edge.node] > new_node_distance:
126 | distance_dict[edge.node] = new_node_distance
127 |
128 | return shortest_path_to_node[end_node]
129 |
130 |
131 | print('Shortest Distance from {} to {} is {}'.format(node_u.value, node_y.value, dijkstra(node_u, node_y)))
132 |
--------------------------------------------------------------------------------
/practice/graphs/graph_bfs_iteration.py:
--------------------------------------------------------------------------------
1 | """
2 | graph_bfs_iteration.py
3 |
4 | """
5 |
6 | class GraphNode(object):
7 | def __init__(self, val):
8 | self.value = val
9 | self.children = []
10 |
11 | def add_child(self,new_node):
12 | self.children.append(new_node)
13 |
14 | def remove_child(self,del_node):
15 | if del_node in self.children:
16 | self.children.remove(del_node)
17 |
18 |
19 | class Graph(object):
20 | def __init__(self,node_list):
21 | self.nodes = node_list
22 |
23 | def add_edge(self,node1,node2):
24 | if(node1 in self.nodes and node2 in self.nodes):
25 | node1.add_child(node2)
26 | node2.add_child(node1)
27 |
28 | def remove_edge(self,node1,node2):
29 | if(node1 in self.nodes and node2 in self.nodes):
30 | node1.remove_child(node2)
31 | node2.remove_child(node1)
32 |
33 |
34 | nodeG = GraphNode('G')
35 | nodeR = GraphNode('R')
36 | nodeA = GraphNode('A')
37 | nodeP = GraphNode('P')
38 | nodeH = GraphNode('H')
39 | nodeS = GraphNode('S')
40 |
41 | graph1 = Graph([nodeS,nodeH,nodeG,nodeP,nodeR,nodeA] )
42 | graph1.add_edge(nodeG,nodeR)
43 | graph1.add_edge(nodeA,nodeR)
44 | graph1.add_edge(nodeA,nodeG)
45 | graph1.add_edge(nodeR,nodeP)
46 | graph1.add_edge(nodeH,nodeG)
47 | graph1.add_edge(nodeH,nodeP)
48 | graph1.add_edge(nodeS,nodeR)
49 |
50 |
51 | def bfs_search(root_node, search_value):
52 | visited = []
53 | queue = [root_node]
54 |
55 | while len(queue) > 0:
56 | current_node = queue.pop(0)
57 | queue.append(current_node)
58 |
59 | if current_node.value == search_value:
60 | return current_node
61 |
62 | for child in current_node.children:
63 | if child not in visited:
64 | queue.append(child)
65 |
66 |
67 | print( bfs_search(nodeS, 'A') )
68 | print( bfs_search(nodeP, 'S') )
69 | print( bfs_search(nodeH, 'R') )
70 |
71 |
72 | # iterate through the GraphNode children
73 | graph_node = bfs_search(nodeH, 'R')
74 | print("\nGraph Node Parent Value =", graph_node.value)
75 | print("Children (connections):")
76 | for child in graph_node.children:
77 | print(child.value, end=' ')
78 | print(" ")
79 |
80 |
81 | # this hangs up if passed a search value that doesn't exist in graph
82 | #print( bfs_search(nodeH, 'I') )
83 |
84 | def bfs(graph, initial):
85 | # works for dictionary
86 | visited = []
87 | queue = [initial]
88 |
89 | while queue:
90 | node = queue.pop(0)
91 | if node not in visited:
92 |
93 | visited.append(node)
94 |
95 | if node not in graph:
96 | print("Sorry, '{}' is not in graph".format(node))
97 | return None
98 |
99 | neighbours = graph[node]
100 |
101 | for neighbour in neighbours:
102 | queue.append(neighbour)
103 | return visited
104 |
105 | graph = {'A': ['B', 'C', 'E'],
106 | 'B': ['A', 'D', 'E'],
107 | 'C': ['A', 'F', 'G'],
108 | 'D': ['B'],
109 | 'E': ['A', 'B','D'],
110 | 'F': ['C'],
111 | 'G': ['C']}
112 |
113 | print("\nGraph built with a dictionary:")
114 | print("= - = "*15)
115 | print( bfs(graph, 'G') )
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
--------------------------------------------------------------------------------
/practice/graphs/knapsack.py:
--------------------------------------------------------------------------------
1 | """
2 | knapsack.py
3 |
4 | Code Below:
5 | https://codereview.stackexchange.com/questions/20569/dynamic-programming-knapsack-solution
6 |
7 | Also Reference:
8 | https://rosettacode.org/wiki/Knapsack_Problem/Python
9 | """
10 |
11 | import sys
12 |
13 | def knapsack(items, maxweight):
14 | # Create an (N+1) by (W+1) 2-d list to contain the running values
15 | # which are to be filled by the dynamic programming routine.
16 | #
17 | # There are N+1 rows because we need to account for the possibility
18 | # of choosing from 0 up to and including N possible items.
19 | # There are W+1 columns because we need to account for possible
20 | # "running capacities" from 0 up to and including the maximum weight W.
21 | bestvalues = [[0] * (maxweight + 1)
22 | for i in range(len(items) + 1)]
23 |
24 | # Enumerate through the items and fill in the best-value table
25 | for i, (value, weight) in enumerate(items):
26 | # Increment i, because the first row (0) is the case where no items
27 | # are chosen, and is already initialized as 0, so we're skipping it
28 | i += 1
29 | for capacity in range(maxweight + 1):
30 | # Handle the case where the weight of the current item is greater
31 | # than the "running capacity" - we can't add it to the knapsack
32 | if weight > capacity:
33 | bestvalues[i][capacity] = bestvalues[i - 1][capacity]
34 | else:
35 | # Otherwise, we must choose between two possible candidate values:
36 | # 1) the value of "running capacity" as it stands with the last item
37 | # that was computed; if this is larger, then we skip the current item
38 | # 2) the value of the current item plus the value of a previously computed
39 | # set of items, constrained by the amount of capacity that would be left
40 | # in the knapsack (running capacity - item's weight)
41 | candidate1 = bestvalues[i - 1][capacity]
42 | candidate2 = bestvalues[i - 1][capacity - weight] + value
43 |
44 | # Just take the maximum of the two candidates; by doing this, we are
45 | # in effect "setting in stone" the best value so far for a particular
46 | # prefix of the items, and for a particular "prefix" of knapsack capacities
47 | bestvalues[i][capacity] = max(candidate1, candidate2)
48 |
49 | # Reconstruction
50 | # Iterate through the values table, and check
51 | # to see which of the two candidates were chosen. We can do this by simply
52 | # checking if the value is the same as the value of the previous row. If so, then
53 | # we say that the item was not included in the knapsack (this is how we arbitrarily
54 | # break ties) and simply move the pointer to the previous row. Otherwise, we add
55 | # the item to the reconstruction list and subtract the item's weight from the
56 | # remaining capacity of the knapsack. Once we reach row 0, we're done
57 | reconstruction = []
58 | N = len(items)
59 | W = maxweight
60 | while N > 0:
61 | # bestvalues[N][W] is the best sum of values for any
62 | # subsequence of the first N items, whose weights sum
63 | # to no more than W.
64 | if bestvalues[N][W] != bestvalues[N - 1][W]:
65 | reconstruction.append(items[N - 1])
66 | W -= items[N - 1][1]
67 | N -= 1
68 |
69 | # Reverse the reconstruction list, so that it is presented
70 | # in the order that it was given
71 | reconstruction.reverse()
72 |
73 | # Return the best value, and the reconstruction list
74 | return bestvalues[len(items)][maxweight], reconstruction
75 |
76 | """
77 | >>>>> O R I G I N A L C O D E <<<<<
78 | if __name__ == '__main__':
79 | if len(sys.argv) != 2:
80 | print('usage: knapsack.py [file]')
81 | sys.exit(1)
82 |
83 | filename = sys.argv[1]
84 | with open(filename) as f:
85 | lines = f.readlines()
86 |
87 | maxweight = int(lines[0])
88 | items = [map(int, line.split()) for line in lines[1:]]
89 |
90 | bestvalue, reconstruction = knapsack(items, maxweight)
91 |
92 | print('Best possible value: {0}'.format(bestvalue))
93 | print('Items:')
94 | for value, weight in reconstruction:
95 | print('V: {0}, W: {1}'.format(value, weight))
96 | """
97 |
98 |
99 | """
100 | # Some test values
101 | # items [value, weight]
102 | maxweight = 165
103 | items = [
104 | [92, 23],
105 | [57, 31],
106 | [49, 29],
107 | [68, 44],
108 | [60, 53],
109 | [43, 38],
110 | [67, 63],
111 | [84, 85],
112 | [87, 89],
113 | [72, 82]
114 | ]
115 |
116 | maxweight = 15
117 | items = [ [7,10], [8,9], [6,5] ]
118 | """
119 |
120 | maxweight = 25
121 | items = [ [2,10], [10,29], [7,5], [3,5], [1,5], [12,24] ]
122 |
123 | bestvalue, reconstruction = knapsack(items, maxweight)
124 |
125 | print('Best possible value: {0}'.format(bestvalue))
126 | print('Items:')
127 | for value, weight in reconstruction:
128 | print('V: {0}, W: {1}'.format(value, weight))
129 |
130 |
131 |
--------------------------------------------------------------------------------
/practice/graphs/longest_common_subsequence.odt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ssi112/data-structures-algorithms/e9bdfa4f41b5dd68b3227151084a8c0115bac8b8/practice/graphs/longest_common_subsequence.odt
--------------------------------------------------------------------------------
/practice/graphs/longest_common_subsequence.py:
--------------------------------------------------------------------------------
1 | """
2 | longest_common_subsequence.py
3 |
4 | Dynamic Programming
5 | Breaking a larger problem into a smaller set of subproblems, and
6 | building up a complete result without having to repeat any subproblems.
7 |
8 | The Matrix Rules
9 | You can efficiently fill up this matrix one cell at a time.
10 | Each grid cell only depends on the values in the grid cells
11 | that are directly on top and to the left of it, or on the
12 | diagonal/top-left. The rules are as follows:
13 |
14 | Start with a matrix that has one extra row and column of zeros.
15 | As you traverse your string:
16 |
17 | If there is a match, fill that grid cell with the value to the
18 | top-left of that cell plus one.
19 |
20 | If there is not a match, take the maximum value from either
21 | directly to the left or the top cell, and carry that value over
22 | to the non-match cell.
23 |
24 | After completely filling the matrix, the bottom-right cell will
25 | hold the non-normalized LCS value.
26 |
27 | Reference longest_common_subsequence.odt for complete explanation
28 | """
29 |
30 | def lcs(string_a, string_b):
31 | lookup_table = [[0 for x in range(len(string_b) + 1)] for x in range(len(string_a) + 1)]
32 |
33 | string_a = string_a.lower()
34 | string_b = string_b.lower()
35 | space = ' '
36 |
37 | for char_a_i, char_a in enumerate(string_a):
38 | for char_b_i, char_b in enumerate(string_b):
39 | if char_a == char_b:
40 | if char_a is not space:
41 | lookup_table[char_a_i + 1][char_b_i + 1] = lookup_table[char_a_i][char_b_i] + 1
42 | else:
43 | lookup_table[char_a_i + 1][char_b_i + 1] = max(
44 | lookup_table[char_a_i][char_b_i + 1],
45 | lookup_table[char_a_i + 1][char_b_i])
46 |
47 | return lookup_table[-1][-1]
48 |
49 |
50 | ## >>>>> Test <<<<<
51 | test_A1 = "WHO WEEKLY"
52 | test_B1 = "HOW ONLY"
53 |
54 | lcs_val1 = lcs(test_A1, test_B1)
55 |
56 | test_A2 = "CATS IN SPACE TWO"
57 | test_B2 = "DOGS PACE WHO"
58 |
59 | lcs_val2 = lcs(test_A2, test_B2)
60 |
61 | print('LCS val 1 =', lcs_val1)
62 | assert lcs_val1==5, "Incorrect LCS value."
63 | print('LCS val 2 =', lcs_val2)
64 | assert lcs_val2==7, "Incorrect LCS value."
65 | print('Tests passed!')
66 |
67 | test_A2 = "An apple a day keeps the doctor away"
68 | test_B2 = "Never compare an apple to an orange"
69 |
70 | lcs_val2 = lcs(test_A2, test_B2)
71 | print('LCS val 2 =', lcs_val2)
72 | print("string A2: {}, length: {:d}, {:05.3f}%".format(test_A2, len(test_A2), lcs_val2 / len(test_A2)))
73 | print("string B2: {}, length: {:d}, {:05.3f}%".format(test_B2, len(test_B2), lcs_val2 / len(test_A2)))
74 |
75 |
76 |
--------------------------------------------------------------------------------
/practice/kadane_algorithm.py:
--------------------------------------------------------------------------------
1 | """
2 | Problem Statement
3 |
4 | You have been given an array containg numbers.
5 | Find and return the largest sum in a contiguous subarray within the input array.
6 |
7 | Example 1:
8 | arr= [1, 2, 3, -4, 6]
9 | The largest sum is 8, which is the sum of all elements of the array.
10 |
11 | Example 2:
12 | arr = [1, 2, -5, -4, 1, 6]
13 | The largest sum is 7, which is the sum of the last two elements of the array.
14 | """
15 |
16 | # they apparently are not teaching much of anything in this course:
17 | # Kadane’s Algorithm:
18 | # https://www.geeksforgeeks.org/largest-sum-contiguous-subarray/
19 |
20 | import sys
21 | def maxSubArraySum(a, size):
22 | max_so_far = -sys.maxsize - 1
23 | max_ending_here = 0
24 |
25 | for i in range(0, size):
26 | max_ending_here = max_ending_here + a[i]
27 | if (max_so_far < max_ending_here):
28 | max_so_far = max_ending_here
29 |
30 | if max_ending_here < 0:
31 | max_ending_here = 0
32 | return max_so_far
33 |
34 | def max_sum_subarray(arr):
35 | """
36 | :param - arr - input array
37 | return - number - largest sum in contiguous subarry within arr
38 | """
39 | size = len(arr)
40 | max_so_far = -sys.maxsize - 1
41 | max_ending_here = 0
42 |
43 | for i in range(0, size):
44 | max_ending_here = max_ending_here + arr[i]
45 | if (max_so_far < max_ending_here):
46 | max_so_far = max_ending_here
47 |
48 | if max_ending_here < 0:
49 | max_ending_here = 0
50 | return max_so_far
51 |
52 | # Driver function to check the above function
53 | # a = [1, 2, -5, -4, 1, 6]
54 | # a = [1, 2, 3, -4, 6]
55 | a = [-13, -3, -25, -20, -3, -16, -23, -12, -5, -22, -15, -4, -7]
56 | print("Maximum contiguous sum is", maxSubArraySum(a,len(a)))
57 |
58 | print("Maximum contiguous sum is", max_sum_subarray(a))
59 |
60 |
61 |
--------------------------------------------------------------------------------
/practice/linked_lists/4_reverse_linked_list.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "# Reversing a linked list exercise\n",
8 | "\n",
9 | "Given a singly linked list, return another linked list that is the reverse of the first."
10 | ]
11 | },
12 | {
13 | "cell_type": "code",
14 | "execution_count": 11,
15 | "metadata": {},
16 | "outputs": [],
17 | "source": [
18 | "# Helper Code\n",
19 | "\n",
20 | "class Node:\n",
21 | " def __init__(self, value):\n",
22 | " self.value = value\n",
23 | " self.next = None\n",
24 | "\n",
25 | "class LinkedList:\n",
26 | " def __init__(self):\n",
27 | " self.head = None\n",
28 | " \n",
29 | " def append(self, value):\n",
30 | " if self.head is None:\n",
31 | " self.head = Node(value)\n",
32 | " return\n",
33 | " \n",
34 | " node = self.head\n",
35 | " while node.next:\n",
36 | " node = node.next\n",
37 | "\n",
38 | " node.next = Node(value)\n",
39 | " \n",
40 | " def __iter__(self):\n",
41 | " node = self.head\n",
42 | " while node:\n",
43 | " yield node.value\n",
44 | " node = node.next\n",
45 | " \n",
46 | " def __repr__(self):\n",
47 | " return str([v for v in self])\n",
48 | " \n",
49 | " def printNodes(self):\n",
50 | " curr = self.head\n",
51 | " while curr:\n",
52 | " print(curr.value)\n",
53 | " curr = curr.next"
54 | ]
55 | },
56 | {
57 | "cell_type": "code",
58 | "execution_count": 12,
59 | "metadata": {},
60 | "outputs": [],
61 | "source": [
62 | "# Time complexity O(N)\n",
63 | "def reverse(linked_list):\n",
64 | " \"\"\"\n",
65 | " Reverse the inputted linked list\n",
66 | "\n",
67 | " Args:\n",
68 | " linked_list(obj): Linked List to be reversed\n",
69 | " Returns:\n",
70 | " obj: Reveresed Linked List\n",
71 | " \"\"\"\n",
72 | " new_list = LinkedList()\n",
73 | " node = linked_list.head\n",
74 | " prev_node = None\n",
75 | "\n",
76 | " # A bit of a complex operation here. We want to take the\n",
77 | " # node from the original linked list and prepend it to \n",
78 | " # the new linked list\n",
79 | " for value in linked_list:\n",
80 | " new_node = Node(value)\n",
81 | " new_node.next = prev_node\n",
82 | " prev_node = new_node\n",
83 | " new_list.head = prev_node\n",
84 | " return new_list"
85 | ]
86 | },
87 | {
88 | "cell_type": "code",
89 | "execution_count": 13,
90 | "metadata": {},
91 | "outputs": [
92 | {
93 | "name": "stdout",
94 | "output_type": "stream",
95 | "text": [
96 | "Pass\n",
97 | "4\n",
98 | "2\n",
99 | "5\n",
100 | "1\n",
101 | "-3\n",
102 | "0\n"
103 | ]
104 | }
105 | ],
106 | "source": [
107 | "llist = LinkedList()\n",
108 | "for value in [4,2,5,1,-3,0]:\n",
109 | " llist.append(value)\n",
110 | "\n",
111 | "flipped = reverse(llist)\n",
112 | "is_correct = list(flipped) == list([0,-3,1,5,2,4]) and list(llist) == list(reverse(flipped))\n",
113 | "print(\"Pass\" if is_correct else \"Fail\")\n",
114 | "\n",
115 | "llist.printNodes()"
116 | ]
117 | },
118 | {
119 | "cell_type": "code",
120 | "execution_count": null,
121 | "metadata": {},
122 | "outputs": [],
123 | "source": []
124 | }
125 | ],
126 | "metadata": {
127 | "kernelspec": {
128 | "display_name": "Python 3",
129 | "language": "python",
130 | "name": "python3"
131 | },
132 | "language_info": {
133 | "codemirror_mode": {
134 | "name": "ipython",
135 | "version": 3
136 | },
137 | "file_extension": ".py",
138 | "mimetype": "text/x-python",
139 | "name": "python",
140 | "nbconvert_exporter": "python",
141 | "pygments_lexer": "ipython3",
142 | "version": "3.7.3"
143 | }
144 | },
145 | "nbformat": 4,
146 | "nbformat_minor": 2
147 | }
148 |
--------------------------------------------------------------------------------
/practice/linked_lists/5_detecting_loops.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {
6 | "graffitiCellId": "id_0d6ntqj"
7 | },
8 | "source": [
9 | "# Detecting Loops in Linked Lists\n",
10 | "\n",
11 | "In this notebook, you'll implement a function that detects if a loop exists in a linked list. The way we'll do this is by having two pointers, called \"runners\", moving through the list at different rates. Typically we have a \"slow\" runner which moves at one node per step and a \"fast\" runner that moves at two nodes per step.\n",
12 | "\n",
13 | "If a loop exists in the list, the fast runner will eventually move behind the slow runner as it moves to the beginning of the loop. Eventually it will catch up to the slow runner and both runners will be pointing to the same node at the same time. If this happens then you know there is a loop in the linked list. Below is an example where we have a slow runner (the green arrow) and a fast runner (the red arrow).\n",
14 | "\n",
15 | "
"
16 | ]
17 | },
18 | {
19 | "cell_type": "code",
20 | "execution_count": 1,
21 | "metadata": {
22 | "graffitiCellId": "id_kqi6rp6"
23 | },
24 | "outputs": [],
25 | "source": [
26 | "class Node:\n",
27 | " def __init__(self, value):\n",
28 | " self.value = value\n",
29 | " self.next = None\n",
30 | " \n",
31 | "class LinkedList:\n",
32 | " def __init__(self, init_list=None):\n",
33 | " self.head = None\n",
34 | " if init_list:\n",
35 | " for value in init_list:\n",
36 | " self.append(value)\n",
37 | " \n",
38 | " def append(self, value):\n",
39 | " if self.head is None:\n",
40 | " self.head = Node(value)\n",
41 | " return\n",
42 | " \n",
43 | " # Move to the tail (the last node)\n",
44 | " node = self.head\n",
45 | " while node.next:\n",
46 | " node = node.next\n",
47 | " node.next = Node(value)\n",
48 | " return"
49 | ]
50 | },
51 | {
52 | "cell_type": "code",
53 | "execution_count": 2,
54 | "metadata": {
55 | "graffitiCellId": "id_17pg09d"
56 | },
57 | "outputs": [],
58 | "source": [
59 | "list_with_loop = LinkedList([2, -1, 3, 0, 5])\n",
60 | "\n",
61 | "# Creating a loop where the last node points back to the second node\n",
62 | "loop_start = list_with_loop.head.next\n",
63 | "\n",
64 | "node = list_with_loop.head\n",
65 | "while node.next: \n",
66 | " node = node.next \n",
67 | "node.next = loop_start"
68 | ]
69 | },
70 | {
71 | "cell_type": "markdown",
72 | "metadata": {
73 | "graffitiCellId": "id_kwwwkfx"
74 | },
75 | "source": [
76 | "### Exercise: \n",
77 | "\n",
78 | "**Given a linked list, implement a function `iscircular` that returns `True` if a loop exists in the list and `False` otherwise.**"
79 | ]
80 | },
81 | {
82 | "cell_type": "code",
83 | "execution_count": 3,
84 | "metadata": {
85 | "graffitiCellId": "id_bgz20kt"
86 | },
87 | "outputs": [],
88 | "source": [
89 | "def iscircular(linked_list):\n",
90 | " \"\"\"\n",
91 | " Determine whether the Linked List is circular or not\n",
92 | "\n",
93 | " Args:\n",
94 | " linked_list(obj): Linked List to be checked\n",
95 | " Returns:\n",
96 | " bool: Return True if the linked list is circular, return False otherwise\n",
97 | " \"\"\"\n",
98 | " if linked_list.head is None:\n",
99 | " return False\n",
100 | " \n",
101 | " slow = linked_list.head\n",
102 | " fast = linked_list.head\n",
103 | " \n",
104 | " while fast and fast.next:\n",
105 | " # slow pointer moves one node\n",
106 | " slow = slow.next\n",
107 | " # fast pointer moves two nodes\n",
108 | " fast = fast.next.next\n",
109 | " \n",
110 | " if slow == fast:\n",
111 | " return True\n",
112 | " \n",
113 | " # If we get to a node where fast doesn't have a next node or doesn't exist itself, \n",
114 | " # the list has an end and isn't circular\n",
115 | " return False\n"
116 | ]
117 | },
118 | {
119 | "cell_type": "code",
120 | "execution_count": 4,
121 | "metadata": {
122 | "graffitiCellId": "id_fob6797"
123 | },
124 | "outputs": [
125 | {
126 | "name": "stdout",
127 | "output_type": "stream",
128 | "text": [
129 | "Pass\n",
130 | "Pass\n",
131 | "Pass\n",
132 | "Pass\n",
133 | "Pass\n"
134 | ]
135 | }
136 | ],
137 | "source": [
138 | "# Test Cases\n",
139 | "\n",
140 | "small_loop = LinkedList([0])\n",
141 | "small_loop.head.next = small_loop.head\n",
142 | "print (\"Pass\" if iscircular(list_with_loop) else \"Fail\")\n",
143 | "print (\"Pass\" if not iscircular(LinkedList([-4, 7, 2, 5, -1])) else \"Fail\")\n",
144 | "print (\"Pass\" if not iscircular(LinkedList([1])) else \"Fail\")\n",
145 | "print (\"Pass\" if iscircular(small_loop) else \"Fail\")\n",
146 | "print (\"Pass\" if not iscircular(LinkedList([])) else \"Fail\")\n"
147 | ]
148 | },
149 | {
150 | "cell_type": "code",
151 | "execution_count": null,
152 | "metadata": {},
153 | "outputs": [],
154 | "source": []
155 | }
156 | ],
157 | "metadata": {
158 | "graffiti": {
159 | "firstAuthorId": "10694620118",
160 | "id": "id_ao3gp0i",
161 | "language": "EN"
162 | },
163 | "kernelspec": {
164 | "display_name": "Python 3",
165 | "language": "python",
166 | "name": "python3"
167 | },
168 | "language_info": {
169 | "codemirror_mode": {
170 | "name": "ipython",
171 | "version": 3
172 | },
173 | "file_extension": ".py",
174 | "mimetype": "text/x-python",
175 | "name": "python",
176 | "nbconvert_exporter": "python",
177 | "pygments_lexer": "ipython3",
178 | "version": "3.7.3"
179 | }
180 | },
181 | "nbformat": 4,
182 | "nbformat_minor": 2
183 | }
184 |
--------------------------------------------------------------------------------
/practice/linked_lists/even-after-odd.py:
--------------------------------------------------------------------------------
1 | """
2 | Problem Statement
3 |
4 | Given a linked list with integer data, arrange the elements in such a
5 | manner that all nodes with even numbers are placed after odd numbers.
6 | Do not create any new nodes and avoid using any other data structure.
7 | The relative order of even and odd elements must not change.
8 |
9 | Example:
10 | linked list = 1 2 3 4 5 6
11 | output = 1 3 5 2 4 6
12 |
13 | """
14 |
15 | class Node:
16 | def __init__(self, data):
17 | self.data = data
18 | self.next = None
19 |
20 |
21 | # helper functions for testing purpose
22 | def create_linked_list(arr):
23 | if len(arr)==0:
24 | return None
25 | head = Node(arr[0])
26 | tail = head
27 | for data in arr[1:]:
28 | tail.next = Node(data)
29 | tail = tail.next
30 | return head
31 |
32 | def print_linked_list(head):
33 | while head:
34 | print(head.data, end=' ')
35 | head = head.next
36 | print()
37 |
38 |
39 | def even_after_odd(head):
40 | """
41 | :param - head - head of linked list
42 | return - updated list with all even elements are odd elements
43 | """
44 | if head is None:
45 | return head
46 |
47 | even = None
48 | odd = None
49 | even_tail = None
50 | head_tail = None
51 |
52 | while head:
53 | next_node = head.next
54 | if head.data % 2 == 0:
55 | if even is None:
56 | even = head
57 | even_tail = even
58 | else:
59 | even_tail.next = head
60 | even_tail = even_tail.next
61 | else:
62 | if odd is None:
63 | odd = head
64 | odd_tail = odd
65 | else:
66 | odd_tail.next = head
67 | odd_tail = odd_tail.next
68 | head.next = None
69 | head = next_node
70 |
71 | if odd is None:
72 | return even
73 | odd_tail.next = even
74 | return odd
75 |
76 | def main():
77 | arr = [1, 2, 3, 4, 5, 6]
78 | head = create_linked_list(arr)
79 | print("Before even-odd: ")
80 | print_linked_list(head)
81 |
82 | print("\nAfter even-odd: ")
83 | even_odd = even_after_odd(head)
84 | print_linked_list(head)
85 |
86 |
87 | if __name__ == '__main__':
88 | main()
89 |
--------------------------------------------------------------------------------
/practice/maps_hashing/count-stair-hops.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ssi112/data-structures-algorithms/e9bdfa4f41b5dd68b3227151084a8c0115bac8b8/practice/maps_hashing/count-stair-hops.jpg
--------------------------------------------------------------------------------
/practice/maps_hashing/longest_consecutive_subsequence.py:
--------------------------------------------------------------------------------
1 | """
2 | longest_consecutive_subsequence.py
3 |
4 | Problem Statement
5 |
6 | Given list of integers that contain numbers in random order, write a program to
7 | find the longest possible sub sequence of consecutive numbers in the array.
8 | Return this subsequence in sorted order. The solution must take O(n) time
9 |
10 | For e.g. given the list 5, 4, 7, 10, 1, 3, 55, 2, the output should be 1, 2, 3, 4, 5
11 | """
12 | def longest_consecutive_subsequence(input_list):
13 | element_dict = dict()
14 |
15 | for index, element in enumerate(input_list):
16 | element_dict[element] = index
17 |
18 | # print(element_dict)
19 |
20 | max_length = -1
21 | starts_at = len(input_list)
22 |
23 | for index, element in enumerate(input_list):
24 | current_starts = index
25 | element_dict[element] = -1
26 |
27 | current_count = 1
28 |
29 | # check upwards
30 | current = element + 1
31 |
32 | while current in element_dict and element_dict[current] > 0:
33 | current_count += 1
34 | element_dict[current] = -1
35 | current = current + 1
36 |
37 | # check downwards
38 | current = element - 1
39 | while current in element_dict and element_dict[current] > 0:
40 | current_starts = element_dict[current]
41 | current_count += 1
42 | element_dict[current] = -1
43 | current = current - 1
44 |
45 | if current_count >= max_length:
46 | if current_count == max_length and current_starts > starts_at:
47 | continue
48 | starts_at = current_starts
49 | max_length = current_count
50 |
51 | start_element = input_list[starts_at]
52 | return [element for element in range(start_element, start_element + max_length)]
53 |
54 |
55 | """
56 | --------------------------------------------------------------------------------------
57 | https://www.geeksforgeeks.org/longest-consecutive-subsequence/
58 |
59 | Given an array of integers, find the length of the longest sub-sequence such that
60 | elements in the subsequence are consecutive integers, the consecutive numbers can
61 | be in any order.
62 |
63 | Return this subsequence in sorted order.
64 |
65 | The solution must take O(n) time
66 |
67 | For e.g. given the list 5, 4, 7, 10, 1, 3, 55, 2, the output should be 1, 2, 3, 4, 5
68 |
69 | One Solution is to first sort the array and find the longest subarray with consecutive
70 | elements. Time complexity of this solution is O(nLogn).
71 |
72 | We can solve this problem in O(n) time using an Efficient Solution.
73 |
74 | The idea is to use Hashing. We first insert all elements in a Set.
75 | Then check all the possible starts of consecutive subsequences.
76 |
77 | Below is the complete algorithm.
78 |
79 | Create an empty hash.
80 | Insert all array elements to hash.
81 | Do following for every element arr[i]
82 | Check if this element is the starting point of a subsequence.
83 | To check this, we simply look for arr[i] – 1 in the hash, if not found,
84 | then this is the first element in subsequence.
85 | If this element is the first element, then count number of elements in the
86 | consecutive starting with this element. Iterate from arr[i] + 1 till
87 | the last element that can be found.
88 | If the count is more than the previous longest subsequence found, then update this.
89 | """
90 | def length_longest_consecutive_subsequence(input_list):
91 | s = set()
92 | n = len(input_list)
93 | ans = 0
94 | out_list = []
95 |
96 | # Hash all the array elements
97 | for ele in input_list:
98 | s.add(ele)
99 |
100 | # check each possible sequence from the start
101 | # then update optimal length
102 | for i in range(n):
103 |
104 | # if current element is the starting element of a sequence
105 | if (input_list[i] - 1) not in s:
106 | # Then check for next elements in the sequence
107 | j = input_list[i]
108 | while(j in s):
109 | j += 1
110 | out_list.append(j - 1)
111 |
112 | # update optimal length if this length is more
113 | ans = max(ans, j - input_list[i])
114 | return ans
115 |
116 |
117 | test = [5, 4, 7, 10, 1, 3, 55, 2]
118 | # s/be [1, 2, 3, 4, 5]
119 | print("\n-----------------------------------------------------------------------")
120 | print("test: {} = {}".format(test, length_longest_consecutive_subsequence(test)))
121 | print("test: {} = {}".format(test, longest_consecutive_subsequence(test)))
122 |
123 | test = [2, 12, 9, 16, 10, 5, 3, 20, 25, 11, 1, 8, 6 ]
124 | # s/be [8, 9, 10, 11, 12]
125 | print("\n-----------------------------------------------------------------------")
126 | print("test: {} = {}".format(test, length_longest_consecutive_subsequence(test)))
127 | print("test: {} = {}".format(test, longest_consecutive_subsequence(test)))
128 |
129 | test = [0, 1, 2, 3, 4]
130 | # s/be [0, 1, 2, 3, 4]
131 | print("\n-----------------------------------------------------------------------")
132 | print("test: {} = {}".format(test, length_longest_consecutive_subsequence(test)))
133 | print("test: {} = {}".format(test, longest_consecutive_subsequence(test)))
134 |
135 |
136 |
--------------------------------------------------------------------------------
/practice/maps_hashing/maps_dict.py:
--------------------------------------------------------------------------------
1 | """
2 | maps_dict.py
3 |
4 | In Python, the map concept appears as a built-in data type called a dictionary.
5 | A dictionary contains key-value pairs.
6 | """
7 |
8 | locations = {'North America': {'USA': ['Mountain View']}}
9 | locations['Asia'] = {'India': ['Bangalore']}
10 | locations['North America']['USA'].append('Atlanta')
11 | locations['Africa'] = {'Egypt': 'Cairo'}
12 | locations['Asia'].update( {'China': ['Shanghai']} )
13 | locations['North America'].update({'Mexico': ['Mexico City']})
14 | locations['North America']['Mexico'].append('Guadalajara')
15 | locations['North America']['Mexico'].append('Juárez')
16 | locations['North America']['Mexico'].append('Guadalupe')
17 | locations['North America']['Mexico'].append('Nuevo Laredo')
18 |
19 | # TODO: Print a list of all cities in the USA in alphabetic order.
20 | country_select = "Mexico"
21 | usa_city_list = sorted(locations['North America'][country_select])
22 |
23 | # print (usa_city_list)
24 | print("="*25)
25 |
26 | for city in usa_city_list:
27 | print (city)
28 | print("="*25)
29 |
30 | import operator
31 | asia_city_list = []
32 |
33 | # TODO: Print all cities in Asia, in alphabetic order, next to the name of the country
34 | for country, cities in locations['Asia'].items():
35 | # print(cities, country)
36 | asia_city_list.append(cities[0] + ', ' + country)
37 |
38 | # sort list on multiple attributes
39 | asia_sorted = sorted(asia_city_list, key = operator.itemgetter(1, 2))
40 | # print(asia_sorted)
41 |
42 | for val in asia_sorted:
43 | print(val)
44 |
45 | # print(locations)
46 |
--------------------------------------------------------------------------------
/practice/maps_hashing/pair_sum_to_target.py:
--------------------------------------------------------------------------------
1 | """
2 | pair_sum_to_target.py
3 |
4 | Problem statement
5 |
6 | Given an input_list and a target, return the indices of pair of integers in the
7 | list that sum to the target. The best solution takes O(n) time.
8 | You can assume that the list does not have any duplicates.
9 |
10 | For e.g. input_list = [1, 5, 9, 7] and target = 8, the answer would be [0, 3]
11 | """
12 |
13 | def pair_sum_to_zero(in_list, target):
14 | # TODO: Write pair sum to zero function
15 |
16 | first_index = 0
17 | last_index = len(in_list) - 1
18 |
19 | while first_index < last_index:
20 | pair_sum = in_list[first_index] + in_list[last_index]
21 | if pair_sum < target:
22 | first_index += 1
23 | elif pair_sum > target:
24 | last_index -= 1
25 | else:
26 | # below is the actual values
27 | # in_list[first_index], in_list[last_index]
28 | return [first_index, last_index]
29 | return [None, None]
30 |
31 |
32 | input_list = [1, 5, 9, 7]
33 | print(pair_sum_to_zero(input_list, 8)) # [0, 3]
34 |
35 | input_list = [10, 5, 9, 8, 12, 1, 16, 6]
36 | print(pair_sum_to_zero(input_list, 16)) # [0, 7]
37 |
38 | input_list = [0, 1, 2, 3, -4]
39 | print(pair_sum_to_zero(input_list, -4)) # [0, 4]
40 |
41 | input_list = [0, 1, 2, 3, -4, 77, 101, -3, 11, 81]
42 | print(pair_sum_to_zero(input_list, -11)) # [None, None]
43 |
44 |
--------------------------------------------------------------------------------
/practice/maps_hashing/string_hash_table.py:
--------------------------------------------------------------------------------
1 | """
2 | string_hash_table.py
3 |
4 | Write your own hash table and hash function that uses string keys.
5 | Your table will store strings in buckets by their first two letters,
6 | according to the formula below:
7 |
8 | Hash Value = (ASCII Value of First Letter * 100) + ASCII Value of Second Letter
9 |
10 | You can assume that the string will have at least two letters, and the first two
11 | characters are uppercase letters (ASCII values from 65 to 90). You can use the
12 | Python function ord() to get the ASCII value of a letter, and chr() to get the
13 | letter associated with an ASCII value.
14 |
15 | You'll create a HashTable class, methods to store and lookup values, and a
16 | helper function to calculate a hash value given a string.
17 |
18 | You cannot use a Python dictionary—only lists!
19 |
20 | And remember to store lists at each bucket, and not just the string itself.
21 | For example, you can store "UDACITY" at index 8568
22 | as ["UDACITY"].
23 | """
24 |
25 |
26 | class HashTable(object):
27 | def __init__(self):
28 | self.table = [None]*10000
29 |
30 | def store(self, string):
31 | """TODO: Input a string that's stored in
32 | the table."""
33 | hashed = self.calculate_hash_value(string)
34 | if hashed: # != -1:
35 | if self.table[hashed] != None:
36 | self.table[hashed].append(string)
37 | else:
38 | self.table[hashed] = [string]
39 |
40 | def lookup(self, string):
41 | """TODO: Return the hash value if the
42 | string is already in the table.
43 | Return -1 otherwise."""
44 | hashed = self.calculate_hash_value(string)
45 | if hashed: # != -1:
46 | if self.table[hashed] != None:
47 | if string in self.table[hashed]:
48 | return hashed
49 | return -1
50 |
51 | def calculate_hash_value(self, string):
52 | """TODO: Helper function to calulate a
53 | hash value from a string."""
54 | return ord(string[0]) * 100 + ord(string[1])
55 |
56 |
57 |
58 | hashish = HashTable()
59 | print("hashish value (UDACITY) =", hashish.calculate_hash_value("UDACITY"))
60 |
61 | print("hashish lookup (UDACITY) =", hashish.lookup("UDACITY"))
62 |
63 | hashish.store("UDACITY")
64 | print("hashish lookup (UDACITY) =", hashish.lookup("UDACITY"))
65 |
66 | print("hashish lookup (UDACIOUS) =", hashish.lookup("UDACIOUS"))
67 |
68 | hashish.store("Esteban")
69 | print("hashish lookup (Esteban) =", hashish.lookup("Esteban"))
70 | hashish.store("ESTEBAN")
71 | print("hashish lookup (ESTEBAN) =", hashish.lookup("ESTEBAN"))
72 |
73 |
--------------------------------------------------------------------------------
/practice/pascals_triangle.py:
--------------------------------------------------------------------------------
1 | """
2 | Problem Statement
3 |
4 | Find and return the nth row of Pascal's triangle in the form a list. n is 0-based.
5 |
6 | For exmaple, if n = 4, then output = [1, 4, 6, 4, 1].
7 |
8 | To know more about Pascal's triangle: https://www.mathsisfun.com/pascals-triangle.html
9 |
10 | """
11 |
12 | # once again they are not attempting to teach anything in this course:
13 | # Pascal's Triangle
14 | # https://spiderlabweb.com/python-program-print-pascals-triangle/
15 |
16 |
17 | def nth_row_pascal(n):
18 | """
19 | :param: - n - index (0 based)
20 | return - list() representing nth row of Pascal's triangle
21 | """
22 | if n == 0:
23 | return [1]
24 | current_row = [1]
25 | for i in range(1, n + 1):
26 | previous_row = current_row
27 | current_row = [1]
28 | for j in range(1, i):
29 | next_number = previous_row[j] + previous_row[j - 1]
30 | current_row.append(next_number)
31 | current_row.append(1)
32 | return current_row
33 |
34 |
35 | # factorial
36 | def fact(n):
37 | res=1
38 | for c in range(1, n + 1):
39 | res = res * c
40 | return res
41 |
42 |
43 | def pascal_triangle_fact(rows):
44 | for i in range(0, rows):
45 | for j in range(1, rows-i):
46 | print(" ", end="")
47 | for k in range(0, i+1):
48 | coff = int(fact(i) / (fact(k) * fact(i - k)))
49 | print(" ", coff, end="")
50 | print()
51 |
52 |
53 | def pascal_triangle(rows):
54 | for i in range(0, rows):
55 | coff = 1
56 | for j in range(1, rows - i):
57 | print(" ", end="")
58 | for k in range(0, i+1):
59 | print(" ", coff, end="")
60 | coff = int(coff * (i - k) / (k + 1))
61 | print()
62 |
63 |
64 | def main():
65 | rows = int(input("Enter the number of rows : "))
66 | pascal_triangle_fact(rows)
67 |
68 | # without using the factorial function
69 | rows = int(input("Enter the number of rows : "))
70 | pascal_triangle(rows)
71 |
72 | rows = int(input("Enter the number of rows : "))
73 | print("Udacity's lack of teaching solution", nth_row_pascal(rows - 1))
74 |
75 | if __name__ == '__main__':
76 | main()
77 |
--------------------------------------------------------------------------------
/practice/queues/priority_queue.py:
--------------------------------------------------------------------------------
1 | """
2 | priority_queue.py
3 |
4 | min priority queue - lower priority elements are added to the front
5 | of the queue (smaller numbers have higher priority)
6 |
7 | elements are added or removed based on priority
8 |
9 | priority is set and elements are added at the proper position
10 |
11 | when dequeued, the item with highest priority is removed first
12 |
13 | the queue will remain sorted by priority
14 | """
15 |
16 | class QueueElement(object):
17 | element = ""
18 | priority = 0.0
19 |
20 | def __init__(self, element = None, priority = None):
21 | self.element = element
22 | self.priority = priority
23 |
24 | class PriorityQueue(object):
25 | def __init__(self):
26 | self.queue = []
27 |
28 | def enqueue(self, element, priority):
29 | queueElement = QueueElement(element, priority)
30 | if self.isEmpty():
31 | # push the first item onto the queue
32 | self.queue.append(queueElement)
33 | else:
34 | added = False
35 | for inx, queueItem in enumerate(self.queue):
36 | if queueElement.priority < queueItem.priority:
37 | # insert the new element one position before
38 | # respects other elements with same priority
39 | # but were added first
40 | # inx = index and where to insert new element
41 | self.queue.insert(inx, queueElement)
42 | added = True
43 | # stop searching
44 | break
45 | if added == False:
46 | # if no other elements are greater than this element's
47 | # priority then add it to the end of the queue
48 | self.queue.append(queueElement)
49 |
50 | def dequeue(self):
51 | # remove & return highest priority item
52 | return self.queue.pop(0)
53 |
54 | def removeLowestPriorityItem(self):
55 | # this will remove and return lowest priority item
56 | # used if size is fixed and space is needed
57 | lastItem = self.queue[-1]
58 | del self.queue[-1]
59 | return lastItem
60 |
61 | def front(self):
62 | return self.queue[0]
63 |
64 | def isEmpty(self):
65 | if len(self.queue) == 0:
66 | return True
67 | return False
68 |
69 | def clearQueue(self):
70 | self.queue = []
71 |
72 | def size(self):
73 | return len(self.queue)
74 |
75 | def __repr__(self):
76 | return ''.join([str(inx) + '-' + str(queueItem.priority) + '-' + str(queueItem.element) + '\n' for inx, queueItem in enumerate(self.queue)])
77 |
78 |
--------------------------------------------------------------------------------
/practice/queues/priority_queue_test01.py:
--------------------------------------------------------------------------------
1 | """
2 | priority_queue_test01.py
3 |
4 | some tests for priority_queue.py
5 |
6 | TESTING 1, 2, 3
7 | see https://repl.it/@ssi112/PriorityQueue for similar testing
8 |
9 | """
10 | import sys
11 | from priority_queue import *
12 |
13 | # ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ###
14 |
15 | pq = PriorityQueue()
16 |
17 | pq.enqueue("John", 2);
18 | pq.enqueue("Jack", 1);
19 | pq.enqueue("Camila", 1);
20 | pq.enqueue("Esteban", 3)
21 |
22 | print()
23 | print("Is queue empty?", pq.isEmpty())
24 | print("What is the size of queue?", pq.size())
25 | hpi = pq.front()
26 | print("Highest priority item is {} at priority {}".format( hpi.element, hpi.priority) )
27 |
28 | print("adding Esperanaza 4...")
29 | pq.enqueue("Esperanaza", 4)
30 | print("\n.....current priority queue.....")
31 | print(pq)
32 |
33 | print()
34 | print("adding Hayleigh 0.05...")
35 | pq.enqueue("Hayleigh", 0.05)
36 |
37 | print()
38 | print("\n.....current priority queue.....")
39 | print(pq)
40 |
41 | print()
42 | print("adding Rachele 9...")
43 | pq.enqueue("Rachele", 9)
44 | print("What is the size of queue?", pq.size())
45 | print("\n.....current priority queue.....")
46 | print(pq)
47 |
48 | print()
49 | print("adding Robin 7...")
50 | pq.enqueue("Robin", 7)
51 | print("\n.....current priority queue.....")
52 | print(pq)
53 |
54 | print()
55 | print("Removing lowest priority item...")
56 | lowestRemoved = pq.removeLowestPriorityItem()
57 | print(lowestRemoved.element, lowestRemoved.priority)
58 | print("\n.....current priority queue.....")
59 | print(pq)
60 |
61 |
--------------------------------------------------------------------------------
/practice/queues/priority_queue_test02.py:
--------------------------------------------------------------------------------
1 | """
2 | priority_queue_test02.py
3 |
4 | some tests for priority_queue.py
5 |
6 | TESTING 1, 2, 3
7 | see https://repl.it/@ssi112/PriorityQueue for similar testing
8 |
9 | """
10 | import sys
11 | from priority_queue import *
12 |
13 |
14 | # ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ###
15 |
16 | def frequency_count(sentence):
17 | """
18 | counts the occurrence of each letter in sentence
19 | calculates a relative frequency for each letter
20 | convert it to a sorted list of tuples [(frequency, letter)]
21 | """
22 | char_count = {}
23 | # create the counts for each letter
24 | for char in sentence:
25 | if char in char_count:
26 | char_count[char] += 1
27 | else:
28 | char_count[char] = 1
29 | # sum of all values
30 | sum_a_thon = sum(char_count.values())
31 | # convert counts to a relative frequency
32 | list_of_tuples = []
33 | for key in char_count:
34 | char_count[key] = round(char_count[key] / sum_a_thon * 100, 3)
35 | # convert to a list of tuples
36 | list_of_tuples = [(key, value) for key, value in char_count.items()]
37 | # sort the list by the letter x[1] then return it
38 | return list_of_tuples
39 |
40 |
41 | def create_priority_queue(frequencies):
42 | priority_queue = PriorityQueue()
43 | # create a leaf node for each letter
44 | for inx, (letter, frequency) in enumerate(frequencies):
45 | # add it to the queue
46 | priority_queue.enqueue(letter, frequency)
47 | """ TEST THIS LATER
48 | while priority_queue.qsize() > 1:
49 | # get the two highest nodes
50 | left, right = priority_queue.get(), priority_queue.get()
51 | print("left - left[1]", left, left[1])
52 | print("right - right[1]", right, right[1])
53 | # add the two nodes to the tree
54 | node = huffman_node(left, right)
55 | # add the new node to the queue
56 | priority_queue.put(((left[1] + right[1]), node))
57 | """
58 | return priority_queue
59 |
60 |
61 | sentence = "The bird is the word"
62 |
63 | print("~"*sys.getsizeof(sentence))
64 | print ("The size of the data is: {}\n".format(sys.getsizeof(sentence)))
65 | print ("The content of the data is: '{}'\n".format(sentence))
66 |
67 | frequencies = frequency_count(sentence)
68 |
69 | print("\nindex letter frequency")
70 | print("------------------------")
71 | for inx, (letter, frequency) in enumerate(frequencies):
72 | #print(" {0:02d} '{1}' {2:6.3f}".format(inx, frequency, letter))
73 | print(" {0:02d} '{1}' {2:6.3f}".format(inx, letter, frequency))
74 |
75 | pq = create_priority_queue(frequencies)
76 |
77 | print()
78 | print("Is queue empty?", pq.isEmpty())
79 | print("What is the size of queue?", pq.size())
80 | hpi = pq.front()
81 | print("Highest priority item is {} at priority {}".format( hpi.element, hpi.priority) )
82 |
83 | print()
84 | print("\n.....current priority queue.....")
85 | print(pq)
86 |
87 | print("\n...about to remove highest priority item...")
88 | hpi = pq.dequeue()
89 | print("Removed highest priority item {} at priority {}".format( hpi.element, hpi.priority))
90 |
91 | print()
92 | print("What is the size of queue?", pq.size())
93 | print("\n.....current priority queue.....")
94 | print(pq)
95 |
96 |
--------------------------------------------------------------------------------
/practice/queues/queue_array.py:
--------------------------------------------------------------------------------
1 | """
2 | queue_array.py
3 |
4 | Queue will need to have the following functionality:
5 |
6 | enqueue - adds data to the back of the queue
7 | dequeue - removes data from the front of the queue
8 | front - returns the element at the front of the queue
9 | front advances to the next position in the queue as items are removed
10 | size - returns the number of elements present in the queue
11 | is_empty - returns True if there are no elements in the queue, and False otherwise
12 | _handle_full_capacity - increases the capacity of the array, for cases in which the queue would otherwise overflow
13 |
14 | Also, if the queue is empty, dequeue and front operations should return None.
15 | """
16 |
17 | class Queue:
18 | def __init__(self, initial_size = 10):
19 | self.arr = [0 for _ in range(initial_size)]
20 | self.next_index = 0 # next free slot in array
21 | self.front_index = -1 # -1 indicates empty
22 | self.queue_size = 0
23 |
24 | def enqueue(self, value):
25 | # if queue is already full --> increase capacity
26 | if self.queue_size == len(self.arr):
27 | self._handle_queue_capacity_full()
28 |
29 | # enqueue new element
30 | self.arr[self.next_index] = value
31 | self.queue_size += 1
32 | # allows queue to wrap around and use a free slot
33 | # assumes items at front of queue were already dequeued
34 | self.next_index = (self.next_index + 1) % len(self.arr)
35 | if self.front_index == -1:
36 | self.front_index = 0
37 |
38 | def dequeue(self):
39 | # check if queue is empty
40 | if self.is_empty():
41 | self.front_index = -1 # resetting pointers
42 | self.next_index = 0
43 | return None
44 | # dequeue front element
45 | value = self.arr[self.front_index]
46 | # move front index to pt to the next available item
47 | # modulo allows for wrap around reuse of space in queue
48 | self.front_index = (self.front_index + 1) % len(self.arr)
49 | self.queue_size -= 1
50 | return value
51 |
52 | def size(self):
53 | # returns the current size of the queue
54 | return self.queue_size
55 |
56 | def is_empty(self):
57 | # returns True if the queue is empty and False otherwise
58 | return self.size() == 0
59 |
60 | def front(self):
61 | # returns the value for the front element
62 | # (whatever item is located at the front_index position).
63 | # otherwise returns None
64 | if self.is_empty():
65 | return None
66 | return self.arr[self.front_index]
67 |
68 | def _handle_queue_capacity_full(self):
69 | old_arr = self.arr
70 | self.arr = [0 for _ in range(2 * len(old_arr))]
71 |
72 | index = 0
73 |
74 | # copy all elements from front of queue (front-index) until end
75 | for i in range(self.front_index, len(old_arr)):
76 | self.arr[index] = old_arr[i]
77 | index += 1
78 |
79 | # case: when front-index is ahead of next index
80 | for i in range(0, self.front_index):
81 | self.arr[index] = old_arr[i]
82 | index += 1
83 |
84 | # reset pointers
85 | self.front_index = 0
86 | self.next_index = index
87 |
88 | # Setup
89 | q = Queue()
90 | q.enqueue(1)
91 | q.enqueue(2)
92 | q.enqueue(3)
93 |
94 | # Test size
95 | print ("Pass" if (q.size() == 3) else "Fail")
96 |
97 | # Test dequeue
98 | print ("Pass" if (q.dequeue() == 1) else "Fail")
99 |
100 | # Test enqueue
101 | q.enqueue(4)
102 | print ("Pass" if (q.dequeue() == 2) else "Fail")
103 | print ("Pass" if (q.dequeue() == 3) else "Fail")
104 | print ("Pass" if (q.dequeue() == 4) else "Fail")
105 | q.enqueue(5)
106 | print ("Pass" if (q.size() == 1) else "Fail")
107 |
108 |
109 |
--------------------------------------------------------------------------------
/practice/queues/queue_linked_list.py:
--------------------------------------------------------------------------------
1 | """
2 | queue_linked_list.py
3 |
4 | Time Complexity
5 | When we use enqueue, we simply create a new node and add it to the tail of the list.
6 | And when we dequeue an item, we simply get the value from the head of the list and then
7 | shift the head variable so that it refers to the next node over.
8 |
9 | Both of these operations happen in constant time—that is, they have a time-complexity of O(1).
10 | """
11 |
12 | class Node:
13 | def __init__(self, value):
14 | self.value = value
15 | self.next = None
16 |
17 | def __repr__(self):
18 | return repr(self.value)
19 |
20 | class Stack:
21 | def __init__(self):
22 | self.num_elements = 0
23 | self.head = None
24 |
25 | def push(self, value):
26 | new_node = Node(value)
27 | if self.head is None:
28 | self.head = new_node
29 | else:
30 | new_node.next = self.head
31 | self.head = new_node
32 | self.num_elements += 1
33 |
34 | def pop(self):
35 | if self.is_empty():
36 | return None
37 | temp = self.head.value
38 | self.head = self.head.next
39 | self.num_elements -= 1
40 | return temp
41 |
42 | def top(self):
43 | if self.head is None:
44 | return None
45 | return self.head.value
46 |
47 | def size(self):
48 | return self.num_elements
49 |
50 | def is_empty(self):
51 | return self.num_elements == 0
52 |
53 | class Queue:
54 | def __init__(self):
55 | """
56 | head attribute to keep track of the first node in the linked list
57 | tail attribute to keep track of the last node in the linked list
58 | num_elements attribute to keep track of how many items are in the stack
59 | """
60 | self.head = None
61 | self.tail = None
62 | self.num_elements = 0
63 |
64 | def enqueue(self, value):
65 | """
66 | Adds a new item to the back of the queue. Since we're using a linked list,
67 | this is equivalent to creating a new node and adding it to the tail (append).
68 |
69 | If the queue is empty, then both the head and tail should refer to the new node
70 | (because when there's only one node, this node is both the head and the tail)
71 | Otherwise (if the queue has items), add the new node to the tail
72 | Be sure to shift the tail reference so that it refers to the new node
73 | (because it is the new tail)
74 | """
75 | new_node = Node(value)
76 | if self.head is None:
77 | self.head = new_node
78 | self.tail = self.head
79 | else:
80 | # add value to the next attribute of the tail (append)
81 | self.tail.next = new_node
82 | # shift the tail (i.e., the back of the queue)
83 | self.tail = self.tail.next
84 | self.num_elements += 1
85 |
86 | def dequeue(self):
87 | """
88 | If the queue is empty, it should simply return None else
89 | Get the value from the front of the queue (head of the linked list)
90 | Shift the head over so that it refers to the next node
91 | Update the num_elements attribute
92 | Return the value that was dequeued
93 | """
94 | if self.is_empty():
95 | return None
96 | value = self.head.value # copy the value to a local variable
97 | self.head = self.head.next # shift the head (front of the queue)
98 | self.num_elements -= 1
99 | return value
100 |
101 | def size(self):
102 | # returns the current size of the stack
103 | return self.num_elements
104 |
105 | def is_empty(self):
106 | # returns True if the stack is empty and False otherwise
107 | return self.num_elements == 0
108 |
109 | def __repr__(self):
110 | return '<%s (%s:%s) %s>' % (self.__class__.__name__, self.head, self.tail, self.num_elements)
111 |
112 |
113 | def reverse_queue(queue):
114 | """
115 | Reverese the input queue
116 | Args:
117 | queue(queue),str2(string): Queue to be reversed
118 | Returns:
119 | queue: Reveresed queue
120 | """
121 |
122 | # You can use an auxiliary stack to reverse the elements of your Queue.
123 | # Basically, you will stack every element of your Queue until it becomes empty.
124 | # Then you pop each element of your stack and enqueue it until your stack is empty.
125 | aux_stack = Stack()
126 | while not queue.is_empty():
127 | aux_stack.push(queue.dequeue())
128 |
129 | while not aux_stack.is_empty():
130 | queue.enqueue(aux_stack.pop())
131 | return queue
132 |
133 | # Setup
134 | q = Queue()
135 | q.enqueue(1)
136 | q.enqueue(2)
137 | q.enqueue(3)
138 |
139 | print(q)
140 |
141 | r = reverse_queue(q)
142 | print(r)
143 |
144 | # Test size
145 | print ("Pass" if (q.size() == 3) else "Fail")
146 |
147 | # Test dequeue
148 | print ("Pass" if (q.dequeue() == 1) else "Fail")
149 |
150 | # Test enqueue
151 | q.enqueue(4)
152 | print ("Pass" if (q.dequeue() == 2) else "Fail")
153 | print ("Pass" if (q.dequeue() == 3) else "Fail")
154 | print ("Pass" if (q.dequeue() == 4) else "Fail")
155 | q.enqueue(5)
156 | print ("Pass" if (q.size() == 1) else "Fail")
157 |
158 | print(q)
159 |
--------------------------------------------------------------------------------
/practice/recursion/binary_search.py:
--------------------------------------------------------------------------------
1 | """
2 | binary_search.py
3 |
4 | Using recursion
5 |
6 | 𝑇(𝑛)=𝑙𝑜𝑔(𝑛)∗𝑘
7 |
8 | The time complexity of the function is a logarithmic function of the input, 𝑛
9 | Hence, the time complexity of the recursive algorithm for binary search is 𝑙𝑜𝑔(𝑛).
10 | """
11 | def binary_search(arr, target):
12 | return binary_search_func(arr, 0, len(arr) - 1, target)
13 |
14 | def binary_search_func(arr, start_index, end_index, target):
15 | if start_index > end_index:
16 | return -1
17 |
18 | mid_index = (start_index + end_index)//2
19 |
20 | if arr[mid_index] == target:
21 | return mid_index
22 | elif arr[mid_index] > target:
23 | return binary_search_func(arr, start_index, mid_index - 1, target)
24 | else:
25 | return binary_search_func(arr, mid_index + 1, end_index, target)
26 |
27 | arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 44, 71, 72, 73, 101, 999]
28 | print(binary_search(arr, 71))
29 |
30 |
--------------------------------------------------------------------------------
/practice/recursion/last_index.py:
--------------------------------------------------------------------------------
1 | """
2 | last_index.py
3 |
4 | Problem statement
5 |
6 | Given an array arr and a target element target, find the last index of occurence
7 | of target in arr using recursion. If target is not present in arr, return -1.
8 |
9 | For example:
10 |
11 | arr = [1, 2, 5, 5, 4] and target = 5, output = 3
12 | arr = [1, 2, 5, 5, 4] and target = 7, output = -1
13 | """
14 |
15 | def last_index(arr, target):
16 | """
17 | :param: arr - input array
18 | :param: target - integer element
19 | return: int - last index of target in arr
20 | TODO: complete this method to find the last index of target in arr
21 | """
22 | # we start looking from the last index
23 | return last_index_arr(arr, target, len(arr) - 1)
24 |
25 |
26 | def last_index_arr(arr, target, index):
27 | if index < 0:
28 | return -1
29 |
30 | # check if target is found - searching backwards
31 | if arr[index] == target:
32 | return index
33 |
34 | # else make a recursive call to the rest of the array
35 | return last_index_arr(arr, target, index - 1)
36 |
37 |
38 | def test_function(test_case):
39 | arr = test_case[0]
40 | target = test_case[1]
41 | solution = test_case[2]
42 | output = last_index(arr, target)
43 | if output == solution:
44 | print("Pass")
45 | else:
46 | print("False")
47 |
48 |
49 | arr = [1, 2, 5, 5, 4]
50 | target = 5
51 | solution = 3
52 |
53 | test_case = [arr, target, solution]
54 | test_function(test_case)
55 |
56 | arr = [1, 2, 5, 5, 4]
57 | target = 7
58 | solution = -1
59 |
60 | test_case = [arr, target, solution]
61 | test_function(test_case)
62 |
63 | arr = [91, 19, 3, 8, 9]
64 | target = 91
65 | solution = 0
66 |
67 | test_case = [arr, target, solution]
68 | test_function(test_case)
69 |
70 | arr = [1, 1, 1, 1, 1, 1]
71 | target = 1
72 | solution = 5
73 |
74 | test_case = [arr, target, solution]
75 | test_function(test_case)
76 |
--------------------------------------------------------------------------------
/practice/recursion/recursion1_simple.py:
--------------------------------------------------------------------------------
1 | """
2 | recursion1_simple.py
3 |
4 | Call Stack
5 | There are limitations of recursion on a call stack.
6 | Python limits the depth on recursion if you create a condition wherein a
7 | very large call stack is used.
8 | It will raise the error RecursionError: maximum recursion depth exceeded in comparison.
9 |
10 | Some compiliers may turn them into an iterative loop to prevent using up the stack.
11 | Python does not do this.
12 | """
13 |
14 | def power_of_2(n):
15 | """
16 | The base case. This is where you catch edge cases that don't fit the problem (2∗2𝑛−1).
17 | Since we aren't considering any 𝑛<0 valid, 2∗2𝑛−1 can't be used when 𝑛 is 0.
18 | This section of the code returns the solution to 2^0 without using 2∗2𝑛−1.
19 | """
20 | if n == 0:
21 | return 1
22 | """
23 | This code is where it breaks the problem down into smaller instances.
24 | Calling itself to calculate 2𝑛−1
25 | """
26 | return 2 * power_of_2(n - 1)
27 |
28 |
29 | def sum_integers(n):
30 | if n == 1:
31 | return 1
32 | return n + sum_integers(n - 1)
33 |
34 |
35 | # sometime iterative solutions are more readable and hence less prone to bugs
36 | def sum_array_iter(array):
37 | result = 0
38 | for x in array:
39 | result += x
40 | return result
41 |
42 |
43 |
44 |
45 | def factorial(n):
46 | """
47 | A factorial function is a mathematical function that multiplies a given number, 𝑛,
48 | and all of the whole numbers from 𝑛 down to 1
49 |
50 | 4! = 4∗3∗2∗1 = 24
51 |
52 | we can say that for any input 𝑛
53 |
54 | 𝑛! = 𝑛∗(𝑛−1)∗(𝑛−2)...1
55 |
56 | For Python: factorial(n) = n * factorial(n - 1)
57 |
58 | Calculate n!
59 | Args:
60 | n(int): factorial to be computed
61 | Returns:
62 | n!
63 | """
64 | if n == 0:
65 | return 1 # by definition of 0!
66 | return n * (factorial(n - 1))
67 |
68 |
69 | sumnum = 5
70 | print()
71 | print("-"*55)
72 | print("power_of_2({}) = {}".format(sumnum, power_of_2(sumnum)))
73 |
74 | sumnum = 3
75 | print()
76 | print("-"*55)
77 | print("sum_integers({}) = {}".format(sumnum, sum_integers(sumnum)))
78 |
79 | arr = [1, 2, 3, 4, 5, 6]
80 | print()
81 | print("-"*55)
82 | print("sum_array_iter({}) = {}\n".format(arr, sum_array_iter(arr)))
83 |
84 | sumnum = 4
85 | print()
86 | print("-"*55)
87 | print("factorial({}) = {}\n".format(sumnum, factorial(sumnum)))
88 |
--------------------------------------------------------------------------------
/practice/recursion/recursion2_reverse_string.py:
--------------------------------------------------------------------------------
1 | """
2 | recursion2_reverse_string.py
3 |
4 | Call Stack
5 | There are limitations of recursion on a call stack.
6 | Python limits the depth on recursion if you create a condition wherein a
7 | very large call stack is used.
8 | It will raise the error RecursionError: maximum recursion depth exceeded in comparison.
9 |
10 | Some compiliers may turn them into an iterative loop to prevent using up the stack.
11 | Python does not do this.
12 | """
13 |
14 |
15 | """
16 | Reversing a String
17 |
18 | Practice a problem that is frequently solved by recursion: Reversing a string.
19 |
20 | Note that Python has a built-in function that you could use for this,
21 | but the goal here is to avoid that and understand how it can be done using recursion instead.
22 | """
23 | def reverse_string(input):
24 | """
25 | Return reversed input string
26 |
27 | Examples:
28 | reverse_string("abc") returns "cba"
29 |
30 | Args:
31 | input(str): string to be reversed
32 |
33 | Returns:
34 | a string that is the reverse of input
35 | """
36 | if len(input) == 0:
37 | return ""
38 | else:
39 | first_char = input[0]
40 | the_rest = slice(1, None)
41 | sub_string = input[the_rest]
42 | reversed_substring = reverse_string(sub_string)
43 | return reversed_substring + first_char
44 |
45 |
46 | def reverse_string_print(input):
47 | """
48 | Return reversed input string
49 |
50 | Examples:
51 | reverse_string("abc") returns "cba"
52 |
53 | Args:
54 | input(str): string to be reversed
55 |
56 | Returns:
57 | a string that is the reverse of input
58 | """
59 | if len(input) == 0:
60 | return ""
61 | else:
62 | first_char = input[0]
63 | print("\nfirst_char", first_char)
64 |
65 | the_rest = slice(1, None)
66 | print("\nthe_rest", the_rest)
67 |
68 | sub_string = input[the_rest]
69 | print("\nsub_string", sub_string)
70 |
71 | reversed_substring = reverse_string(sub_string)
72 | print("\nreversed_substring", reversed_substring)
73 |
74 | return reversed_substring + first_char
75 |
76 | sumstr = ""
77 | print()
78 | print("-"*55)
79 | print("reverse_string({}) = [{}]\n".format(sumstr, reverse_string(sumstr)))
80 |
81 | sumstr = "Esteban DeJesus"
82 | print()
83 | print("-"*55)
84 | print("reverse_string({}) = [{}]\n".format(sumstr, reverse_string(sumstr)))
85 |
86 | sumstr = "cba"
87 | print("-"*55)
88 | print("reverse_string_print({}) = [{}]\n".format(sumstr, reverse_string_print(sumstr)))
89 |
--------------------------------------------------------------------------------
/practice/recursion/recursion3_palindrome.py:
--------------------------------------------------------------------------------
1 | """
2 | recursion3_palindrome.py
3 |
4 | A palindrome is a word that is the reverse of itself.
5 | That is it is the same word when read forwards and backwards.
6 | """
7 |
8 |
9 | def is_palindrome(input):
10 | """
11 | Return True if input is palindrome, False otherwise.
12 |
13 | Args:
14 | input(str): input to be checked if it is palindrome
15 | """
16 | if len(input) <= 1:
17 | return True
18 | else:
19 | first_char = input[0]
20 | last_char = input[-1]
21 |
22 | # sub_input is input with first and last char removed
23 | sub_input = input[1:-1]
24 | # print(sub_input)
25 | return (first_char == last_char) and is_palindrome(sub_input)
26 |
27 | print ("Pass" if (is_palindrome("")) else "Fail")
28 | print ("Pass" if (is_palindrome("a")) else "Fail")
29 | print ("Pass" if (is_palindrome("madam")) else "Fail")
30 | print ("Pass" if (is_palindrome("abba")) else "Fail")
31 | print ("Pass" if not (is_palindrome("Udacity")) else "Fail")
32 |
--------------------------------------------------------------------------------
/practice/recursion/recursion4_permutations.py:
--------------------------------------------------------------------------------
1 | """
2 | recursion4_permutations.py
3 |
4 | The number of permutations on a set of n elements is given by n!.
5 | For example, there are 2! = 2*1 = 2 permutations of {1, 2}, namely {1, 2} and {2, 1},
6 | and 3! = 3*2*1 = 6 permutations of {1, 2, 3},
7 | namely {1, 2, 3}, {1, 3, 2}, {3, 1, 2}, {3, 2, 1}, {2, 3, 1} and {2, 1, 3}
8 |
9 | *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
10 | in the last example, kept shifting last number to the left until it was
11 | at the beginning of the list then repeated
12 | *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
13 |
14 | https://www.geeksforgeeks.org/generate-all-the-permutation-of-a-list-in-python/
15 |
16 | In mathematics, permutation is the act of arranging the members of a set into a
17 | sequence or order, or, if the set is already ordered, rearranging (reordering)
18 | its elements—a process called permuting.
19 |
20 | https://www.andlearning.org/permutation-combination-formulas/
21 | https://en.wikipedia.org/wiki/Permutation
22 | """
23 |
24 | import copy
25 |
26 | def permutation(lst):
27 | """
28 | Return a list of permutations
29 |
30 | Examples:
31 | permute([0, 1]) returns [ [0, 1], [1, 0] ]
32 |
33 | Args:
34 | lst(list): list of items to be permuted
35 |
36 | Returns:
37 | list of permutation with each permuted item being represented by a list
38 |
39 | ----------------------------------------------------------------------------
40 | The idea is to one by one extract all elements, place them at first position
41 | and recur for remaining list.
42 | ----------------------------------------------------------------------------
43 | """
44 | # If lst is empty then there are no permutations
45 | if len(lst) == 0:
46 | return []
47 |
48 | # If there is only one element in lst then only one permuatation is possible
49 | if len(lst) == 1:
50 | return [lst]
51 |
52 | # Find the permutations for lst if there are
53 | # more than 1 characters
54 |
55 | l = [] # empty list that will store current permutation
56 |
57 | # Iterate the input(lst) and calculate the permutation
58 | for i in range(len(lst)):
59 | m = lst[i]
60 |
61 | # Extract lst[i] or m from the list. remLst is
62 | # remaining list
63 | remLst = lst[:i] + lst[i+1:]
64 |
65 | # Generating all permutations where m is first element
66 | for p in permutation(remLst):
67 | l.append([m] + p)
68 | return l
69 |
70 | # Helper Function
71 | def check_output(output, expected_output):
72 | """
73 | Return True if output and expected_output
74 | contains the same lists, False otherwise.
75 |
76 | Note that the ordering of the list is not important.
77 |
78 | Examples:
79 | check_output([ [0, 1], [1, 0] ] ], [ [1, 0], [0, 1] ]) returns True
80 |
81 | Args:
82 | output(list): list of list
83 | expected_output(list): list of list
84 |
85 | Returns:
86 | bool
87 | """
88 | o = copy.deepcopy(output) # so that we don't mutate input
89 | e = copy.deepcopy(expected_output) # so that we don't mutate input
90 |
91 | o.sort()
92 | e.sort()
93 | return o == e
94 |
95 | print ("Pass" if (check_output(permutation([]), [])) else "Fail")
96 | print ("Pass" if (check_output(permutation([0]), [[0]])) else "Fail")
97 | print ("Pass" if (check_output(permutation([0, 1]), [[0, 1], [1, 0]])) else "Fail")
98 | print ("Pass" if (check_output(permutation([0, 1, 2]), [[0, 1, 2], [0, 2, 1], [1, 0, 2], [1, 2, 0], [2, 0, 1], [2, 1, 0]])) else "Fail")
99 |
100 |
101 | # -----------------------------------------------------------------------------
102 | # UDACITY Solution
103 | def permute(l):
104 | """
105 | Return a list of permutations
106 |
107 | Examples:
108 | permute([0, 1]) returns [ [0, 1], [1, 0] ]
109 |
110 | Args:
111 | l(list): list of items to be permuted
112 |
113 | Returns:
114 | list of permutation with each permuted item be represented by a list
115 | """
116 | perm = []
117 | if len(l) == 0:
118 | perm.append([])
119 | else:
120 | first_element = l[0]
121 | after_first = slice(1, None)
122 | sub_permutes = permute(l[after_first])
123 | for p in sub_permutes:
124 | for j in range(0, len(p) + 1):
125 | r = copy.deepcopy(p)
126 | r.insert(j, first_element)
127 | perm.append(r)
128 | return perm
129 | # -----------------------------------------------------------------------------
130 |
131 | # Python program to print all permutations with
132 | # duplicates allowed
133 |
134 | def toString(List):
135 | return ''.join(List)
136 |
137 | # Function to print permutations of string
138 | # This function takes three parameters:
139 | # 1. String
140 | # 2. Starting index of the string
141 | # 3. Ending index of the string.
142 |
143 | def permute(a, l, r):
144 | if l==r:
145 | print(toString(a))
146 | else:
147 | for i in range(l,r+1):
148 | a[l], a[i] = a[i], a[l]
149 | permute(a, l+1, r)
150 | a[l], a[i] = a[i], a[l] # backtrack
151 |
152 | # Driver program to test the above function
153 | print("\nPermuting a string using backtrack")
154 | print("Reference: https://www.geeksforgeeks.org/write-a-c-program-to-print-all-permutations-of-a-given-string/")
155 | string = "ABC"
156 | n = len(string)
157 | a = list(string)
158 | permute(a, 0, n-1)
159 |
160 | # -----------------------------------------------------------------------------
161 | # UDACITY Solution
162 | # Solution
163 | def permutations(string):
164 | return return_permutations(string, 0)
165 |
166 | def return_permutations(string, index):
167 | # Base Case
168 | if index >= len(string):
169 | return [""]
170 |
171 | small_output = return_permutations(string, index + 1)
172 |
173 | output = list()
174 | current_char = string[index]
175 |
176 | # iterate over each permutation string received thus far
177 | # and place the current character at between different indices of the string
178 | for permutation in small_output:
179 | for index in range(len(small_output[0]) + 1):
180 | new_permutation = permutation[0: index] + current_char + permutation[index:]
181 | output.append(new_permutation)
182 | return output
183 | # -----------------------------------------------------------------------------
184 |
--------------------------------------------------------------------------------
/practice/recursion/tower_of_hanoi.py:
--------------------------------------------------------------------------------
1 | """
2 | tower_of_hanoi.py
3 |
4 | Problem Statement
5 |
6 | The Tower of Hanoi is a puzzle where we have three rods and n disks. The three rods are:
7 |
8 | 1. source
9 | 2. destination
10 | 3. auxiliary
11 |
12 | Initally, all the n disks are present on the source rod. The final objective of the puzzle is
13 | to move all disks from the source rod to the destination rod using the auxiliary rod.
14 | However, there are some rules according to which this has to be done:
15 |
16 | 1. Only one disk can be moved at a time.
17 | 2. A disk can be moved only if it is on the top of a rod.
18 | 3. No disk can be placed on the top of a smaller disk.
19 |
20 | You will be given the number of disks num_disks as the input parameter.
21 |
22 | For example, if you have num_disks = 3, then the disks should be moved as follows:
23 |
24 | 1. move disk from source to auxiliary
25 | 2. move disk from source to destination
26 | 3. move disk from auxiliary to destination
27 |
28 | You must print these steps as follows:
29 |
30 | S A
31 | S D
32 | A D
33 |
34 | Where S = source, D = destination, A = auxiliary
35 |
36 |
37 | Wikipedia has nice animation showing the solution:
38 | https://en.wikipedia.org/wiki/Tower_of_Hanoi
39 |
40 | Also, the friendly geeks describe an algorithm too:
41 | https://www.geeksforgeeks.org/c-program-for-tower-of-hanoi/
42 |
43 | """
44 |
45 | def tower_of_Hanoi(num_disks):
46 | """
47 | :param: num_disks - number of disks
48 | TODO: print the steps required to move all disks from source to destination
49 | """
50 | move(num_disks, 'S', 'D', 'A')
51 |
52 | def move(n , from_rod, to_rod, aux_rod):
53 | if n == 1:
54 | print("Move disk 1 from rod", from_rod, "to rod", to_rod)
55 | return
56 | move(n- 1, from_rod, aux_rod, to_rod)
57 | print("Move disk",n,"from rod", from_rod, "to rod", to_rod)
58 | move(n - 1, aux_rod, to_rod, from_rod)
59 |
60 | # Driver code
61 | n = 4
62 | tower_of_Hanoi(n)
63 |
--------------------------------------------------------------------------------
/practice/stacks/balanced_paranthesis.py:
--------------------------------------------------------------------------------
1 |
2 | """
3 | In this exercise you are going to apply what you learned about stacks with a real world problem.
4 | We will be using stacks to make sure the parentheses are balanced in mathematical expressions
5 | such as: ((3^2+8)∗(5/2))/(2+6).
6 |
7 | In real life you can see this extend to many things such as text editor plugins and
8 | interactive development environments for all sorts of bracket completion checks.
9 |
10 | Take a string as an input and return True if it's parentheses are balanced or False if it is not.
11 | """
12 |
13 | # Solution
14 |
15 | # Our Stack Class
16 | class Stack:
17 | def __init__(self):
18 | self.items = []
19 |
20 | def size(self):
21 | return len(self.items)
22 |
23 | def push(self, item):
24 | self.items.append(item)
25 |
26 | def pop(self):
27 | if self.size()==0:
28 | return None
29 | else:
30 | return self.items.pop()
31 |
32 |
33 | def equation_checker(equation):
34 | """
35 | Check equation for balanced parentheses
36 |
37 | Args:
38 | equation(string): String form of equation
39 | Returns:
40 | bool: Return if parentheses are balanced or not
41 | """
42 |
43 | stack = Stack()
44 |
45 | for char in equation:
46 | # push all '(' on the stack
47 | if char == "(":
48 | stack.push(char)
49 | # pop the '(' off the stack for corresponding closing parenthesis
50 | elif char == ")":
51 | if stack.pop() == None:
52 | return False # if nothing to pop then it's unbalanced
53 |
54 | # if we popped all the '(' corresponding to all the ')' then we're good
55 | if stack.size() == 0:
56 | return True
57 | else:
58 | return False
59 |
60 |
61 | print ("Pass" if (equation_checker('((3^2 + 8)*(5/2))/(2+6)')) else "Fail")
62 | print ("Pass" if not (equation_checker('((3^2 + 8)*(5/2))/(2+6))')) else "Fail")
63 |
--------------------------------------------------------------------------------
/practice/stacks/reversed_polish_notation.py:
--------------------------------------------------------------------------------
1 |
2 | """
3 | Reverse Polish Notation
4 | Also referred to as Polish postfix notation is a way of laying out operators and operands.
5 |
6 | When making mathematical expressions, we typically put arithmetic operators
7 | (like +, -, *, and /) between operands. For example: 5 + 7 - 3 * 8
8 |
9 | However, in Reverse Polish Notation, the operators come after the operands.
10 | For example: 3 1 + 4 *
11 |
12 | The above expression would be evaluated as (3 + 1) * 4 = 16
13 |
14 | The goal of this exercise is to create a function that does the following:
15 |
16 | Given a postfix expression as input, evaluate and return the correct final answer.
17 |
18 | Note: In Python 3, the division operator / is used to perform float division.
19 | So for this problem, you should use int() after every division to convert the
20 | answer to an integer.
21 | """
22 |
23 | class LinkedListNode:
24 | def __init__(self, data):
25 | self.data = data
26 | self.next = None
27 |
28 | class Stack:
29 | def __init__(self):
30 | self.num_elements = 0
31 | self.head = None
32 |
33 | def push(self, data):
34 | new_node = LinkedListNode(data)
35 | if self.head is None:
36 | self.head = new_node
37 | else:
38 | new_node.next = self.head
39 | self.head = new_node
40 | self.num_elements += 1
41 |
42 | def pop(self):
43 | if self.is_empty():
44 | return None
45 | temp = self.head.data
46 | self.head = self.head.next
47 | self.num_elements -= 1
48 | return temp
49 |
50 | def top(self):
51 | if self.head is None:
52 | return None
53 | return self.head.data
54 |
55 | def size(self):
56 | return self.num_elements
57 |
58 | def is_empty(self):
59 | return self.num_elements == 0
60 |
61 | def evaluate_post_fix(input_list):
62 | """
63 | Evaluate the postfix expression to find the answer
64 |
65 | Args:
66 | input_list(list): List containing the postfix expression
67 | Returns:
68 | int: Postfix expression solution
69 |
70 | Process expression from left to right:
71 | Reference: https://en.wikipedia.org/wiki/Reverse_Polish_notation
72 |
73 | for each token in the postfix expression:
74 | if token is an operator:
75 | operand_2 ← pop from the stack
76 | operand_1 ← pop from the stack
77 | result ← evaluate token with operand_1 and operand_2
78 | push result back onto the stack
79 | else if token is an operand:
80 | push token onto the stack
81 | result ← pop from the stack
82 |
83 | Additional info: infix to RPN
84 | https://en.wikipedia.org/wiki/Shunting-yard_algorithm
85 | """
86 |
87 | operators = ['+', '-', '/', '*']
88 | stack = Stack()
89 | operand1 = 0
90 | operand2 = 0
91 | result = 0
92 |
93 | for token in input_list:
94 | if token in operators:
95 | operand2 = int( stack.pop() )
96 | operand1 = int( stack.pop() )
97 | if token == '+':
98 | result = operand1 + operand2
99 | elif token == '-':
100 | result = operand1 - operand2
101 | elif token == '/':
102 | result = int(operand1 / operand2)
103 | elif token == '*':
104 | result = operand1 * operand2
105 | stack.push(result)
106 | else:
107 | stack.push(token)
108 | return int(stack.pop())
109 |
110 |
111 | def test_function(test_case):
112 | output = evaluate_post_fix(test_case[0])
113 | print("test_case {} = {}".format(test_case, output))
114 | if output == test_case[1]:
115 | print("Pass")
116 | else:
117 | print("Fail")
118 |
119 | test_case_1 = [["3", "1", "+", "4", "*"], 16]
120 | test_function(test_case_1)
121 |
122 | test_case_2 = [["4", "13", "5", "/", "+"], 6]
123 | test_function(test_case_2)
124 |
125 | test_case_3 = [["10", "6", "9", "3", "+", "-11", "*", "/", "*", "17", "+", "5", "+"], 22]
126 | test_function(test_case_3)
127 |
128 |
--------------------------------------------------------------------------------
/practice/stacks/stack1_simple.py:
--------------------------------------------------------------------------------
1 | """
2 | Stack class has the following behaviors:
3 |
4 | push - adds an item to the top of the stack
5 | pop - removes an item from the top of the stack (and returns the value of that item)
6 | size - returns the size of the stack
7 | top - returns the value of the item at the top of stack (without removing that item)
8 | is_empty - returns True if the stack is empty and False otherwise
9 |
10 | """
11 |
12 | class Stack:
13 | def __init__(self, initial_size = 10):
14 | self.arr = [0 for _ in range(initial_size)]
15 | self.next_index = 0
16 | self.num_elements = 0
17 |
18 | def push(self, data):
19 | """
20 | The method will need to have a parameter for the value that you want to push
21 | Remember that next_index will have the index for where the value should be added
22 | Once you've added the value, you'll want to increment both next_index and num_elements
23 |
24 | Provide a conditional check to see if array is full and increase it's size
25 | """
26 | if self.next_index == len(self.arr):
27 | print("Yikes, out os stack space! Increasing capacity...")
28 | self._handle_stack_capacity_full()
29 |
30 | self.arr[self.next_index] = data
31 | self.next_index += 1
32 | self.num_elements += 1
33 |
34 | def _handle_stack_capacity_full(self):
35 | """
36 | copy contents of stack array to temp array
37 | double the size of stack array
38 | copy contents back into stack array
39 | *** not efficient (time complexity) ***
40 | """
41 | old_arr = self.arr
42 | self.arr = [0 for _ in range( 2 * len(old_arr))]
43 | for index, element in enumerate(old_arr):
44 | self.arr[index] = element
45 |
46 | def size(self):
47 | return self.num_elements
48 |
49 | def is_empty(self):
50 | # [on_true] if [expression] else [on_false]
51 | return (True if self.num_elements == 0 else False)
52 |
53 | def pop(self):
54 | """
55 | Check if the stack is empty and, if it is, return None
56 | Decrement next_index and num_elements
57 | Return the item that is being "popped"
58 | *** note the item is replaced with a 0 in this case ***
59 | """
60 | if self.is_empty():
61 | self.next_index = 0
62 | return None
63 | self.num_elements -= 1
64 | self.next_index -= 1
65 | popped = self.arr[self.next_index]
66 | self.arr[self.next_index] = 0
67 | return popped
68 |
69 | foo = Stack()
70 | print(foo.arr)
71 | foo.push("test")
72 | foo.push(101)
73 | foo.push("fuken-a")
74 | foo.push("text")
75 | foo.push(99)
76 | foo.push("fuken-puken")
77 | foo.push("temp")
78 | foo.push(42)
79 | foo.push("fuken-gruven")
80 | foo.push("trax")
81 | foo.push(9)
82 | foo.push("fuken-postal")
83 | print(foo.arr)
84 | print(foo.num_elements)
85 | print("~"*25)
86 | print(foo.pop())
87 | print(foo.arr)
88 | print(foo.num_elements)
89 |
--------------------------------------------------------------------------------
/practice/stacks/stack2_linked_list.py:
--------------------------------------------------------------------------------
1 | """
2 | Implement a stack using a linked list. The top of the stack is the beginning
3 | of the list, so items are added or removed from the head of the list.
4 |
5 | If we pop or push an element with this stack, there's no traversal.
6 | We simply add or remove the item from the head of the linked list,
7 | and update the head reference. So with our linked list implementaion,
8 | pop and push have a time complexity of O(1).
9 | """
10 |
11 | class Node(object):
12 | def __init__(self, data, next = None):
13 | self.data = data
14 | self.next = None
15 |
16 | class Stack(object):
17 | def __init__(self, head = None, num_elements = 0):
18 | self.head = None
19 | self.num_elements = 0
20 |
21 | def push(self, data):
22 | """
23 | The method will need to have a parameter for the value that you want to push
24 | You'll then need to create a new Node object with this value and link it to the list
25 | Remember that we want to add new items at the head of the stack, not the tail!
26 | Once you've added the new node, you'll want to increment num_elements
27 | """
28 | new_node = Node(data)
29 | # stack is empty
30 | if self.head is None:
31 | self.head = new_node
32 | # add new node to head/top of stack
33 | else:
34 | # existing item at top of stack becomes new node's next
35 | new_node.next = self.head
36 | # now pt head to top of stack
37 | self.head = new_node
38 |
39 | self.num_elements += 1
40 |
41 | def size(self):
42 | return self.num_elements
43 |
44 | def is_empty(self):
45 | return (True if self.num_elements == 0 else False)
46 |
47 | def pop(self):
48 | """
49 | Check if the stack is empty and, if it is, return None
50 | Get the value from the head node and put it in a local variable
51 | Move the head so that it refers to the next item in the list
52 | Return the value we got earlier
53 | """
54 | if self.is_empty():
55 | return None
56 | popped = self.head.data
57 | # move head pointer to next node (this removes item from stack)
58 | self.head = self.head.next
59 | self.num_elements -= 1
60 | return popped
61 |
62 | # Setup
63 | stack = Stack()
64 | stack.push(10)
65 | stack.push(20)
66 | stack.push(30)
67 | stack.push(40)
68 | stack.push(50)
69 |
70 | # Test size
71 | print ("Pass" if (stack.size() == 5) else "Fail")
72 |
73 | # Test pop
74 | print ("Pass" if (stack.pop() == 50) else "Fail")
75 |
76 | # Test push
77 | stack.push(60)
78 | print ("Pass" if (stack.pop() == 60) else "Fail")
79 | print ("Pass" if (stack.pop() == 40) else "Fail")
80 | print ("Pass" if (stack.pop() == 30) else "Fail")
81 | stack.push(50)
82 | print ("Pass" if (stack.size() == 3) else "Fail")
83 |
--------------------------------------------------------------------------------
/practice/stacks/stack3_linked_list.py:
--------------------------------------------------------------------------------
1 | """
2 | Implement a stack using a linked list. The bottom of the stack is the beginning
3 | of the list, so items are added or removed from the TAIL of the list.
4 | """
5 | class Node(object):
6 | def __init__(self, value, previous = None):
7 | self.value = value
8 | self.previous = None
9 |
10 | class Stack:
11 | def __init__(self):
12 | # TODO: Initialize the Stack
13 | self.head = None
14 | self.tail = None
15 | self.size = 0
16 |
17 | def size(self):
18 | # TODO: Check the size of the Stack
19 | return self.size
20 |
21 | def push(self, value):
22 | # TODO: Push item onto Stack
23 | if self.head is None: # empty list
24 | newNode = Node(value)
25 | self.head = newNode
26 | self.tail = self.head
27 | self.size += 1
28 | return True
29 | # head does not change since we add at end of list
30 | self.tail.next = Node(value) # tail next points to new node
31 | self.tail.next.previous = self.tail # point new node prev back to tail
32 | self.tail = self.tail.next # point the tail to the end of list again
33 | self.size += 1
34 | return True
35 |
36 | def pop(self):
37 | # TODO: Pop item off of the Stack
38 | """
39 | Check if the stack is empty and, if it is, return None
40 | Get the value from the tail node and put it in a local variable
41 | Move the tail so that it refers to the previous item in the list
42 | Return the value we got earlier
43 | """
44 | if self.size == 0:
45 | return None
46 | popped = self.tail.value
47 | # move tail pointer to previous node (this removes item from stack)
48 | self.tail = self.tail.previous
49 | self.size -= 1
50 | return popped
51 |
52 | MyStack = Stack()
53 |
54 | MyStack.push("Web Page 1")
55 | MyStack.push("Web Page 2")
56 | MyStack.push("Web Page 3")
57 |
58 | print (MyStack.head.value)
59 | print (MyStack.tail.previous.value)
60 | print (MyStack.tail.value)
61 |
62 | print("Popped!", MyStack.pop())
63 | print("Popped!", MyStack.pop())
64 |
65 | print("current size =", MyStack.size)
66 |
67 |
68 | print("Popped!", MyStack.pop())
69 |
70 | print("current size =", MyStack.size)
71 |
72 |
--------------------------------------------------------------------------------
/practice/stacks/stack_reverse.py:
--------------------------------------------------------------------------------
1 |
2 | """
3 | Reverse a Stack (in-place reversal)
4 | If your stack initially has 1, 2, 3, 4 (4 at the top and 1 at the bottom),
5 | after reversing the order must be 4, 3, 2, 1 (4 at the bottom and 1 at the top)
6 | """
7 |
8 | class LinkedListNode:
9 |
10 | def __init__(self, data):
11 | self.data = data
12 | self.next = None
13 |
14 | class Stack:
15 | def __init__(self):
16 | self.num_elements = 0
17 | self.head = None
18 |
19 | def push(self, data):
20 | new_node = LinkedListNode(data)
21 | if self.head is None:
22 | self.head = new_node
23 | else:
24 | new_node.next = self.head
25 | self.head = new_node
26 | self.num_elements += 1
27 |
28 | def pop(self):
29 | if self.is_empty():
30 | return None
31 | temp = self.head.data
32 | self.head = self.head.next
33 | self.num_elements -= 1
34 | return temp
35 |
36 | def top(self):
37 | if self.head is None:
38 | return None
39 | return self.head.data
40 |
41 | def size(self):
42 | return self.num_elements
43 |
44 | def is_empty(self):
45 | return self.num_elements == 0
46 |
47 | def printNodes(self):
48 | current = self.head
49 | while current:
50 | print(current.data)
51 | current = current.next
52 |
53 |
54 | def reverse_stack(stack):
55 | """
56 | Reverse a given input stack
57 |
58 | Args:
59 | stack(stack): Input stack to be reversed
60 | Returns:
61 | stack: Reversed Stack
62 | """
63 | holder_stack = Stack()
64 | while not stack.is_empty():
65 | popped_element = stack.pop()
66 | holder_stack.push(popped_element)
67 | _reverse_stack_recursion(stack, holder_stack)
68 |
69 |
70 | def _reverse_stack_recursion(stack, holder_stack):
71 | if holder_stack.is_empty():
72 | return
73 | popped_element = holder_stack.pop()
74 | _reverse_stack_recursion(stack, holder_stack)
75 | stack.push(popped_element)
76 | return
77 |
78 |
79 | def test_function(test_case):
80 | stack = Stack()
81 | for num in test_case:
82 | stack.push(num)
83 |
84 | reverse_stack(stack)
85 | index = 0
86 | while not stack.is_empty():
87 | popped = stack.pop()
88 | if popped != test_case[index]:
89 | print("Fail")
90 | return
91 | else:
92 | index += 1
93 | print("Pass")
94 |
95 | test_case_1 = [1, 2, 3, 4]
96 | test_function(test_case_1)
97 |
98 | test_case_2 = [1]
99 | test_function(test_case_2)
100 |
101 | test_case_3 = [99, 17, 33, 1, 11, 0, -11, 209, 99]
102 | test_function(test_case_3)
103 |
104 | stacked = Stack()
105 | for num in test_case_3:
106 | stacked.push(num)
107 |
108 | print("\nsize of stacked =", stacked.size())
109 | print("stacked in original order...")
110 | print(stacked.printNodes())
111 |
112 | reverse_stack(stacked)
113 |
114 | print("\nsize of stacked =", stacked.size())
115 | print("stacked in reverse order...")
116 | print(stacked.printNodes())
117 |
118 |
--------------------------------------------------------------------------------
/practice/trees/binary_tree.py.txt:
--------------------------------------------------------------------------------
1 | node0:
2 | value: None
3 | left: None
4 | right: None
5 |
6 | has left child? False
7 | has right child? False
8 | adding left and right children
9 |
10 | node 0: apple
11 | node 0 left child: banana
12 | node 0 right child: orange
13 |
14 | has left child? True
15 | has right child? True
16 |
17 | ===========================
18 | visit_order ['apple']
19 | stack:
20 |
21 | _________________
22 | Node(apple)
23 | _________________
24 |
25 |
26 | Node(apple) has left child? True
27 | visit Node(banana)
28 |
29 | ===========================
30 | visit_order ['apple', 'banana']
31 | stack:
32 |
33 | _________________
34 | Node(banana)
35 | _________________
36 | Node(apple)
37 | _________________
38 |
39 |
40 | Node(banana) has left child? True
41 | visit Node(dates)
42 |
43 | visit_order ['apple', 'banana', 'dates']
44 | stack:
45 |
46 | _________________
47 | Node(dates)
48 | _________________
49 | Node(banana)
50 | _________________
51 | Node(apple)
52 | _________________
53 |
54 |
55 | Node(dates) has left child? False
56 | Node(dates) has right child? False
57 | Node(dates)
58 | Node(banana)
59 | Node(banana) has right child? False
60 | pop Node(banana) off stack
61 |
62 | stack
63 |
64 | _________________
65 | Node(apple)
66 | _________________
67 |
68 |
69 | Node(apple)
70 | Node(apple) has right child? True
71 | visit Node(cherry)
72 |
73 | visit_order ['apple', 'banana', 'dates', 'cherry']
74 | stack
75 |
76 | _________________
77 | Node(cherry)
78 | _________________
79 | Node(apple)
80 | _________________
81 |
82 |
83 | Node(cherry) has left child? False
84 | Node(cherry) has right child? False
85 | pop Node(cherry) off the stack
86 |
87 | visit_order ['apple', 'banana', 'dates', 'cherry']
88 | stack
89 |
90 | _________________
91 | Node(apple)
92 | _________________
93 |
94 |
95 | pop Node(apple) off stack
96 | pre-order traversal visited nodes in this order: ['apple', 'banana', 'dates', 'cherry']
97 | stack
98 |
99 |
--------------------------------------------------------------------------------
/practice/trees/binary_tree_diameter.py:
--------------------------------------------------------------------------------
1 | """
2 | binary_tree_diameter.py
3 |
4 | Note: Diameter of a Binary Tree is the maximum distance between any two nodes
5 |
6 | The diameter of a tree (sometimes called the width) is the number of nodes
7 | on the longest path between two end nodes.
8 |
9 | https://www.geeksforgeeks.org/diameter-of-a-binary-tree/
10 | """
11 |
12 |
13 | # basic binary tree node
14 | class Node:
15 | # Constructor to create a new node
16 | def __init__(self, data):
17 | self.data = data
18 | self.left = None
19 | self.right = None
20 |
21 | """
22 | The function Compute the "height" of a tree. Height is the
23 | number f nodes along the longest path from the root node
24 | down to the farthest leaf node.
25 | """
26 | def height(node):
27 |
28 | # Base Case : Tree is empty
29 | if node is None:
30 | return 0 ;
31 |
32 | # If tree is not empty then height = 1 + max of left
33 | # height and right heights
34 | return 1 + max(height(node.left) ,height(node.right))
35 |
36 | # Function to get the diamtere of a binary tree
37 | def diameter(root):
38 | # Base Case when tree is empty
39 | if root is None:
40 | return 0;
41 |
42 | # Get the height of left and right sub-trees
43 | lheight = height(root.left)
44 | rheight = height(root.right)
45 |
46 | # Get the diameter of left and irgh sub-trees
47 | ldiameter = diameter(root.left)
48 | rdiameter = diameter(root.right)
49 |
50 | # Return max of the following tree:
51 | # 1) Diameter of left subtree
52 | # 2) Diameter of right subtree
53 | # 3) Height of left subtree + height of right subtree +1
54 | return max(lheight + rheight + 1, max(ldiameter, rdiameter))
55 |
56 |
57 | # Driver program to test above functions
58 |
59 | """
60 | Constructed binary tree is
61 | 1
62 | / \
63 | 2 3
64 | / \
65 | 4 5
66 | """
67 |
68 | root = Node(1)
69 | root.left = Node(2)
70 | root.right = Node(3)
71 | root.left.left = Node(4)
72 | root.left.right = Node(5)
73 | print("Diameter of given binary tree is %d" %(diameter(root)) )
74 |
75 | # This code is contributed by Nikhil Kumar Singh(nickzuck_007)
76 |
--------------------------------------------------------------------------------
/practice/trees/binary_tree_root_to_node_path.py:
--------------------------------------------------------------------------------
1 | """
2 | binary_tree_root_to_node_path.py
3 |
4 | Given the root of a Binary Tree and a data value representing a node,
5 | return the path from the root to that node in the form of a list.
6 | You can assume that the binary tree has nodes with unique values.
7 |
8 | """
9 |
10 | from queue import Queue
11 |
12 | # basic binary tree node
13 | class BinaryTreeNode:
14 |
15 | def __init__(self, data):
16 | self.data = data
17 | self.left = None
18 | self.right = None
19 |
20 |
21 | def path_from_root_to_node(root, data):
22 | """
23 | :param: root - root of binary tree
24 | :param: data - value (representing a node)
25 |
26 | return a list containing values of each node in the path from the
27 | root to the data node
28 | """
29 | output = path_from_node_to_root(root, data)
30 | return list(reversed(output))
31 |
32 | def path_from_node_to_root(root, data):
33 | if root is None:
34 | return None
35 |
36 | elif root.data == data:
37 | return [data]
38 |
39 | left_answer = path_from_node_to_root(root.left, data)
40 | if left_answer is not None:
41 | left_answer.append(root.data)
42 | return left_answer
43 |
44 | right_answer = path_from_node_to_root(root.right, data)
45 | if right_answer is not None:
46 | right_answer.append(root.data)
47 | return right_answer
48 | return None
49 |
50 |
51 | def convert_arr_to_binary_tree(arr):
52 | """
53 | Takes arr representing level-order traversal of Binary Tree
54 | """
55 | index = 0
56 | length = len(arr)
57 |
58 | if length <= 0 or arr[0] == -1:
59 | return None
60 |
61 | root = BinaryTreeNode(arr[index])
62 | index += 1
63 | queue = Queue()
64 | queue.put(root)
65 |
66 | while not queue.empty():
67 | current_node = queue.get()
68 | left_child = arr[index]
69 | index += 1
70 |
71 | if left_child is not None:
72 | left_node = BinaryTreeNode(left_child)
73 | current_node.left = left_node
74 | queue.put(left_node)
75 |
76 | right_child = arr[index]
77 | index += 1
78 |
79 | if right_child is not None:
80 | right_node = BinaryTreeNode(right_child)
81 | current_node.right = right_node
82 | queue.put(right_node)
83 | return root
84 |
85 |
86 | def test_function(test_case):
87 | arr = test_case[0]
88 | data = test_case[1]
89 | solution = test_case[2]
90 | root = convert_arr_to_binary_tree(arr)
91 | output = path_from_root_to_node(root, data)
92 | if output == solution:
93 | print("Pass")
94 | print("Solution:", solution)
95 | print(" Output:", output)
96 | else:
97 | print("Fail")
98 | print("Solution:", solution)
99 | print(" Output:", output)
100 |
101 |
102 | arr = [1, 2, 3, None, None, 4, 5, 6, None, 7, 8, 9, 10, None, None, None, None, None, None, 11, None, None, None]
103 | data = 11
104 | solution = [1, 3, 4, 6, 10, 11]
105 |
106 | test_case = [arr, data, solution]
107 | test_function(test_case)
108 |
--------------------------------------------------------------------------------
/practice/trees/binary_tree_state.py.txt:
--------------------------------------------------------------------------------
1 |
2 | loop count: 0
3 | current node: Node(apple)
4 | stack:
5 |
6 | _________________
7 | Node(apple)
8 | visited_left: False
9 | visited_right: False
10 |
11 | _________________
12 |
13 |
14 |
15 | loop count: 1
16 | current node: Node(banana)
17 | stack:
18 |
19 | _________________
20 | Node(banana)
21 | visited_left: False
22 | visited_right: False
23 |
24 | _________________
25 | Node(apple)
26 | visited_left: True
27 | visited_right: False
28 |
29 | _________________
30 |
31 |
32 |
33 | loop count: 2
34 | current node: Node(dates)
35 | stack:
36 |
37 | _________________
38 | Node(dates)
39 | visited_left: False
40 | visited_right: False
41 |
42 | _________________
43 | Node(banana)
44 | visited_left: True
45 | visited_right: False
46 |
47 | _________________
48 | Node(apple)
49 | visited_left: True
50 | visited_right: False
51 |
52 | _________________
53 |
54 |
55 |
56 | loop count: 3
57 | current node: Node(banana)
58 | stack:
59 |
60 | _________________
61 | Node(banana)
62 | visited_left: True
63 | visited_right: False
64 |
65 | _________________
66 | Node(apple)
67 | visited_left: True
68 | visited_right: False
69 |
70 | _________________
71 |
72 |
73 |
74 | loop count: 4
75 | current node: Node(apple)
76 | stack:
77 |
78 | _________________
79 | Node(apple)
80 | visited_left: True
81 | visited_right: False
82 |
83 | _________________
84 |
85 |
86 |
87 | loop count: 5
88 | current node: Node(cherry)
89 | stack:
90 |
91 | _________________
92 | Node(apple)
93 | visited_left: True
94 | visited_right: True
95 |
96 | _________________
97 |
98 |
99 |
100 | loop count: 6
101 | current node: None
102 | stack:
103 |
104 |
105 |
106 | *** pre_order_recursion ***
107 | ['apple', 'banana', 'dates', 'cherry']
108 |
109 | *** in_order_recursion ***
110 | ['dates', 'banana', 'apple', 'cherry']
111 |
112 | *** post_order_recursion ***
113 | ['dates', 'banana', 'cherry', 'apple']
114 |
--------------------------------------------------------------------------------
/practice/trees/bst-delete-case3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ssi112/data-structures-algorithms/e9bdfa4f41b5dd68b3227151084a8c0115bac8b8/practice/trees/bst-delete-case3.png
--------------------------------------------------------------------------------
/practice/trees/bst_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ssi112/data-structures-algorithms/e9bdfa4f41b5dd68b3227151084a8c0115bac8b8/practice/trees/bst_01.png
--------------------------------------------------------------------------------
/practice/trees/tree-simple-example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ssi112/data-structures-algorithms/e9bdfa4f41b5dd68b3227151084a8c0115bac8b8/practice/trees/tree-simple-example.png
--------------------------------------------------------------------------------
/project1/P0.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ssi112/data-structures-algorithms/e9bdfa4f41b5dd68b3227151084a8c0115bac8b8/project1/P0.zip
--------------------------------------------------------------------------------
/project1/Task0.py:
--------------------------------------------------------------------------------
1 | """
2 | Read file into texts and calls.
3 | It's ok if you don't understand how to read files.
4 | """
5 | import csv
6 | texts = list()
7 | calls = list()
8 |
9 | def readTexts():
10 | with open('texts.csv', 'r') as f:
11 | reader = csv.reader(f, delimiter='\n')
12 | for line in reader:
13 | texts.append(line)
14 |
15 |
16 | def readCalls():
17 | with open('calls.csv', 'r') as f:
18 | reader = csv.reader(f, delimiter='\n')
19 | for line in reader:
20 | calls.append(line)
21 |
22 |
23 | def main():
24 | """
25 | TASK 0:
26 | What is the first record of texts and what is the last record of calls?
27 | Print messages:
28 | "First record of texts, texts at time