Your extract_campaign_names function just handed the CMO a clean list of channel labels. Now she has a harder question: which of those channels is delivering leads most cheaply? She wants them ranked — cheapest first.
We have the per-channel CPL figures from channel_summary. I could sort them in Excel by that column, but we've never sorted a list of dicts in Python.
You've sorted simple lists with sorted([3, 1, 2]). The jump to dicts is one argument: key=. You hand sorted() a function that pulls the sort value out of each item. A lambda is the cleanest way to write that inline — no separate def needed:
channels = [
{"channel": "social", "avg_cpl": 20.0},
{"channel": "email", "avg_cpl": 25.0},
{"channel": "paid_search", "avg_cpl": 238.5},
]
ranked = sorted(channels, key=lambda x: x["avg_cpl"])What does lambda x: x["avg_cpl"] actually say? I've seen lambda before but I always skipped it.
Read it as a one-line function with no name. lambda x: declares one parameter called x. Everything after the colon is the return value — here, x["avg_cpl"]. So sorted() calls that function on each dict, gets a float back, and uses it to order the list. It's identical to writing:
def get_cpl(x):
return x["avg_cpl"]
ranked = sorted(channels, key=get_cpl)So the lambda just saves me naming a function I'll only use once. And sorted() never mutates the original list — it hands back a new one?
Correct on both counts. sorted() is always non-destructive — your original list is untouched. If you wanted in-place, you'd call .sort() directly on the list. Two functions, one letter apart, very different contracts. CFOs have been surprised by that distinction.
I can already see how this wires into channel_summary — group, compute avg CPL per channel, then sort the result. The whole pipeline is three function calls.
That's exactly rank_channels_by_cpl. It owns the inline grouping, the per-channel aggregation, and the sort — so the caller never needs to know about the intermediate steps. One function, one ranked list, ready to drop into a slide or a Slack message.
sorted(iterable, key=fn) returns a new list sorted by the value fn(item) returns for each element. The original iterable is never modified.
| Form | When to use |
|---|---|
key=lambda x: x["avg_cpl"] | One-off, inline — no name needed |
key=get_cpl (named def) | Reused in multiple places |
Add reverse=True to sort descending (most expensive first):
sorted(channels, key=lambda x: x["avg_cpl"], reverse=True)Calling .sort() mutates the list in place and returns None. Assigning ranked = channels.sort() silently loses the data. Always use sorted() when you need the result as a value.
Your extract_campaign_names function just handed the CMO a clean list of channel labels. Now she has a harder question: which of those channels is delivering leads most cheaply? She wants them ranked — cheapest first.
We have the per-channel CPL figures from channel_summary. I could sort them in Excel by that column, but we've never sorted a list of dicts in Python.
You've sorted simple lists with sorted([3, 1, 2]). The jump to dicts is one argument: key=. You hand sorted() a function that pulls the sort value out of each item. A lambda is the cleanest way to write that inline — no separate def needed:
channels = [
{"channel": "social", "avg_cpl": 20.0},
{"channel": "email", "avg_cpl": 25.0},
{"channel": "paid_search", "avg_cpl": 238.5},
]
ranked = sorted(channels, key=lambda x: x["avg_cpl"])What does lambda x: x["avg_cpl"] actually say? I've seen lambda before but I always skipped it.
Read it as a one-line function with no name. lambda x: declares one parameter called x. Everything after the colon is the return value — here, x["avg_cpl"]. So sorted() calls that function on each dict, gets a float back, and uses it to order the list. It's identical to writing:
def get_cpl(x):
return x["avg_cpl"]
ranked = sorted(channels, key=get_cpl)So the lambda just saves me naming a function I'll only use once. And sorted() never mutates the original list — it hands back a new one?
Correct on both counts. sorted() is always non-destructive — your original list is untouched. If you wanted in-place, you'd call .sort() directly on the list. Two functions, one letter apart, very different contracts. CFOs have been surprised by that distinction.
I can already see how this wires into channel_summary — group, compute avg CPL per channel, then sort the result. The whole pipeline is three function calls.
That's exactly rank_channels_by_cpl. It owns the inline grouping, the per-channel aggregation, and the sort — so the caller never needs to know about the intermediate steps. One function, one ranked list, ready to drop into a slide or a Slack message.
sorted(iterable, key=fn) returns a new list sorted by the value fn(item) returns for each element. The original iterable is never modified.
| Form | When to use |
|---|---|
key=lambda x: x["avg_cpl"] | One-off, inline — no name needed |
key=get_cpl (named def) | Reused in multiple places |
Add reverse=True to sort descending (most expensive first):
sorted(channels, key=lambda x: x["avg_cpl"], reverse=True)Calling .sort() mutates the list in place and returns None. Assigning ranked = channels.sort() silently loses the data. Always use sorted() when you need the result as a value.
Isla's CMO wants to know which advertising channels deliver leads at the lowest cost. Write `rank_channels_by_cpl(campaigns)` that takes a list of campaign dicts (each with `"channel"`, `"spend"`, and `"leads"` keys), groups them by channel, computes average CPL per channel (guarding against zero leads with `0.0`), and returns a list of dicts — `{"channel", "avg_cpl", "total_leads"}` — sorted ascending by `avg_cpl`. For example, given two email campaigns totalling $2500 spend and 100 leads alongside a paid_search campaign at $12400 spend and 52 leads, email should rank first with `avg_cpl` 25.0.
Tap each step for scaffolded hints.
No blank-editor panic.