MIT 6.0001 Comp Sci and Programming Python Full Course Notes
MIT 6.0001 Comp Sci and Programming Python Full Course Notes
1. Introduction:
2. Algorithms:
3. History of Computers:
● Alan Turing introduced six primitives: Move left, move right, read/write, scan, and
do nothing.
● Expressions combine primitives in a programming language.
● In English, primitive constructs are words. In Python, they are data types like
integers, floats, booleans, etc.
7. Python Programming:
11. Conclusion:
● The lecture concludes with a reminder that computers only execute the
instructions given to them.
● The next lecture will delve into control flow in programming.
● If Statement:
● Checks a condition. If true, executes a block of code.
● Syntax:
if condition:
# code block
● If-Else Statement:
● Checks a condition. If true, executes one block of code, otherwise
executes another.
● Syntax:
if condition:
# code block 1
else:
# code block 2
● If-Elif-Else Statement:
● Multiple decision points.
● Syntax:
if condition1:
# code block 1
elif condition2:
# code block 2
else:
# code block 3
3. Loops
while condition:
# code block
# code block
if condition:
break
● For Loops:
● Used when the number of iterations is known.
● Has an inherent counter.
● While Loops:
● Useful for unpredictable tasks, like user input.
● Can use a counter, but must be initialized and incremented manually.
● Can be rewritten as a for loop, but the reverse isn't always true.
1. Robot Cheerleaders:
2. Toolbox Expansion:
● Added knowledge:
● Integers, floats, booleans.
● String manipulation.
● Math operations.
● Conditionals and branching.
● Loops: for and while.
3. Algorithms:
5. Approximate Solutions:
6. Bisection Search:
7. Practical Demonstration:
● The bisection search code shown only works for positive cubes.
● For numbers less than 1, the cube root will be outside the initial boundary.
● A small adjustment can be made to the code to account for this.
Conclusion:
1. Functions:
● Functions are like mini-programs within a program.
● They allow for code reuse and modularization.
● Functions can take parameters and return values.
Example:
python
Copy code
def is_even(i):
"""
Input: i, a positive integer
Returns True if i is even, otherwise False
"""
return i % 2 == 0
2. Scope:
● Scope refers to the environment in which variables exist and can be accessed.
● Each function has its own scope.
● Variables defined inside a function are not accessible outside that function.
4. Nested Functions:
python
Copy code
def outer_function(x):
def inner_function(y):
return y + x
return inner_function
Conclusion:
● Functions are crucial for writing clean, efficient, and maintainable code.
● Understanding scope is essential to avoid variable access errors.
● Decomposition and abstraction are foundational concepts in computer science
and are facilitated by the use of functions.
1. Introduction to Lists
3. Mutability of Lists
● Lists can be modified after they are created.
● Example: If L = [2, 1, 3], then executing L[1] = 5 would change L to [2, 5, 3].
● Multiple variables can point to the same list. If the list is modified, all variables
pointing to that list will reflect the change.
● It's more Pythonic to iterate over the elements of a list directly rather than using
indices.
● Example:
● python
● Copy code
for i in L:
print(i)
●
5. List Operations
● list(s) converts string s into a list where each character becomes an element.
● s.split(delimiter) splits a string into a list based on a delimiter.
● 'delimiter'.join(L) joins a list into a string using a delimiter.
8. Cloning Lists
10. Conclusion
1. Recursion:
2. Towers of Hanoi:
● Problem: Move a stack of disks from one pole to another, with the constraint that
you can only move one disk at a time and you can't place a larger disk on top of
a smaller disk.
● Recursive solution:
● Move n-1 disks to the spare pole.
● Move the nth disk to the target pole.
● Move the n-1 disks from the spare pole to the target pole.
3. Fibonacci Sequence:
● A sequence where each number is the sum of the two preceding ones.
● Recursive solution:
● Base cases:
● Fib(0) = 1
● Fib(1) = 1
● Recursive case:
● Fib(n) = Fib(n-1) + Fib(n-2)
4. Palindromes:
5. Dictionaries:
6. Using Dictionaries:
7. Memoization:
● A method of improving the efficiency of recursive algorithms by storing previously
computed results in a dictionary.
● Example: Improving the efficiency of the Fibonacci sequence calculation.
● Store previously computed Fibonacci numbers in a dictionary.
● Check the dictionary before computing a Fibonacci number to see if it has
already been calculated.
Conclusion:
● Recursion is a powerful tool for breaking down complex problems into simpler
versions of the same problem.
● Dictionaries are versatile data structures that can be used to store and retrieve
data efficiently.
● Memoization can significantly improve the efficiency of recursive algorithms.
1. Debugging:
2. Error Messages:
● Types of Errors:
● IndexError: Accessing beyond list limits.
● TypeError: Incorrect data type conversion.
● NameError: Accessing undeclared variables.
● SyntaxError: Missing parentheses, colons, etc.
● Logic Errors: These are harder to spot and require more time to debug.
● Explaining your code to someone else (or even a rubber ducky) can help you
understand and identify issues in your code.
● Don't: Write the entire program, then test and debug all at once.
● Do: Use unit testing. Write a function, test it, debug it, and then move on to the
next function.
Exceptions:
Example:
python
Copy code
try:
val = int(input("Enter a number: "))
except ValueError:
print("That's not a number!")
Assertions:
Example:
python
Copy code
def avg(grades):
assert len(grades) != 0, "No grades data"
return sum(grades)/len(grades)
Summary:
1. Defining Classes:
● Classes are defined using the class keyword.
● The __init__ method is a special method used to initialize an object.
● self is a reference to the instance of the object itself. It's used to access variables
that belong to the class.
Example:
python
Copy code
class Coordinate:
def __init__(self, x, y):
self.x = x
self.y = y
3. Creating Objects:
Example:
python
Copy code
c = Coordinate(3, 4)
origin = Coordinate(0, 0)
Example:
python
Copy code
print(c.x) # Outputs: 3
print(origin.x) # Outputs: 0
5. Methods:
Example:
python
Copy code
def distance(self, other):
return ((self.x - other.x)**2 + (self.y - other.y)**2)**0.5
6. Using Methods:
Example:
python
Copy code
dist = c.distance(origin)
7. Special Methods:
● Methods with double underscores before and after their name are special
methods.
● They allow customization of default Python operations.
Example:
python
Copy code
def __str__(self):
return "<" + str(self.x) + "," + str(self.y) + ">"
8. Printing Objects:
Example:
python
Copy code
def __add__(self, other):
return Coordinate(self.x + other.x, self.y + other.y)
Example:
python
Copy code
class Fraction:
def __init__(self, top, bottom):
self.num = top
self.den = bottom
● Hierarchies:
● Classes can be organized in hierarchies.
● Example: All animals have an age, but specific animals like cats, rabbits,
and humans have additional attributes and behaviors.
● A student is a type of person and also an animal. They might have
attributes like a major or favorite subject.
● Parent classes are at the top of the hierarchy, and child classes inherit
from them.
● Inheritance:
● Child classes inherit all attributes and methods from their parent classes.
● They can add new attributes and methods or override existing ones.
● Example: An animal might have a basic speak method, but a cat's speak
method might make it say "meow".
● Instance Variables:
● Specific to each instance of a class.
● Example: self.name or self.age in an animal class.
● Class Variables:
● Shared across all instances of a class.
● If one instance modifies a class variable, all other instances will see the
modified value.
● Example: A tag variable in a rabbit class that keeps track of how many
rabbit instances have been created.
● Overriding Methods:
● Child classes can override methods from their parent classes.
● Example: A student's speak method might make them say "I have
homework" instead of just "hello".
● Using Class Variables:
● Class variables can be used to keep track of shared data across
instances.
● Example: A tag class variable in a rabbit class that increments each time a
new rabbit is created.
● Special Methods:
● Python classes can implement special methods to define custom behavior
for built-in operations.
● Example: The __add__ method can be defined to specify what happens
when two instances of a class are added together.
Conclusion:
Introduction
Big O Notation
● Describes how the runtime of an algorithm grows relative to the input size.
● Focuses on the term that grows most rapidly.
● Ignores additive and multiplicative constants.
Complexity Classes
Analyzing Algorithms
Examples
Linear Search:
● Searching for an element in an unsorted list.
● Worst-case scenario: O(n).
Nested Loops:
● Typically quadratic behavior (O(n^2)).
● For each element in one list, the algorithm checks all elements in another
list.
Subset Check:
● Check if one list is a subset of another.
● Nested loops result in O(n^2) complexity.
Intersection of Two Lists:
● Find common elements in two lists.
● Nested loops and list membership checks lead to O(n^2) complexity.
Key Takeaways
1. Towers of Hanoi:
● The recursive solution for the Towers of Hanoi problem has exponential growth.
● The order of growth is
● 2�
● 2
● n
● .
● The story of the Towers of Hanoi: When the priests in the temple move the entire
stack from one peg to another, the world ends. For
● �=64
● n=64, the number is
● 264
● 2
● 64
● , which is a huge number. Even if one move is made per second, it would take an
incredibly long time to finish.
2. Power Set:
● Definition: For a given set of integers (without repeats), the power set is the
collection of all possible subsets.
● Example: For the set {1, 2, 3, 4}, the power set includes subsets with 0, 1, 2, 3,
and 4 elements.
● Recursive solution:
● Base case: If the list is empty, return a list containing an empty list.
● Recursive case: Generate the power set for the list excluding the last
element. Then, for each subset in that power set, create a new subset that
includes the last element.
● The size of the power set doubles with each additional element in the original set.
3. Code Analysis:
● The code for generating the power set is elegant and concise.
● The recursive solution is more intuitive and cleaner than an iterative solution with
nested loops.
● Complexity analysis:
● The code has a recursive call, which is called
● �
● n times (linear).
● However, there's an inner loop that grows exponentially with the size of
the "smaller" list. This leads to an exponential growth in complexity.
4. Fibonacci Sequence:
● Definition: The nth Fibonacci number is the sum of the (n-1)th and (n-2)th
Fibonacci numbers.
● Iterative solution:
● Complexity:
● �(�)
● O(n) (linear).
● Recursive solution:
● Two recursive calls for each Fibonacci number.
● Complexity: Exponential growth, approximately the golden ratio to the nth
power.
5. Big O Notation:
Conclusion:
1. Selection Sort:
● Overview: Iteratively selects the smallest element from the unsorted portion and
swaps it with the first unsorted element.
● Mechanism:
● Start with the first element.
● Search for the smallest element in the list.
● Swap the smallest element with the first element.
● Move to the next element and repeat until the entire list is sorted.
● Complexity: Quadratic (O(n^2)).
2. Merge Sort:
● Overview: A divide and conquer algorithm that splits a list into halves, sorts each
half, and then merges the sorted halves.
● Mechanism:
● If the list has 0 or 1 element, it's already sorted.
● Otherwise, split the list into two halves.
● Recursively sort both halves.
● Merge the two halves to produce a single sorted list.
● Visualization:
● Start with a large unsorted list.
● Continuously split the list until you have lists of size 0 or 1.
● Merge these small lists pairwise into larger sorted lists.
● Complexity: Log-linear (O(n log n)).
3. Merging Process:
● Overview: Given two sorted lists, the merging process combines them into a
single sorted list.
● Mechanism:
● Compare the smallest elements of each list.
● Add the smaller element to the result list.
● Move the pointer in the list from which the smaller element was taken.
● Repeat until one list is empty, then append the remaining elements from
the other list.
● Complexity: Linear (O(n)).
5. Computational Thinking:
7. Conclusion: