Skip to content

Commit a56d11d

Browse files
authored
Merge pull request #303 from realpython/first-match
How to Find the First Match Materials
2 parents 7a74a81 + 4f37b0b commit a56d11d

File tree

11 files changed

+3569
-0
lines changed

11 files changed

+3569
-0
lines changed

python-first/README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# How to Get the First Match From a Python List or Iterable
2+
3+
The code samples and supporting materials for the [corresponding tutorial](https://realpython.com/python-first/) on Real Python.
4+
5+
To run the chart scripts, you'll need to have `matplotlib` installed.
6+
7+
Country data obtained from https://github.com/samayo/country-json

python-first/chart.py

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

python-first/chart_gen_loop_in.py

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
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+
TIMEIT_TIMES = 100 # Increase number for smoother lines
11+
LIST_SIZE = 1000
12+
POSITION_INCREMENT = 10
13+
14+
looping_times = []
15+
generator_times = []
16+
in_times = []
17+
positions = []
18+
19+
20+
def build_list(size, fill, value, at_position):
21+
return [value if i == at_position else fill for i in range(size)]
22+
23+
24+
def find_match_loop(list_to_search, item_to_find):
25+
for value in list_to_search:
26+
if value == item_to_find:
27+
return value
28+
return None
29+
30+
31+
def find_match_gen(list_to_search, item_to_find):
32+
return next(
33+
(value for value in list_to_search if value == item_to_find), None
34+
)
35+
36+
37+
def find_match_in(list_to_search, item_to_find):
38+
if item_to_find in list_to_search:
39+
return item_to_find
40+
41+
42+
for position in range(0, LIST_SIZE, POSITION_INCREMENT):
43+
print(
44+
f"Progress {position / LIST_SIZE:.0%}",
45+
end=f"{3 * ' '}\r", # Clear previous characters and resets cursor
46+
)
47+
48+
positions.append(position)
49+
50+
list_to_search = build_list(
51+
LIST_SIZE, "Fake Python", "Real Python", position
52+
)
53+
54+
looping_times.append(
55+
timeit(
56+
"find_match_loop(list_to_search, 'Real Python')",
57+
globals=globals(),
58+
number=TIMEIT_TIMES,
59+
)
60+
)
61+
generator_times.append(
62+
timeit(
63+
"find_match_gen(list_to_search, 'Real Python')",
64+
globals=globals(),
65+
number=TIMEIT_TIMES,
66+
)
67+
)
68+
in_times.append(
69+
timeit(
70+
"find_match_in(list_to_search, 'Real Python')",
71+
globals=globals(),
72+
number=TIMEIT_TIMES,
73+
)
74+
)
75+
76+
print("Progress 100%")
77+
78+
fig, ax = plt.subplots()
79+
80+
plot = ax.plot(positions, looping_times, label="loop")
81+
plot = ax.plot(positions, generator_times, label="generator")
82+
plot = ax.plot(positions, in_times, label="in")
83+
84+
plt.xlim([0, LIST_SIZE])
85+
plt.ylim([0, max(max(looping_times), max(generator_times), max(in_times))])
86+
87+
plt.xlabel("Index of element to be found")
88+
plt.ylabel(f"Time in seconds to find element {TIMEIT_TIMES:,} times")
89+
plt.title("Raw Time to Find First Match")
90+
plt.legend()
91+
92+
plt.show()
93+
94+
# Ratio
95+
96+
looping_ratio = [loop / loop for loop in looping_times]
97+
generator_ratio = [
98+
gen / loop for gen, loop in zip(generator_times, looping_times)
99+
]
100+
in_ratio = [in_ / loop for in_, loop in zip(in_times, looping_times)]
101+
102+
fig, ax = plt.subplots()
103+
104+
plot = ax.plot(positions, looping_ratio, label="loop")
105+
plot = ax.plot(positions, generator_ratio, label="generator")
106+
plot = ax.plot(positions, in_ratio, label="in")
107+
108+
plt.xlim([0, LIST_SIZE])
109+
plt.ylim([0, max(max(looping_ratio), max(generator_ratio), max(in_ratio))])
110+
plt.xlabel("Index of element to be found")
111+
plt.ylabel("Speed to find element, relative to loop")
112+
plt.title("Relative Speed to Find First Match")
113+
plt.legend()
114+
115+
plt.show()

0 commit comments

Comments
 (0)