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 replay shows lo and hi narrowing to their final bounds before the result tuples are assembled.

By hand

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.

naive.py
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)
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 because val == lo[cat] == hi[cat] at that point.
  • lo and hi stay as plain int-valued dicts throughout the loop so the replay shows concrete numbers at every step; the tuple is formed only in the final comprehension.
  • result is 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.