Skip to content

Commit c450459

Browse files
committed
updating materials after writing article
1 parent b32f546 commit c450459

File tree

8 files changed

+164
-76
lines changed

8 files changed

+164
-76
lines changed

python-first/README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
# How to Find the First Match from an Iterable in Python
1+
# How to Get the First Match From a Python List or Iterable
22

33
The code samples and supporting materials for the corresponding tutorial on Real Python.
44

5+
To run the chart scripts, you'll need to have `matplotlib` installed.
6+
57
Country data obtained from https://github.com/samayo/country-json

python-first/chart.py

Lines changed: 26 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,61 @@
1+
"""
2+
Script to chart out the performance of generators and loops for finding the
3+
first matching element in a list.
4+
"""
5+
16
from timeit import timeit
27

38
import matplotlib.pyplot as plt
49

5-
from test_fixtures import build_list
610

711
TIMEIT_TIMES = 100 # Increase number for smoother lines
8-
LIST_SIZE = 10000
12+
LIST_SIZE = 1000
913
POSITION_INCREMENT = 10
1014

11-
looping_times = []
12-
generator_times = []
13-
in_times = []
14-
positions = []
1515

16-
17-
def find_match_loop(list_to_search, item_to_find):
18-
for val in list_to_search:
19-
if val == item_to_find:
16+
def find_match_loop(iterable):
17+
for val in iterable:
18+
if val["population"] > 50:
2019
return val
21-
return None
2220

2321

24-
def find_match_gen(list_to_search, item_to_find):
25-
return next((val for val in list_to_search if val == item_to_find), None)
22+
def find_match_gen(iterable):
23+
return next(val for val in iterable if val["population"] > 50)
24+
2625

26+
def build_list(size, fill, value, at_position):
27+
return [fill if i != at_position else value for i in range(size)]
2728

28-
def find_match_in(list_to_search, item_to_find):
29-
if item_to_find in list_to_search:
30-
return item_to_find
3129

30+
looping_times = []
31+
generator_times = []
32+
positions = []
3233

3334
for position in range(0, LIST_SIZE, POSITION_INCREMENT):
3435
print(
3536
f"Progress {position/LIST_SIZE:.0%}",
36-
end=f"{3 * ' '}\r", # Clear previous characters and resets cursor
37+
end=f"{3 * ' '}\r", # Clear previous characters and reset cursor
3738
)
3839

3940
positions.append(position)
4041

4142
list_to_search = build_list(
42-
LIST_SIZE, "Fake Python", "Real Python", position
43+
LIST_SIZE,
44+
{"country": "Oceania", "population": 10},
45+
{"country": "Atlantis", "population": 100},
46+
position,
4347
)
4448

4549
looping_times.append(
4650
timeit(
47-
"find_match_loop(list_to_search, 'Real Python')",
51+
"find_match_loop(list_to_search)",
4852
globals=globals(),
4953
number=TIMEIT_TIMES,
5054
)
5155
)
5256
generator_times.append(
5357
timeit(
54-
"find_match_gen(list_to_search, 'Real Python')",
55-
globals=globals(),
56-
number=TIMEIT_TIMES,
57-
)
58-
)
59-
in_times.append(
60-
timeit(
61-
"find_match_in(list_to_search, 'Real Python')",
58+
"find_match_gen(list_to_search)",
6259
globals=globals(),
6360
number=TIMEIT_TIMES,
6461
)
@@ -70,11 +67,10 @@ def find_match_in(list_to_search, item_to_find):
7067

7168
plot = ax.plot(positions, looping_times, label="loop")
7269
plot = ax.plot(positions, generator_times, label="generator")
73-
plot = ax.plot(positions, in_times, label="in")
7470

7571
plt.xlim([0, LIST_SIZE])
7672
plt.xlabel("Position of element to be found")
77-
plt.ylim([0, max(max(looping_times), max(generator_times), max(in_times))])
73+
plt.ylim([0, max(max(looping_times), max(generator_times))])
7874
plt.ylabel(f"Time to complete {TIMEIT_TIMES:,} times")
7975
plt.legend()
8076

@@ -86,17 +82,15 @@ def find_match_in(list_to_search, item_to_find):
8682
generator_ratio = [
8783
gen / loop for gen, loop in zip(generator_times, looping_times)
8884
]
89-
in_ratio = [in_ / loop for in_, loop in zip(in_times, looping_times)]
9085

9186
fig, ax = plt.subplots()
9287

9388
plot = ax.plot(positions, looping_ratio, label="loop")
9489
plot = ax.plot(positions, generator_ratio, label="generator")
95-
plot = ax.plot(positions, in_ratio, label="in")
9690

9791
plt.xlim([0, LIST_SIZE])
9892
plt.xlabel("Position of element to be found")
99-
plt.ylim([0, max(max(looping_ratio), max(generator_ratio), max(in_ratio))])
93+
plt.ylim([0, max(max(looping_ratio), max(generator_ratio))])
10094
plt.ylabel(f"Time to complete {TIMEIT_TIMES:,} times")
10195
plt.legend()
10296

python-first/chart_gen_loop_in.py

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
"""
2+
Script to chart out the performance of generators, loops and the in operator
3+
when trying to find the first matching element in a list.
4+
"""
5+
6+
from timeit import timeit
7+
8+
import matplotlib.pyplot as plt
9+
10+
from test_fixtures import build_list
11+
12+
TIMEIT_TIMES = 100 # Increase number for smoother lines
13+
LIST_SIZE = 10000
14+
POSITION_INCREMENT = 10
15+
16+
looping_times = []
17+
generator_times = []
18+
in_times = []
19+
positions = []
20+
21+
22+
def find_match_loop(list_to_search, item_to_find):
23+
for val in list_to_search:
24+
if val == item_to_find:
25+
return val
26+
return None
27+
28+
29+
def find_match_gen(list_to_search, item_to_find):
30+
return next((val for val in list_to_search if val == item_to_find), None)
31+
32+
33+
def find_match_in(list_to_search, item_to_find):
34+
if item_to_find in list_to_search:
35+
return item_to_find
36+
37+
38+
for position in range(0, LIST_SIZE, POSITION_INCREMENT):
39+
print(
40+
f"Progress {position/LIST_SIZE:.0%}",
41+
end=f"{3 * ' '}\r", # Clear previous characters and resets cursor
42+
)
43+
44+
positions.append(position)
45+
46+
list_to_search = build_list(
47+
LIST_SIZE, "Fake Python", "Real Python", position
48+
)
49+
50+
looping_times.append(
51+
timeit(
52+
"find_match_loop(list_to_search, 'Real Python')",
53+
globals=globals(),
54+
number=TIMEIT_TIMES,
55+
)
56+
)
57+
generator_times.append(
58+
timeit(
59+
"find_match_gen(list_to_search, 'Real Python')",
60+
globals=globals(),
61+
number=TIMEIT_TIMES,
62+
)
63+
)
64+
in_times.append(
65+
timeit(
66+
"find_match_in(list_to_search, 'Real Python')",
67+
globals=globals(),
68+
number=TIMEIT_TIMES,
69+
)
70+
)
71+
72+
print("Progress 100%")
73+
74+
fig, ax = plt.subplots()
75+
76+
plot = ax.plot(positions, looping_times, label="loop")
77+
plot = ax.plot(positions, generator_times, label="generator")
78+
plot = ax.plot(positions, in_times, label="in")
79+
80+
plt.xlim([0, LIST_SIZE])
81+
plt.xlabel("Position of element to be found")
82+
plt.ylim([0, max(max(looping_times), max(generator_times), max(in_times))])
83+
plt.ylabel(f"Time to complete {TIMEIT_TIMES:,} times")
84+
plt.legend()
85+
86+
plt.show()
87+
88+
# Ratio
89+
90+
looping_ratio = [loop / loop for loop in looping_times]
91+
generator_ratio = [
92+
gen / loop for gen, loop in zip(generator_times, looping_times)
93+
]
94+
in_ratio = [in_ / loop for in_, loop in zip(in_times, looping_times)]
95+
96+
fig, ax = plt.subplots()
97+
98+
plot = ax.plot(positions, looping_ratio, label="loop")
99+
plot = ax.plot(positions, generator_ratio, label="generator")
100+
plot = ax.plot(positions, in_ratio, label="in")
101+
102+
plt.xlim([0, LIST_SIZE])
103+
plt.xlabel("Position of element to be found")
104+
plt.ylim([0, max(max(looping_ratio), max(generator_ratio), max(in_ratio))])
105+
plt.ylabel(f"Time to complete {TIMEIT_TIMES:,} times")
106+
plt.legend()
107+
108+
plt.show()

python-first/find_first_match.py

Lines changed: 0 additions & 10 deletions
This file was deleted.
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from test_fixtures import countries
22

33

4-
def find_match_gen(iterable, value=None, key=None, default=None):
4+
def get_first(iterable, value=None, key=None, default=None):
55
match value is None, callable(key):
66
case (True, True):
77
gen = (val for val in iterable if key(val))
@@ -17,9 +17,9 @@ def find_match_gen(iterable, value=None, key=None, default=None):
1717

1818
if __name__ == "__main__":
1919
target_match = {"country": "Norway", "population": 5311916}
20-
print(find_match_gen(countries, target_match))
20+
print(get_first(countries, target_match))
2121

2222
def match_scotland(data):
2323
return data["country"] == "Scotland"
2424

25-
print(find_match_gen(countries, key=match_scotland))
25+
print(get_first(countries, key=match_scotland))

python-first/get_first_alt.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from test_fixtures import countries
2+
3+
4+
def find_match_gen(iterable, key=None, default=None):
5+
if callable(key):
6+
gen = (val for val in iterable if key(val))
7+
else:
8+
gen = (val for val in iterable if val)
9+
10+
return next(gen, default)
11+
12+
13+
if __name__ == "__main__":
14+
target_match = {"country": "Norway", "population": 5311916}
15+
print(find_match_gen(countries, target_match))
16+
17+
def match_scotland(data):
18+
return data["country"] == "Scotland"
19+
20+
print(find_match_gen(countries, key=match_scotland))
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
from test_fixtures import countries
22

33

4-
def find_match_loop_no_key(iterable, value=None, default=None):
4+
def get_first_loop_no_key(iterable, value=None, default=None):
55
for val in iterable:
66
if (value is None and val) or (value is not None and val == value):
77
return val
88
return default
99

1010

11-
def find_match_loop(iterable, value=None, key=None, default=None):
11+
def get_first_loop(iterable, value=None, key=None, default=None):
1212
for val in iterable:
1313
_val = key(val) if callable(key) else val
1414

@@ -19,9 +19,9 @@ def find_match_loop(iterable, value=None, key=None, default=None):
1919

2020
if __name__ == "__main__":
2121
target_match = {"country": "Puerto Rico", "population": 3195153}
22-
print(find_match_loop(countries, target_match))
22+
print(get_first_loop(countries, target_match))
2323

2424
def match_scotland(data):
2525
return data["country"] == "Scotland"
2626

27-
print(find_match_loop(countries, key=match_scotland))
27+
print(get_first_loop(countries, key=match_scotland))

python-first/perf_check.py

Lines changed: 0 additions & 26 deletions
This file was deleted.

0 commit comments

Comments
 (0)