Skip to content

Commit cc51a7a

Browse files
committed
Continued work on approach: filter for multiples
1 parent a4bb5cd commit cc51a7a

File tree

1 file changed

+93
-16
lines changed
  • exercises/practice/sum-of-multiples/.approaches/filter-for-multiples

1 file changed

+93
-16
lines changed

exercises/practice/sum-of-multiples/.approaches/filter-for-multiples/content.md

Lines changed: 93 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -24,21 +24,20 @@ Therefore, Python provides the built-in function [`sum`][builtin-sum].
2424
A value is iterable whenever it makes sense to use it in a `for` loop like this:
2525

2626
```python
27-
for _ in iterable_value: # 👈
27+
for element in iterable_value: # 👈
2828
...
2929
```
3030

3131
The `list` is the most commonly used iterable data structure.
3232
Many other containers are also iterable, such as `set`s, `tuple`s, `range`s, and even `dict`s and `str`ings.
33-
Still other examples include iterators and generators, which are discuss below.
33+
Still other examples include iterators and generators, which are discussed below.
3434

35-
When given such a collection of numbers, `sum` will look at the elements one by one and add them together.
35+
When given a collection of numbers, `sum` will look at the elements one by one and add them up.
3636
The result is a single number.
3737

3838
```python
3939
numbers = range(1, 100 + 1) # 1, 2, …, 100
40-
sum(numbers)
41-
# ⟹ 5050
40+
sum(numbers) # ⟹ 5050
4241
```
4342

4443
Had the highlighted solution not used `sum`, it might have looked like this:
@@ -48,7 +47,7 @@ def sum_of_multiples(limit, factors):
4847
is_multiple = lambda n: any(n % f == 0 for f in factors if f != 0)
4948
total = 0
5049
for multiple in filter(is_multiple, range(limit)):
51-
total += total
50+
total += multiple
5251
return total
5352
```
5453

@@ -74,7 +73,7 @@ str.isupper("⬆️💼") # ⟹ False
7473

7574
Thus, the function `str.isupper` represents the property of _being an uppercase string_.
7675

77-
Contrary to what you might expect, `filter` does not return a data structure like the one given as an argument:
76+
Contrary to what you might expect, `filter` does not return a data structure like the one given as the iterable argument:
7877

7978
```python
8079
filter(str.isupper, ["THUNDERBOLTS", "and", "LIGHTNING"])
@@ -84,12 +83,21 @@ filter(str.isupper, ["THUNDERBOLTS", "and", "LIGHTNING"])
8483
Instead, it returns an **iterator**.
8584

8685
An iterator is an object whose sole purpose is to guide iteration through some data structure.
87-
In particular, `filter` makes sure that elements that do not satisfy the predicate are skipped.
88-
It is a bit like a cursor that can move only to the right.
86+
In particular, `filter` makes sure that elements that do not satisfy the predicate are skipped:
87+
88+
```python
89+
for word in filter(str.isupper, ["THUNDERBOLTS", "and", "LIGHTNING"]):
90+
print(word)
91+
# prints:
92+
# THUNDERBOLTS
93+
# LIGHTNING
94+
```
95+
96+
An iterator is a bit like a cursor that can move only to the right.
8997

9098
The main differences between containers (such as `list`s) and iterators are
9199

92-
- Containers can, depending on their contents, take up a lot of space in memory, but iterators are generally very small (regardless of how many elements they 'contain').
100+
- Containers can, depending on their contents, take up a lot of space in memory, but iterators are typically very small regardless of how many elements they 'contain'.
93101
- Containers can be iterated over multiple times, but iterators can be used only once.
94102

95103
To illustrate the latter difference:
@@ -109,10 +117,10 @@ Here, `sum` iterates over both `numbers` and `even_numbers` twice.
109117
In the case of `numbers` everything is fine.
110118
Even after looping through the whole of `numbers`, all its elements are still there, and so `sum` can ask to see them again without problem.
111119

112-
The situation with `even_numbers` is move involved.
120+
The situation with `even_numbers` is less simple.
113121
To use the _cursor_ analogy: after going through all of `even_number`'s 'elements' – actually elements of `numbers` – the cursor has moved all the way to the right.
114-
It cannot move backwards, so if you wish to iterate over all even numbers then you need a new cursor.
115-
We say the the `even_numbers` iterator is _exhausted_. When `sum` asks for its elements again, `even_numbers` comes up empty and so `sum` returns `0`.
122+
It cannot move backwards, so if you wish to iterate over all even numbers again then you need a new cursor.
123+
We say that the `even_numbers` iterator is _exhausted_. When `sum` asks for its elements again, `even_numbers` comes up empty and so `sum` returns `0`.
116124

117125
Had the highlighted solution not used `filter`, it might have looked like this:
118126

@@ -124,14 +132,83 @@ def sum_of_multiples(limit, factors):
124132
```
125133

126134
This variant stores all the multiples in a `list` before summing them.
127-
Such a list can potentially be very big.
128-
For example, if `limit = 1_000_000_000` and `factors = [1]` then `multiples` will be a list 8 gigabytes large!
135+
Such a list can become very big.
136+
For example, if `limit = 1_000_000_000` and `factors = [1]` then `multiples` will take up 8 gigabytes of memory!
129137
It is to avoid unnecessarily creating such large intermediate data structures that iterators are often used.
130138

131139

132140
### A function expression: `lambda`
133141

134-
...
142+
Typically, when using higher-order functions like `filter` and `map`, the function to pass as an argument does not yet exist and needs to be defined first.
143+
144+
The standard way of defining functions is through the `def` statement:
145+
146+
```python
147+
def name(parameters):
148+
statements
149+
```
150+
151+
Downsides of this construct include
152+
153+
- the syntax can be a bit bulky
154+
- it requires coming up with a fresh name
155+
156+
These qualities can be quite bothersome when you just need a simple function of no particular significance for single use only.
157+
In situations like this you might like to use a **lambda expression** instead.
158+
159+
A lambda expression is a specific kind of expression that evaluates to a function.
160+
It looks like this:
161+
162+
```python
163+
lambda parameters: expression # general form
164+
lambda a, b, x: a * x + b # specific example
165+
```
166+
167+
This latter lambda expression evaluates to a function that takes three arguments (`a`, `b`, `x`) and returns the value `a * x + b`.
168+
Except for not having a name, it is equivalent to the function defined by
169+
170+
```python
171+
def some_name(a, b, x):
172+
return a * x + b
173+
```
174+
175+
A lambda expression need not necessarily be passed as an argument.
176+
It can also be applied to arguments immediately, or assigned to a variable:
177+
178+
```python
179+
lambda a, b, x: a * x + b
180+
# ⟹ <function <lambda> at 0x000001F36A274CC0>
181+
182+
(lambda a, b, x: a * x + b)(2, 3, 5)
183+
# ⟹ 13
184+
185+
some_function = lambda a, b, x: a * x + b
186+
some_function(2, 3, 5)
187+
# ⟹ 13
188+
189+
list(filter(
190+
lambda s: len(s) <= 3,
191+
["aaaa", "b", "ccccc", "dd", "eee"]
192+
))
193+
# ⟹ ['b', 'dd', 'eee']
194+
```
195+
196+
Only functions that can be defined using a single (`return`) statement can be written as a lambda expression.
197+
If you need multiple statements, you have no choice but to use `def`.
198+
199+
The solution highlighted above assigns a lambda expression to a variable: `is_multiple`.
200+
Some people consider this to be unidiomatic and feel one should always use `def` when a function is to have a name.
201+
A lambda expression is used here anyway to demonstrate the feature, and also because the author prefers its compactness.
202+
203+
Had the highlighted solution not used `lambda`, it might have looked like this:
204+
205+
```python
206+
def sum_of_multiples(limit, factors):
207+
def is_multiple(n):
208+
return any(n % f == 0 for f in factors if f != 0)
209+
210+
return sum(filter(is_multiple, range(limit)))
211+
```
135212

136213

137214
### Built-in function: `any`

0 commit comments

Comments
 (0)