P2.2 — Lists & Collections¶
What you'll learn
- What an array is: an ordered list of values held under one name.
- That positions are counted from
0, and how to read one witharray[index]. - How to go through every item with a
forloop, add items, and ask how many there are. - What a dictionary is: a collection that looks values up by a name (key) instead of a position.
How it applies
- One name for many values. Tracking 50 enemies as 50 separate variables is hopeless; an array holds them all under one name, so the program can process "all of them" with a single loop.
- Counting from zero is the rule, and the trap. The first item is at index
0, so the last item of a 3-item list is at index2, not3. Reaching for the position "after the end" is the most common list error. - Loops and lists are partners. A
forloop over a list runs its body once per item — exactly how you apply the same step to every element (alert every enemy, total every price). - Dictionaries name their data. When values are not a sequence but labelled facts —
"hp","armor","name"— a dictionary looks them up by key, which reads far better than remembering which position held what.
Concepts¶
Arrays: an ordered list¶
An array is an ordered list of values stored under one name, written in square brackets:
items holds three values in order. You read one by its position, called its index, in
square brackets — and positions start at 0:
print(items[0]) # sword — the FIRST item is index 0
print(items[1]) # shield
print(items[2]) # potion — the third (and last) item is index 2
Counting from zero is the single most important thing here. In a three-item list, the valid indexes
are 0, 1, 2. There is no index 3 — asking for items[3] is reaching past the end, an error.
The last valid index is always one less than the number of items.
How many, and adding more¶
size() tells you how many items the array holds; append(...) adds one to the end:
print(items.size()) # 3
items.append("torch") # now ["sword", "shield", "potion", "torch"]
print(items.size()) # 4
Because indexes start at 0, a list of size() 4 has valid indexes 0 through 3 — the largest
index is size() - 1. Keeping that relationship straight is what avoids off-by-one mistakes when you
index a list.
Going through every item with for¶
The natural partner of a list is a loop. A for loop can walk a list directly, giving you each item
in turn:
Here item takes the value of each element, one per pass — no index needed. This is how you apply the
same action to every element: total a list of prices, alert every enemy, draw every tile. (You can
still loop by index with for i in range(items.size()) when you need the position itself.)
Dictionaries: look up by name¶
Some collections are not an ordered sequence but a set of labelled values. A dictionary maps a key (a name) to a value, written in curly braces:
var stats := {"hp": 30, "armor": 5}
print(stats["hp"]) # 30 — look up by key, not position
stats["mana"] = 10 # add or change a key
print(stats["armor"]) # 5
You reach a value by its key (stats["hp"]) rather than a numeric position. Use an array when
order and "all of them" matter (a list of items to loop over); use a dictionary when each value has
a meaningful name (the hp, the armor, the level).
Example
Lists plus loops, doing real work — totalling prices:
var prices := [10, 25, 5, 50]
var total := 0
for price in prices: # price is 10, then 25, then 5, then 50
total = total + price # runs once per item, accumulating
print(total) # 90
The loop visits each of the four prices and adds it to total, which ends at 90. Notice you never
wrote an index — for price in prices hands you the values directly. The same four-line shape sums
any list of any length, which is the power of pairing a list with a loop: write the per-item step
once, and it applies to every item, however many there are.
Walkthrough¶
Use your P1.1 script setup.
- Make
var items := ["sword", "shield", "potion"]. Printitems[0]anditems[2]. Then printitems[3]and read the error — there is no index 3 in a three-item list. - Print
items.size()(3).appenda fourth item, printsize()again (4), and now printitems[3]successfully. Note how the largest valid index grew tosize() - 1. - Loop over the list with
for item in items: print(item)and confirm every item prints in order. - Make
var prices := [10, 25, 5, 50]and total them with a loop (as in the example); confirm90. Then make a dictionaryvar stats := {"hp": 30, "armor": 5}, printstats["hp"], add a"mana"key, and print it.
Optional sanity check
With var a := [10, 20, 30], predict what a[a.size()] does before running. a.size() is 3, and
there is no index 3 (valid are 0,1,2), so it errors — reaching one past the end. The last item
is a[a.size() - 1], i.e. a[2]. That size() versus size() - 1 gap is the list off-by-one.
Self-check quiz¶
Q1 — In var x := [\"a\", \"b\", \"c\"], what is x[0], and what is the last valid index?
A. x[0] is \"b\"; the last index is 3.
B. x[0] is \"a\"; the last valid index is 2 (positions start at 0).
C. x[0] is the size; the last index is 3.
D. x[0] errors; indexing starts at 1.
Reveal answer
B. Indexes start at 0, so x[0] is the first item "a", and a three-item list has valid
indexes 0, 1, 2 — the last is 2, one less than the size. A starts at the wrong position. C
and D assume 1-based indexing, which this is not.
Q2 — How do you perform the same action on every item of a list things?
A. Write the action once for each item by hand.
B. for item in things: with the action in the loop body, so it runs once per item.
C. things.size() performs the action.
D. You cannot; lists are read-only.
Reveal answer
B. A for loop over the list runs its body once per element, applying the same step to each
— the list-and-loop partnership. A defeats the purpose (and fails for unknown lengths). C only
counts items. D is false — lists are not read-only.
Q3 — When is a dictionary a better fit than an array?
A. Always; arrays are obsolete. B. When each value has a meaningful name/key (hp, armor) and you look it up by that name rather than by position. C. Only for numbers. D. When you need the values in order.
Reveal answer
B. A dictionary maps keys to values, ideal when data is labelled ("hp", "armor") and
accessed by name. A is false. C is wrong (keys and values can be many types). D describes when an
array (ordered) is the better fit.
Integration question¶
Q4 — open
You have a list of enemy health values healths := [10, 5, 20, 8] and want to (a) print each
enemy's health, and (b) count how many enemies have more than 9 health. Sketch the code combining a
loop, indexing/iteration, and a decision (P1.5), and explain why you would iterate with
for h in healths rather than for i in range(healths.size()) here — then name the off-by-one
mistake that range version would invite.
Reveal expected answer
Iterate the list and use a counter with an if:
var healthy := 0
for h in healths:
print(h) # (a) print each value
if h > 9: # (b) decision per item
healthy = healthy + 1
print(healthy) # 2 (10 and 20 exceed 9)
The loop body runs once per enemy (four times), printing each health and, when the value exceeds
9, incrementing healthy; the if is inside the loop so the decision is made per item, and
healthy (declared outside) survives across iterations to hold the running count, ending at
2. Why for h in healths: it hands you each value directly, with no index arithmetic, so
there is nothing to get wrong — cleaner and safer when you only need the values. The range
off-by-one it avoids: for i in range(healths.size()) is correct only with range(healths.size())
giving 0..size()-1; the tempting mistakes are range(healths.size() + 1) (runs one pass too
many and indexes one past the end → error) or starting/stopping at the wrong number. Since the
last valid index is size() - 1, the index-based loop is exactly where the list off-by-one
bites, and iterating by value sidesteps it.