Skip to content

Commit 45af046

Browse files
committed
Initial sketch
1 parent d8e8cb6 commit 45af046

File tree

1 file changed

+109
-0
lines changed

1 file changed

+109
-0
lines changed
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
# Introduction
2+
3+
<!-- TODO write a proper introduction -->
4+
5+
Several possible approaches to this exercise:
6+
7+
- filter for multiples
8+
- generate multiples and
9+
- gather & de-duplicate, e.g. using `set().union`
10+
- merge the multiple-generators into one
11+
- spot the repeating pattern
12+
13+
14+
## Approach: `filter` for multiples
15+
16+
```python
17+
def sum_of_multiples(limit, factors):
18+
is_multiple = lambda n: any(n % f == 0 for f in factors if f != 0)
19+
return sum(filter(is_multiple, range(limit)))
20+
```
21+
22+
Egregious performance when multiples are few.
23+
24+
...
25+
26+
27+
<!-- TODO improve section title -->
28+
## Approach: generate & gather multiples
29+
30+
```python
31+
def sum_of_multiples(limit, factors):
32+
multiples = (range(0, limit, f) for f in factors if f != 0)
33+
return sum(set().union(*multiples))
34+
```
35+
36+
Egregious memory occupancy when multiples are many.
37+
38+
...
39+
40+
41+
<!-- TODO improve section title -->
42+
## Approach: merge the multiple-generators into one
43+
44+
```python
45+
# NOTE This is a sketch (but it does work)
46+
def sum_of_multiples(limit, factors):
47+
generators = [range(0, limit, f) for f in factors if f != 0]
48+
while len(generators) > 1:
49+
generators = [
50+
merge(g, g_)
51+
for g, g_ in zip_longest(generators[0::2], generators[1::2], fillvalue=())
52+
]
53+
all_multiples, *_ = generators + [()]
54+
return sum(all_multiples)
55+
56+
57+
def merge(gen1, gen2):
58+
"""Merge two sorted-without-duplicates iterables
59+
into a single sorted-without-duplicates generator.
60+
"""
61+
return sorted({*gen1, *gen2}) # FIXME this is CHEATING
62+
```
63+
64+
This is supposed to use very little memory.
65+
66+
...
67+
68+
69+
<!-- TODO: improve section title -->
70+
## Approach: spot the repeating pattern
71+
72+
```python
73+
# NOTE this too is but a sketch (that nevertheless works)
74+
def sum_of_multiples(limit, factors):
75+
(*factors,) = filter(lambda f: f != 0, factors)
76+
N = lcm(*factors)
77+
is_multiple = lambda n: any(n % f == 0 for f in factors)
78+
multiples_up_to_lcm = [n for n in range(1, N + 1) if is_multiple(n)]
79+
q, r = divmod(limit - 1, N)
80+
return (
81+
q * (q - 1) // 2 * N * len(multiples_up_to_lcm)
82+
+ q * sum(multiples_up_to_lcm)
83+
+ sum(q * N + m for m in takewhile(lambda m: m <= r, multiples_up_to_lcm))
84+
)
85+
```
86+
87+
```text
88+
assuming: limit = 22 multiples = [2, 3]
89+
the task is to sum the lower 4 below rows
90+
91+
| 1 2 3 4 5 6| 7 8 9 10 11 12|13 14 15 16 17 18|19 20 21
92+
| 2 3 4 6| 6 6 6 6| 6 6 6 6| 6 6
93+
| | 2 3 4 6| 6 6 6 6| 6 6
94+
| | | 2 3 4 6| 6 6
95+
| | | | 2 3
96+
97+
We see
98+
3 copies of - 2 3 4 - 6
99+
0+1+2 = 3×(3-1)/2 = 3 copies of - 6 6 6 - 6
100+
3 copies of - 6 6
101+
1 copy of - 2 3
102+
```
103+
104+
<!-- TODO properly explain this stuff -->
105+
106+
This approach saves on a lot of iteration, but is still vulnerable to excessive memory use.
107+
Fortunately it can be combined with the generator merging approach.
108+
109+
...

0 commit comments

Comments
 (0)