Grouping
Group Min-Max
Find the minimum and maximum value per category from parallel label and
value lists. On the first visit to a category the value seeds both bounds;
subsequent visits update lo or hi only when the new value falls outside
the current range. The trace shows lo and hi narrowing to their final
bounds before the result tuples are assembled.
By hand
Walk cats and vals together with zip. Keep two dicts, lo and hi.
On the first visit to a category, seed both with the current value. On every
visit run two comparisons: update lo[cat] if val is smaller, update
hi[cat] if it is larger. Build result from sorted keys.
naive.py
Replay: real traced execution (multi-file project)
cats = ['a', 'b', 'a', 'c', 'b', 'a', 'c', 'b']
vals = [2, 9, 4, 6, 3, 6, 10, 6]
lo = {}
hi = {}
for cat, val in zip(cats, vals):
if cat not in lo:
lo[cat] = val
hi[cat] = val
if val < lo[cat]:
lo[cat] = val
if val > hi[cat]:
hi[cat] = val
result = {k: (lo[k], hi[k]) for k in sorted(lo)}
print('RESULT:', result)
cats ← ['a', 'b', 'a', 'c', 'b', 'a', 'c', 'b']
1cats = ['a', 'b', 'a', 'c', 'b', 'a', 'c', 'b']2vals = [2, 9, 4, 6, 3, 6, 10, 6]values this step['a', 'b', 'a', 'c', 'b', 'a', 'c', 'b']catsvals ← [2, 9, 4, 6, 3, 6, 10, 6]
1cats = ['a', 'b', 'a', 'c', 'b', 'a', 'c', 'b']2vals = [2, 9, 4, 6, 3, 6, 10, 6]3lo = {}values this step[2, 9, 4, 6, 3, 6, 10, 6]valslo ← {}
2vals = [2, 9, 4, 6, 3, 6, 10, 6]3lo = {}4hi = {}values this step{}lohi ← {}
3lo = {}4hi = {}5for cat, val in zip(cats, vals):values this step{}hicat ← 'a', val ← 2, lo ← {'a': 2}, hi ← {'a': 2}
pass 1 of 24hi = {}5for cat, val in zip(cats, vals):6 if cat not in lo:7 lo[cat] = val8 hi[cat] = val9 if val < lo[cat]:10 lo[cat] = val11 if val > hi[cat]:12 hi[cat] = valvalues this step'a'cat2val{} → {'a': 2}lo{} → {'a': 2}hicat ← 'b', val ← 9, lo ← {'a': 2, 'b': 9}, hi ← {'a': 2, 'b': 9}
pass 2 of 24hi = {}5for cat, val in zip(cats, vals):6 if cat not in lo:7 lo[cat] = val8 hi[cat] = val9 if val < lo[cat]:10 lo[cat] = val11 if val > hi[cat]:12 hi[cat] = valvalues this step'a' → 'b'cat2 → 9val{'a': 2} → {'a': 2, 'b': 9}lo{'a': 2} → {'a': 2, 'b': 9}hicat ← 'a', val ← 4
4hi = {}5for cat, val in zip(cats, vals):6 if cat not in lo:values this step'b' → 'a'cat9 → 4valif cat not in lo:
5for cat, val in zip(cats, vals):6 if cat not in lo:7 lo[cat] = valif val < lo[cat]:
8 hi[cat] = val9if val < lo[cat]:10 lo[cat] = valif val > hi[cat]:
10 lo[cat] = val11if val > hi[cat]:12 hi[cat] = valhi ← {'a': 4, 'b': 9}
11 if val > hi[cat]:12 hi[cat] = val13result = {k: (lo[k], hi[k]) for k in sorted(lo)}values this step{'a': 2, 'b': 9} → {'a': 4, 'b': 9}hicat ← 'c', val ← 6
4hi = {}5for cat, val in zip(cats, vals):6 if cat not in lo:values this step'a' → 'c'cat4 → 6valif cat not in lo:
5for cat, val in zip(cats, vals):6 if cat not in lo:7 lo[cat] = vallo ← {'a': 2, 'b': 9, 'c': 6}
6if cat not in lo:7 lo[cat] = val8 hi[cat] = valvalues this step{'a': 2, 'b': 9} → {'a': 2, 'b': 9, 'c': 6}lohi ← {'a': 4, 'b': 9, 'c': 6}
7 lo[cat] = val8 hi[cat] = val9if val < lo[cat]:values this step{'a': 4, 'b': 9} → {'a': 4, 'b': 9, 'c': 6}hiif val < lo[cat]:
8 hi[cat] = val9if val < lo[cat]:10 lo[cat] = valif val > hi[cat]:
10 lo[cat] = val11if val > hi[cat]:12 hi[cat] = valcat ← 'b', val ← 3
4hi = {}5for cat, val in zip(cats, vals):6 if cat not in lo:values this step'c' → 'b'cat6 → 3valif cat not in lo:
5for cat, val in zip(cats, vals):6 if cat not in lo:7 lo[cat] = valif val < lo[cat]:
8 hi[cat] = val9if val < lo[cat]:10 lo[cat] = vallo ← {'a': 2, 'b': 3, 'c': 6}
9if val < lo[cat]:10 lo[cat] = val11if val > hi[cat]:values this step{'a': 2, 'b': 9, 'c': 6} → {'a': 2, 'b': 3, 'c': 6}loif val > hi[cat]:
10 lo[cat] = val11if val > hi[cat]:12 hi[cat] = valcat ← 'a', val ← 6, hi ← {'a': 6, 'b': 9, 'c': 6}
pass 1 of 24hi = {}5for cat, val in zip(cats, vals):6 if cat not in lo:7 lo[cat] = val8 hi[cat] = val9 if val < lo[cat]:10 lo[cat] = val11 if val > hi[cat]:12 hi[cat] = val13result = {k: (lo[k], hi[k]) for k in sorted(lo)}values this step'b' → 'a'cat3 → 6val{'a': 4, 'b': 9, 'c': 6} → {'a': 6, 'b': 9, 'c': 6}hicat ← 'c', val ← 10, hi ← {'a': 6, 'b': 9, 'c': 10}
pass 2 of 24hi = {}5for cat, val in zip(cats, vals):6 if cat not in lo:7 lo[cat] = val8 hi[cat] = val9 if val < lo[cat]:10 lo[cat] = val11 if val > hi[cat]:12 hi[cat] = val13result = {k: (lo[k], hi[k]) for k in sorted(lo)}values this step'a' → 'c'cat6 → 10val{'a': 6, 'b': 9, 'c': 6} → {'a': 6, 'b': 9, 'c': 10}hicat ← 'b', val ← 6
4hi = {}5for cat, val in zip(cats, vals):6 if cat not in lo:values this step'c' → 'b'cat10 → 6valif cat not in lo:
5for cat, val in zip(cats, vals):6 if cat not in lo:7 lo[cat] = valif val < lo[cat]:
8 hi[cat] = val9if val < lo[cat]:10 lo[cat] = valif val > hi[cat]:
10 lo[cat] = val11if val > hi[cat]:12 hi[cat] = valfor cat, val in zip(cats, vals):
4hi = {}5for cat, val in zip(cats, vals):6 if cat not in lo:result ← {'a': (2, 6), 'b': (3, 9), 'c': (6, 10)}
12 hi[cat] = val13result = {k: (lo[k], hi[k]) for k in sorted(lo)}14print('RESULT:', result)values this step{'a': (2, 6), 'b': (3, 9), 'c': (6, 10)}resultstdout ← RESULT: {'a': (2, 6), 'b': (3, 9), 'c': (6, 10)}
13result = {k: (lo[k], hi[k]) for k in sorted(lo)}14print('RESULT:', result)values this stepRESULT: {'a': (2, 6), 'b': (3, 9), 'c': (6, 10)}stdout
The Pythonic way
Collect all values per group into a defaultdict(list), then compute
min and max over each group list in a single dict comprehension.
library.py
from collections import defaultdict
cats = ['a', 'b', 'a', 'c', 'b', 'a', 'c', 'b']
vals = [2, 9, 4, 6, 3, 6, 10, 6]
groups = defaultdict(list)
for cat, val in zip(cats, vals):
groups[cat].append(val)
result = {k: (min(groups[k]), max(groups[k])) for k in sorted(groups)}
print('RESULT:', result)
RESULT: {'a': (2, 6), 'b': (3, 9), 'c': (6, 10)}
Implementation notes
- The seed
if cat not in lo:block fires only on the first occurrence of each key (three times for three categories); the comparison ifs fire on every iteration including the first, but are always false on the seed pass becauseval == lo[cat] == hi[cat]at that point. loandhistay as plain int-valued dicts throughout the loop so the trace shows concrete numbers at every step; the tuple is formed only in the final comprehension.resultis a dict (trackable), so the comprehension event shows the completed{'a': (2, 6), 'b': (3, 9), 'c': (6, 10)}in the trace even though the values are tuples.