Prepstellar

Python Fundamentals · Built-in Data Structures

20 cards

Lists, Stacks, and Queues

Swipe, scroll or use ← →
  1. Add items to a list

    A list grows in three different ways, and picking the wrong one is a classic first-week bug: the value does arrive, but in the wrong shape or the wrong place. Decide first what you are adding — one value, the whole contents of another collection, or a value that must land at a known position.

    Call What it does
    append(value) Adds one item to the end of the list.
    extend(iterable) Appends every item from an iterable.
    insert(index, value) Places an item before the element at the given index.

    The difference between the first two is how the argument is read. append() treats it as a single object; extend() walks through it and adds each item it yields.

    1 / 20
  2. Add items to a list

    plan = ['wash']
    plan.append(['dry', 'fold'])   # ['wash', ['dry', 'fold']] -> one nested item
    plan = ['wash']
    plan.extend(['dry', 'fold'])   # ['wash', 'dry', 'fold']   -> two separate items
    plan.insert(0, 'sort')         # ['sort', 'wash', 'dry', 'fold']
    

    insert() is defined by the element it pushes aside: the new value goes before the element that currently sits at the index you name. So insert(0, value) puts the value at the front of the list, and inserting at the length of the list is the long way of writing append(value).

    2 / 20
  3. Quick check

    `plan` holds one task and `extra` holds two more tasks. Which call leaves `plan` holding the three tasks as separate items?

    1. A`plan.append(extra)`, which adds the pair as one item

      `append()` adds its argument as a single item, so the pair would sit inside the list as one nested element.

    2. B`plan.extend(extra)`, which adds each of its items

      Right. `extend()` consumes the iterable and appends every item it contains, giving three separate tasks.

    3. C`plan.insert(0, extra)`, which puts the pair at the front

      `insert()` also stores the argument as one item, and it places that item before the current first element.

    3 / 20

  4. Remove items by value or by position

    Removal splits along the same line as addition: do you know the value you want gone, or its position?

    Call Selects Gives back Failure
    remove(value) The first equal item Nothing ValueError when no item is equal
    pop(index) The item at that position The removed item IndexError when the index is outside the range
    pop() The last item The removed item IndexError when the list is empty
    clear() Every item Nothing

    Two details decide most real choices. remove(value) deletes the first equal item and leaves later matches in place, and pop(index) removes and returns the item at the given position — it is the only call here that hands the removed value back.

    4 / 20
  5. Remove items by value or by position

    names = ['ana', 'bruno', 'ana']
    names.remove('ana')   # ['bruno', 'ana'] -> only the first match went
    last = names.pop()    # last == 'ana', names == ['bruno']
    names.pop(7)          # IndexError: the index is outside the range
    names.clear()         # []
    

    Without an index, pop() removes and returns the last item; that default is what makes it feel like "take the newest one off". Both remove() and pop() fail loudly rather than quietly: a missing value raises ValueError, and an empty list or an out-of-range index raises IndexError.

    5 / 20
  6. Quick check

    `values.remove(target)` runs on a list where `target` appears three times. What happens?

    1. AThe first equal item is deleted and the later matches stay

      Right. One call deletes the first equal item, so the remaining matches are untouched.

    2. BEvery equal item is deleted and their former count is returned

      One call removes one item, and it does not report how many equal items existed.

    3. CThe position of the first match is reported instead

      Reporting a position is the job of `index()`; `remove()` changes the list instead.

    6 / 20

  7. Ask the list a question, then reorder it

    Before changing a list it often pays to ask it something. Two methods answer without touching the contents.

    Call Answer
    index(value) The zero-based index of the first matching item, or ValueError when there is no match
    index(value, start, stop) The same search limited by start and stop, but the returned index is still relative to the full sequence
    count(value) How many times the value occurs

    The limited search is the subtle one: narrowing the window changes where Python looks, not how it counts. A match found in a later window still reports its position in the whole list.

    7 / 20
  8. Ask the list a question, then reorder it

    Three more methods change or duplicate the list itself. sort() orders a list in place, while reverse() reverses it in place, and copy() returns a shallow copy of the list.

    Their return value is the trap. List methods that only mutate the list, such as insert(), remove() and sort(), return None.

    names = ['zoe', 'ana']
    result = names.sort()   # names == ['ana', 'zoe'], result is None
    

    So result = names.sort() sorts names in place and leaves result as None. Read the list itself after sorting it; when a separate sorted list is what you need, keep the original untouched and build a new one instead.

    8 / 20
  9. Quick check

    A program runs `result = names.sort()`. What is true afterwards?

    1. A`result` holds a sorted copy and `names` keeps its original order

      The method does not build a copy; it reorders the list it was called on.

    2. B`result` holds the removed last item and `names` is now shorter

      Removing an item and handing it back is what `pop()` does, not `sort()`.

    3. C`names` is sorted in place and `result` is `None`

      Right. `sort()` orders the list in place, and an in-place list method returns `None`.

    9 / 20

  10. Keep your progress in the app

    That’s 3 of 7 quick checks. In the app they stay answered, and every lesson remembers where you left off.

  11. Last-in, first-out work

    Some jobs must reach the newest item first. An undo history is the everyday case: the action you want to reverse is the one you just performed. That behavior has a name — a stack retrieves the last element added before earlier elements.

    A plain list is enough, as long as both operations stay at the same end:

    • Use append() to add an item to the top of a list-backed stack.
    • Use pop() without an index to retrieve the top item.
    10 / 20
  12. Last-in, first-out work

    history = []
    history.append('type')     # ['type']
    history.append('delete')   # ['type', 'delete']
    history.pop()              # 'delete' -> the newest action comes back first
    

    Keeping insertion and removal at the same end is the whole trick, and it is also why the pairing is fast: nothing else in the list has to move. Mixing ends breaks the order — adding at the front and popping from the back would return the oldest action first, which is the opposite of an undo.

    11 / 20
  13. Quick check

    An undo history adds each new action at one end and must retrieve the most recent action first. Which pair does that?

    1. AAdd with `append()` and retrieve with index-free `pop()`

      Right. Both operations stay at the end of the list, so the newest action is the one that comes back.

    2. BAdd with `append()` and retrieve with `pop(0)`

      Popping index zero takes the oldest action under this insertion pattern, which is queue order.

    3. CAdd with `insert(0, action)` and retrieve with index-free `pop()`

      Inserting at the front and popping at the back also returns an older action before the newest one.

    12 / 20

  14. First-in, first-out work

    Now flip the requirement. A ticket line, a print spooler and an arrivals log all need the opposite rule: a queue retrieves the first element added before later elements.

    A list can imitate a queue, but it is the wrong shape for the job. Appending to or popping from the end of a list is fast, but inserting or popping at its beginning is slow, because all the remaining elements have to shift one position.

    Structure Add arrival Take next Behavior
    List append() pop(0) Correct order, but every remaining element shifts
    List insert(0, item) pop() Correct order, and the shifting happens on the way in
    collections.deque append() popleft() Fast at both ends
    13 / 20
  15. First-in, first-out work

    For a queue, collections.deque provides fast appends and pops at both ends. A deque queue adds arrivals with append() and retrieves the earliest arrival with popleft().

    from collections import deque
    line = deque(['ana', 'bruno'])
    line.append('carla')   # deque(['ana', 'bruno', 'carla'])
    line.popleft()         # 'ana' -> the earliest arrival leaves first
    

    Same two verbs as the stack at one end, a different verb at the other — and that is the entire difference between serving the newest item and serving the oldest.

    14 / 20
  16. Quick check

    A service must handle arrivals in their original order, adding and removing constantly without shifting the remaining elements. Which design fits?

    1. AA list, adding with `insert(0, item)` and removing with `pop()`

      The order is right, but front insertion in a list is slow because every remaining element shifts.

    2. BA `collections.deque`, adding with `append()` and removing with `popleft()`

      Right. A deque is fast at both ends, and `popleft()` returns the earliest arrival.

    3. CA `collections.deque`, adding and removing with index-free `pop()`

      Adding and popping at the same end returns the newest arrival first, which is stack behavior.

    15 / 20

  17. Delete without retrieving

    Sometimes the removed value is of no interest at all — a row is wrong, a range of records is obsolete, a name is finished with. That is the del statement, and it works differently from every method above.

    • The del statement removes a list item by index rather than by value.
    • Unlike pop(), del does not return the removed value.
    • del can remove a slice of the list, or clear the whole list through a full slice.
    • del can also delete a variable name, and referring to that name is then an error until another value is assigned to it.
    16 / 20
  18. Delete without retrieving

    records = [0, 1, 2, 3, 4, 5, 6]
    del records[2]      # [0, 1, 3, 4, 5, 6]      -> by index, nothing returned
    del records[2:5]    # [0, 1, 6]               -> a whole slice at once
    del records[:]      # []                      -> the name still exists
    del records         # the name itself is gone
    records             # NameError until `records` is assigned again
    

    The distinctions are worth stating precisely, because three of them look alike. del values[2] removes a position and produces nothing; values.pop(2) removes the same position but returns the item; values.remove(2) ignores positions and hunts for the value 2. And a slice target such as del records[2:5] clears positions 2, 3 and 4 in one statement while the name records stays usable.

    17 / 20
  19. Quick check

    Positions 2 to 4 of `records` must go, the removed values are not needed, and the name must stay usable. Which statement fits?

    1. A`records.pop(2:5)`, ignoring the item it hands back and moving on

      `pop()` takes one position, not a slice, and it hands the removed item back.

    2. B`records.remove(2:5)`, then keep using the list

      `remove()` searches for a value equal to its argument and does not read a slice as positions.

    3. C`del records[2:5]`, which leaves the name in place

      Right. A slice target deletes those positions, returns nothing, and keeps the name bound.

    18 / 20

  20. Key takeaways

    • One item or many: append() adds a single item to the end, while extend() appends every item from an iterable.
    • Value or position: remove() deletes the first equal item, pop(index) removes and returns the item at a position, and index-free pop() takes the last one.
    • In-place methods return None: sort() and reverse() reorder the list itself, so read the list rather than the result.
    • Same end for a stack: append() plus index-free pop() gives last-in, first-out behavior.
    • Both ends for a queue: collections.deque with append() and popleft() gives first-in, first-out behavior without shifting elements.
    • del when the value is not needed: it removes by index or slice, returns nothing, and applied to a bare name it removes the name itself.
    19 / 20
  21. Quick check

    Which line correctly separates the three jobs of adding, retrieving and deleting?

    1. A`extend()` adds one item, `pop(0)` is fast on a long list, and `del` returns the value it removed

      `extend()` adds every item of an iterable, `pop(0)` shifts the rest of the list, and `del` produces no value.

    2. B`append()` adds one item, index-free `pop()` returns the last one, and `del values[0]` removes without returning

      Right. Those are the documented roles of `append()`, index-free `pop()` and an indexed `del`.

    3. C`insert()` returns the changed list, `popleft()` works on any list, and `remove()` deletes by position

      In-place methods return `None`, `popleft()` belongs to a deque, and `remove()` selects by value.

    20 / 20

  22. 7 quick checks · then the test

    In the app, finishing the quick checks opens this lesson’s 10-question test, and the ones you miss come back exactly when you’re about to forget them.

The whole course, on your phone

Lessons you can read, audio you can listen to on the way to work, and practice that remembers what you got wrong.