<style>
.present {
text-align: left;
}
img[alt=knight_moves] {
width: 400px;
}
</style>
# More Built-ins, Comprehensions, and Classes (Archive)
## Week 17 Day 4
---
## Part 1: More Built-ins
---
### Lecture Videos (22 minutes)
Watch:
- Built-Ins: All And Any (8:00)
- Built-Ins: Filter and Map (10:32)
---
### Built-in functions (`all()`)
The `all()` function checks that *all* items in a collection are true (a.k.a, that it contains *no falsey* items).
```python=
test1 = {"item", "truthy", ""}
test2 = []
test3 = [[]]
print(all(test1), test1)
print(all(test2), test2)
print(all(test3), test3)
```
---
### Built-in functions (`all()`)
Answers:
```python=
test1 = {"item", "truthy", ""}
test2 = []
test3 = [[]]
print(all(test1), test1) # False {"", "truthy", "item"}
print(all(test2), test2) # True []
print(all(test3), test3) # False [[]]
```
---
### Built-in functions (`any()`)
The `any()` checks that there is at least one truthy item in the provided collection.
```python=
test1 = ["item", [], []]
test2 = []
test3 = [[]]
print(any(test1), test1)
print(any(test2), test2)
print(any(test3), test3)
```
---
### Built-in functions (`any()`)
Answers:
```python=
test1 = ["item", [], []]
test2 = []
test3 = [[]]
print(any(test1), test1) # True ['item', [], []]
print(any(test2), test2) # False []
print(any(test3), test3) # False [[]]
```
---
### Built-ins (`filter()`)
The `filter()` function takes a function and an iterable, and it returns a "filter object".
The returned collection includes only the items which, when the function parameter was applied to them, returned a truthy value.
```python=
def is_a(num):
if num >= 90:
return True
else:
return False
scores = [90, 86, 75, 91, 62, 99, 88, 90]
only_as = filter(is_a, scores) # does not mutate original
print(only_as) # <filter object at 0x10546ad30>
print(list(only_as)) # [90, 91, 99, 90]
```
---
### Built-ins (`filter()`)
`filter`'s function parameter can also be defined in line as a `lambda` function.
```python=
scores = [90, 86, 75, 91, 62, 99, 88, 90]
only_as = filter(lambda num: num >= 90, scores)
print(only_as) # <filter object at 0x10546ad30>
print(list(only_as)) # [90, 91, 99, 90]
```
---
### Built-ins (`map()`)
The `map()` function takes a function and an iterable, and it returns a "map object" that transforms each value from the original iterable, according to the provided function.
```python=
def get_grade(num):
if (num >= 90):
return "A"
elif (num <90 and num >= 80):
return "B"
elif (num < 80 and num >= 70):
return "C"
elif (num < 70 and num >= 60):
return "D"
else:
return "F"
scores = [90, 86, 75, 91, 62, 99, 88, 90]
print(map(get_grade, scores)) # <map object at 0x106faffa0>
grades = list(map(get_grade, scores))
print(grades) # ['A', 'B', 'C', 'A', 'D', 'A', 'B', 'A']
```
---
### Built-ins (`zip()`)
The `zip()` function takes two iterables and returns a "zip object" that pairs values at corresponding indices.
You can convert the "zip object" into a list of tuples with the `list()` function.
```python=
scores = [90, 86, 75, 91, 62, 99, 88, 90]
grades = ["A", "B", "C", "A", "D", "A", "B", "A"]
combined = zip(scores, grades)
combined_list = list(combined)
combined_dict = dict(combined_list)
print(combined) # <zip object at 0x1023a9600>
print(combined_list) # [(90, 'A'), (86, 'B'), (75, 'C'), (91, 'A'), (62, 'D'), (99, 'A'), (88, 'B'), (90, 'A')]
print(combined_dict) # {90: 'A', 86: 'B', 75: 'C', 91: 'A', 62: 'D', 99: 'A', 88: 'B'}
```
---
## Part 2: Comprehensions
---
### Lecture videos (22 minutes)
Watch:
- List Comprehensions Demo (18:50)
---
### Comprehensions
Comprehensions are composed of an expression followed by a `for...in` statement, followed by an optional `if` clause. They can be used to create new lists (or other mutable sequence types).
```python=
my_list = [expression for member in iterable]
# with optional if statement
my_list = [expression for member in iterable if condition]
```
---
### Copying a list
With a `for` loop:
```python=
my_list = [1, "2", "three", True, None]
my_list_copy = []
# for loop
# ----------
# / \
for item in my_list:
my_list_copy.append(item)
# |
# var
print(my_list_copy) # [1, '2', 'three', True, None]
```
---
### Copying a list
With a list comprehension:
```python=
my_list = [1, "2", "three", True, None]
# var for loop
# | -------------
# | / \
my_list_copy = [item for item in my_list]
print(my_list_copy) # [1, '2', 'three', True, None]
```
---
### Mapping over a list with comprehensions
Include the desired expression before the `for` statement
```python=
my_list = ["jerry", "MARY", "carrie", "larry"]
# expression for loop
# | -------------
# | / \
mapped_list = [item.lower() for item in my_list]
print(mapped_list) # ['jerry', 'mary', 'carrie', 'larry']
```
---
### Convert `map()` to list comprehension
```python=
nums = [-5, 11, 10, 14]
mapped_nums = map(lambda num: num * 2 + 1, nums)
print(list(mapped_nums)) # [-9, 23, 21, 29]
```
---
### Convert `map()` to list comprehension
Answer:
```python=
nums = [-5, 11, 10, 14]
# mapped_nums = map(lambda num: num * 2 +1, nums)
mapped_nums = [num * 2 + 1 for num in nums]
print(mapped_nums) # [-9, 23, 21, 29]
```
---
### Filtering a list with comprehensions
```python=
nums = [-5, 11, 10, 14]
filtered_nums = filter(lambda num: num > 0, nums)
print(list(filtered_nums)) # [11, 10, 14]
```
---
### Filtering a list with comprehensions
Answer:
```python=
nums = [-5, 11, 10, 14]
# filtered_nums = filter(lambda num: num > 0, nums)
filtered_nums = [num for num in nums if num > 0]
print(filtered_nums) # [11, 10, 14]
```
---
### Nested loops
Nested for loop:
```python=
letters = ["a", "b", "c"]
nums = [1, 2]
new_list = []
# outer loop
# ----------
# / \
for l in letters:
for n in nums: # <- inner loop
new_list.append((l, n))
# \ /
# ---
# expression
print(new_list) # [('a', 1), ('a', 2), ('b', 1), ('b', 2), ('c', 1), ('c', 2)]
```
---
### Nested loops (list comprehension)
With list comprehension—note that the outer loop is first:
```python=
letters = ["a", "b", "c"]
nums = [1, 2]
# expression outer loop inner loop
# --- -------------- -----------
# / \ / \ / \
new_list = [(l, n) for l in letters for n in nums]
print(new_list) # [('a', 1), ('a', 2), ('b', 1), ('b', 2), ('c', 1), ('c', 2)]
```
---
### Dictionary comprehensions
Use a colon in the expression to separate the key and value.
```python=
# a dictionary that maps numbers to the square of the number
number_dict = {num: num**2 for num in range(5)}
print(number_dict) # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
```
---
## Part 3: Classes
---
## Lecture videos (23 minutes)
Watch:
- Classes in Python Demo (19:18)
---
### Classes
To create a class we use the `class` keyword, and by convention, we capitalize the names of classes.
```python=
# python example
class Widget:
# more code to come
```
Looks a lot like JavaScript
```javascript=
// javascript comparison
class Widget {
// more code to come
}
```
---
### Initializing classes
Generally speaking:
- Python's `__init__()` is like JavaScript's `constructor()`
- Python's `self` is like JavaScript's `this`
```python=
# python example
class Widget:
def __init__(self):
# more code to follow
```
```javascript=
// javascript comparison
class Widget {
constructor() {
// more code to follow
}
}
```
---
### the `__init__()` method
Python's constructor method is called `__init__()`.
```python=
# python example
class Widget:
def __init__(self, color, shape):
self.color = color
self.shape = shape
```
```javascript=
// javascript comparison
class Widget {
constructor(color, shape) {
this.color = color;
this.shape = shape;
}
}
```
---
### Instance variables and methods
You can set attributes on the instance with dot notation (`self.some_attribute = value`).
You can add methods to the class by defining functions and passing in `self`.
```python=
class Widget:
def __init__(self, color, shape):
self.color = color
self.shape = shape
def my_method(self, word):
print(f"hello {word}")
return
```
---
### Wait, what _is_ `self`?
`self` refers to the instance that a method was called on.
Whenever you invoke a method on an instance of a class, it is as though you are invoking the class's own method, and passing in the instance as an argument.
```python=
some_widget = Widget("blue", "square")
# both below do the same thing
some_widget.my_method("other argument")
Widget.my_method(some_widget, "other argument")
```
---
### Wait, what _is_ `self`?
Calling the first parameter `self` is a convention.
This is technically valid Python, and it would do the same thing as calling it `self`. But no one would write it this way.
```python=
class Widget:
def __init__(banana, color, shape):
banana.color = color
banana.shape = shape
```
---
### Instances of classes
We create instances of a class by invoking the class as though it is a function (this invokes the class's `__init__()` method)
```python=
# in python
my_new_widget = Widget("blue", "circle")
```
```javascript=
// javascript comparison
const myNewWidget = new Widget("blue", "circle");
```
---
### Inheritance
To inherit from another class, we pass a reference to that class as an argument in the class definition.
We can use the `super()` function to get a reference to the parent class, then invoke the desired function.
```python=
class Widget:
def __init__(self, color, shape):
self.color = color
self.shape = shape
class Gadget(Widget):
def __init__(self, color, shape, noise):
super().__init__(color, shape)
self.noise = noise
thingie = Gadget("purple", "spiral", "whrrrrrr")
print(thingie) # <__main__.Gadget object at 0x105103d60>
print(thingie.color, thingie.shape, thingie.noise) # purple spiral whrrrrrr
```
---
### Getters and setters
Using getters and setters lets you have methods that behave like properties—these methods will run without being invoked when you attempt to access or update the value.
This gives your class a convenient interface for more complicated logic that happens behind the scenes, and it could also be useful for protecting certain "private" values on instances of your class.
Python doesn't have any actual "private" attributes—all of the attributes can be accessed or changed from outside the class or instance—so this isn't a security feature. It's just so that other users who interact with your code will understand how it is supposed to be used.
---
### Without getters and setters
```python=
class Widget:
def __init__(self, color, shape):
self.color = color
self.shape = shape
self.secret_property = None
def get_secret_property(self):
# log everytime someone accesses the secret property
print("oooh someone tried to get the secret property")
return self.secret_property
def set_secret_property(self, value):
# super secret hashing strategy
self.secret_property = str(value) + "abcde" * 3
trinket = Widget("orange", "cylinder")
print(trinket.get_secret_property())
trinket.set_secret_property("beep")
print(trinket.get_secret_property())
```
---
### With getters and setters
```python=
class Widget:
def __init__(self, color, shape, regular_property):
self.color = color
self.shape = shape
self.regular_property = regular_property
@property
def regular_property(self):
# log everytime someone accesses the secret property
print("oooh someone tried to get the secret property")
return self.secret_property
@regular_property.setter
def regular_property(self, value):
# super secret hashing strategy
self.secret_property = str(value) + "abcde" * 3
trinket = Widget("orange", "cylinder", "hops")
print(trinket.regular_property)
trinket.regular_property = "beep"
print(trinket.regular_property)
```
---
### "Private" properties
By convention, if you have private properties you want to protect, use a single underscore to indicate that.
We could replace the `secret_property` on our widget with `_property` to follow this convention, e.g.:
```python=
@property
def regular_property(self):
# log everytime someone accesses the secret property
print("oooh someone tried to get the secret property")
return self._property
```
---
### Getters
Using the `@property` decorator on a method creates a **getter**. The getter will not need to be invoked: the method will run when you attempt to access the value.
e.g.
```python=
print(trinket.regular_property)
```
causes this method to run.
```python=
@property
def regular_property(self):
# log everytime someone accesses the secret property
print("oooh someone tried to get the secret property")
return self.secret_property
```
---
### Setters
The decorator used to create a **setter** is `@<getter_method_name>.setter`.
```python=
@regular_property.setter
def regular_property(self, value):
# super secret hashing strategy
self.secret_property = str(value) + "abcde" * 3
```
You do not need to have a setter in order to have a getter, but you do need to have a getter to have a setter. The setter method will run when you try to change the value.
```python=
trinket.regular_property = "beep"
# equivalent to this line without setter
trinket.set_secret_property("beep")
```
---
### Duck-Typing
"If it looks like a duck, and quacks like a duck, it must be a duck"
- What does it mean for an object to "look/quack like a duck"?
- it has the relevent "magic method" that python uses to implement a given built-in function (like `len()` or `print()`)
- What does it mean to "be a duck"?
- that means built-in functions will be able to work on classes that you create
- Why is this good?
- duck-typing means that classes that you define can work as though they are already part of python!
---
### Duck-typing
Using duck-typing you can:
- have your class work with the addition operator (`+`)
- define what equality operator (`==`) means for your class
- loop over an instance of your class with `for ... in`
- and much more!
- duck-typing is powerful!
---
### Duck-typing
The default value when you print a user-defined class instance is not typically very helpful...
```python=
class Widget:
def __init__(self, color, shape):
self.color = color
self.shape = shape
my_new_widget = Widget("blue", "circle")
print(my_new_widget) # <__main__.Widget object at 0x1071cebb0>
```
---
### Duck-typing
You can use the `__repr__` method that will let you control what gets printed for your class.
```python=
class Widget:
def __init__(self, color, shape):
self.color = color
self.shape = shape
def __repr__(self):
return f"Widget({self.color}, {self.shape})"
my_new_widget = Widget("blue", "circle")
print(my_new_widget) # Widget(blue, circle)
```
---
## Nodes, Trees, and Chess
---
### Nodes and Trees
What is a tree?
- A tree is a collection of nodes, each node can have a maximum of one parent.
- The node at the top of a tree which has no parent is the root node.
- Depending on the type of tree, a node can have multiple children, but in a binary tree it can have a maximum of two.
- Nodes with no children are called leaf nodes.
```
x
/ \
x x
/ / \
x x x
/ / \
x x x
```
---
### Depth first search
1. First, fully explore the left side of a tree and each subtree
2. Then move on to the right side.
Traveral order:
```
1
/ \
2 5
/ / \
3 6 9
/ / \
4 7 8
```
---
### Breadth first search
1. First, explore all children of a node.
2. Then move on to the next level of the tree.
Traveral order:
```
1
/ \
2 3
/ / \
4 5 6
/ / \
7 8 9
```
---
### Nodes and Trees
```javascript=
// The start of a node class in JavaScript
class Node {
constructor(value) {
this._value = value;
this._parent = null;
this._children = [];
}
}
const node = new Node("x");
console.log(node)
```
---
### Chess
What does a knight do?
[video description](https://www.chess.com/lessons/how-to-move-the-pieces/the-knight)

---
### Working on Knight Moves
From a given position, the list of possible relative moves could be represented by the following:
```python=
possible_moves = [
(-2, -1),
(-2, 1),
(-1, -2),
(-1, 2),
(1, -2),
(1, 2),
(2, -1),
(2, 1),
]
```
---
### Today's project
We will be implementing nodes and trees so that we can represent all of the moves available to a knight as a tree.
Each node will have a value that is a set of coordinates on the board, and its children will be the collection of positions a knight at that position could move to.
Here are some additional notes for today's project: https://hackmd.io/@jpshafto/S1yh2mmuu
{"metaMigratedAt":"2023-06-16T11:12:27.576Z","metaMigratedFrom":"Content","title":"More Built-ins, Comprehensions, and Classes (Archive)","breaks":true,"contributors":"[{\"id\":\"a6f34c0b-3567-4ed5-ba81-c2299c2d9369\",\"add\":25674,\"del\":7948}]"}