├── .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