Files
agent_coding_eval/challenges.py
T
fn-registry agent f60da6fa6f chore: initial sync
2026-04-28 22:13:07 +02:00

893 lines
28 KiB
Python

"""
challenges.py — Challenges de programación de nivel production.
Inspirados en funciones reales del fn_registry. Prueban:
- Programación funcional (pipe, compose, curry, combinators)
- Procesamiento de datos (coercion, parsing, normalization)
- Algoritmos no triviales (árboles, grafos, scheduling)
- Patterns del mundo real (retry, rate limiting, schema validation)
"""
from eval_runner import Challenge
# ══════════════════════════════════════════════════════════════
# FUNCTIONAL PROGRAMMING
# ══════════════════════════════════════════════════════════════
FUNCTIONAL = [
Challenge(
id="fn_pipe",
name="Pipe with error propagation",
category="functional",
difficulty="medium",
prompt="""Write a Python function:
def pipe_safe(value, *fns):
\"\"\"Pipe a value through functions left-to-right. If any function raises,
return a tuple (None, error_string). On success return (result, None).\"\"\"
Example:
pipe_safe(5, lambda x: x*2, lambda x: x+1) == (11, None)
pipe_safe(0, lambda x: 10/x) == (None, "division by zero") # or similar
""",
test_code="""
# Success cases
assert pipe_safe(5, lambda x: x*2, lambda x: x+1) == (11, None)
assert pipe_safe("hello", str.upper, lambda s: s + "!") == ("HELLO!", None)
assert pipe_safe(42) == (42, None) # no functions
assert pipe_safe([3,1,2], sorted, lambda x: x[0]) == (1, None)
# Error propagation
result, err = pipe_safe(0, lambda x: 10/x)
assert result is None
assert err is not None and "division" in err.lower()
result, err = pipe_safe("abc", lambda x: x*2, int)
assert result is None
assert err is not None
print("PASS: pipe_safe")
""",
),
Challenge(
id="fn_group_by_multi",
name="Group by with transform",
category="functional",
difficulty="medium",
prompt="""Write a Python function:
def group_by_transform(xs: list, key_fn, value_fn=None) -> dict:
\"\"\"Group elements by key_fn. Optionally transform values with value_fn.
If value_fn is None, store raw elements. Preserves insertion order within groups.\"\"\"
Example:
group_by_transform(["hello", "hi", "bye"], lambda s: s[0])
# => {"h": ["hello", "hi"], "b": ["bye"]}
group_by_transform(["hello", "hi", "bye"], lambda s: s[0], str.upper)
# => {"h": ["HELLO", "HI"], "b": ["BYE"]}
""",
test_code="""
# Basic grouping
r = group_by_transform(["hello", "hi", "bye"], lambda s: s[0])
assert r == {"h": ["hello", "hi"], "b": ["bye"]}
# With value transform
r = group_by_transform(["hello", "hi", "bye"], lambda s: s[0], str.upper)
assert r == {"h": ["HELLO", "HI"], "b": ["BYE"]}
# Numbers
r = group_by_transform([1,2,3,4,5,6], lambda x: x % 2, lambda x: x**2)
assert r == {1: [1, 9, 25], 0: [4, 16, 36]}
# Empty
assert group_by_transform([], lambda x: x) == {}
# Single element
assert group_by_transform([42], lambda x: "k") == {"k": [42]}
print("PASS: group_by_transform")
""",
),
Challenge(
id="fn_memoize",
name="Memoize decorator with max size",
category="functional",
difficulty="hard",
prompt="""Write a Python function:
def memoize(max_size: int = 128):
\"\"\"Decorator that memoizes function results. When cache exceeds max_size,
evict the oldest entry (FIFO). The key is (args, tuple(sorted(kwargs.items()))).
Must work with both positional and keyword arguments.\"\"\"
Usage:
@memoize(max_size=3)
def add(a, b):
return a + b
""",
test_code="""
call_count = 0
@memoize(max_size=3)
def expensive(x, y=0):
global call_count
call_count += 1
return x + y
# First call — computes
call_count = 0
assert expensive(1, 2) == 3
assert call_count == 1
# Cached — no recompute
assert expensive(1, 2) == 3
assert call_count == 1
# Different args
assert expensive(3, 4) == 7
assert call_count == 2
# Kwargs
assert expensive(1, y=2) == 3
assert call_count == 2 # same as (1, 2) via kwargs
# Fill cache to max_size=3
assert expensive(10) == 10 # call 3
assert expensive(20) == 20 # call 4, evicts (1,2)
assert call_count == 4
# (1,2) was evicted, must recompute
assert expensive(1, 2) == 3
assert call_count == 5
print("PASS: memoize")
""",
max_tokens=1500,
),
Challenge(
id="fn_compose_async",
name="Partition with multiple predicates",
category="functional",
difficulty="medium",
prompt="""Write a Python function:
def multi_partition(xs: list, *predicates) -> list[list]:
\"\"\"Partition a list into N+1 buckets where N is the number of predicates.
Each element goes into the bucket of the FIRST predicate it satisfies.
Elements matching no predicate go into the last bucket.
Returns list of N+1 lists. Does not mutate input.\"\"\"
Example:
multi_partition([1,2,3,4,5,6,7,8,9,10],
lambda x: x % 3 == 0,
lambda x: x % 2 == 0)
# => [[3,6,9], [2,4,8,10], [1,5,7]]
# 6 goes to first bucket (div by 3) even though also div by 2
""",
test_code="""
# Basic
r = multi_partition([1,2,3,4,5,6,7,8,9,10], lambda x: x%3==0, lambda x: x%2==0)
assert r == [[3,6,9], [2,4,8,10], [1,5,7]], f"got {r}"
# No predicates — everything in remainder
assert multi_partition([1,2,3]) == [[1,2,3]]
# One predicate
r = multi_partition(["a","bb","ccc"], lambda s: len(s) > 1)
assert r == [["bb","ccc"], ["a"]]
# All match first
r = multi_partition([2,4,6], lambda x: x%2==0, lambda x: x>0)
assert r == [[2,4,6], [], []]
# Empty
r = multi_partition([], lambda x: True)
assert r == [[], []]
print("PASS: multi_partition")
""",
),
]
# ══════════════════════════════════════════════════════════════
# DATA PROCESSING
# ══════════════════════════════════════════════════════════════
DATA_PROCESSING = [
Challenge(
id="dp_coerce",
name="Type coercion with schema",
category="data_processing",
difficulty="hard",
prompt="""Write a Python function:
def coerce_types(data: dict, schema: dict[str, str]) -> tuple[dict, list[str]]:
\"\"\"Coerce dict values to types specified in schema. Never mutate original.
Schema maps field names to type strings: "int", "float", "str", "bool", "list[str]".
Rules:
- str → int: parse via float first (handle "3.0" → 3), warn if lossy ("3.7" → 3)
- str → float: standard float()
- str → bool: "true/1/yes" → True, "false/0/no" → False (case-insensitive)
- str → list[str]: split by "," and strip whitespace from each item
- Fields not in schema: pass through unchanged
- Fields in schema but not in data: skip
- Failed coercion: keep original value, add warning string to list
Returns (new_dict, warnings_list).\"\"\"
""",
test_code="""
# Basic coercions
d, w = coerce_types({"age": "25", "score": "3.14", "active": "yes"}, {"age": "int", "score": "float", "active": "bool"})
assert d == {"age": 25, "score": 3.14, "active": True}, f"got {d}"
assert w == []
# Lossy int coercion
d, w = coerce_types({"x": "3.7"}, {"x": "int"})
assert d["x"] == 3
assert len(w) == 1 and "lossy" in w[0].lower() or "3.7" in w[0]
# Bool variants
d, _ = coerce_types({"a": "TRUE", "b": "0", "c": "no"}, {"a": "bool", "b": "bool", "c": "bool"})
assert d == {"a": True, "b": False, "c": False}
# list[str]
d, _ = coerce_types({"tags": "a, b , c"}, {"tags": "list[str]"})
assert d == {"tags": ["a", "b", "c"]}
# Pass through unknown fields
d, _ = coerce_types({"name": "test", "age": "5"}, {"age": "int"})
assert d == {"name": "test", "age": 5}
# Failed coercion
d, w = coerce_types({"x": "not_a_number"}, {"x": "int"})
assert d["x"] == "not_a_number" # kept original
assert len(w) == 1
# No mutation
original = {"x": "5"}
d, _ = coerce_types(original, {"x": "int"})
assert original["x"] == "5"
assert d["x"] == 5
print("PASS: coerce_types")
""",
max_tokens=2048,
),
Challenge(
id="dp_frontmatter",
name="Extract YAML frontmatter",
category="data_processing",
difficulty="medium",
prompt="""Write a Python function:
def extract_frontmatter(content: str) -> tuple[str, dict | None]:
\"\"\"Extract YAML-like frontmatter delimited by '---' from start of markdown.
Frontmatter format:
---
key: value
another: something
---
Rest of content here.
Parse simple key:value pairs (no nested YAML needed). Values are always strings.
Do NOT use the yaml library.
Returns (content_without_frontmatter, parsed_dict_or_None).
If no frontmatter found, return (original_content, None).\"\"\"
""",
test_code="""
# Basic frontmatter
content = "---\\nname: test\\nversion: 1.0\\n---\\n\\nHello world"
body, meta = extract_frontmatter(content)
assert meta == {"name": "test", "version": "1.0"}, f"got {meta}"
assert body.strip() == "Hello world"
# No frontmatter
body, meta = extract_frontmatter("Just text")
assert meta is None
assert body == "Just text"
# Empty frontmatter
body, meta = extract_frontmatter("---\\n---\\nContent")
assert meta == {} or meta is not None
assert "Content" in body
# Values with colons
body, meta = extract_frontmatter("---\\nurl: http://example.com\\n---\\nBody")
assert meta["url"] == "http://example.com"
# Frontmatter must be at start
body, meta = extract_frontmatter("Some text\\n---\\nkey: val\\n---")
assert meta is None
print("PASS: extract_frontmatter")
""",
),
Challenge(
id="dp_json_extract",
name="Extract JSON from LLM response",
category="data_processing",
difficulty="hard",
prompt="""Write a Python function:
def extract_json_from_llm(content: str) -> dict:
\"\"\"Extract and parse JSON from messy LLM responses.
Must handle:
1. JSON inside ```json ... ``` code blocks
2. JSON inside ``` ... ``` blocks (no language tag)
3. Raw JSON with surrounding text
4. Trailing commas: {"a": 1,} or [1, 2,]
5. Python None instead of null
6. Single-quoted strings converted to double quotes
Returns parsed dict. Returns empty dict {} on failure.
Use only stdlib (json, re).\"\"\"
""",
test_code="""
import json
# Clean JSON block
assert extract_json_from_llm('```json\\n{"name": "test"}\\n```') == {"name": "test"}
# Block without language tag
assert extract_json_from_llm('```\\n{"x": 1}\\n```') == {"x": 1}
# JSON with surrounding text
r = extract_json_from_llm('Here is the result: {"count": 42} hope that helps!')
assert r == {"count": 42}
# Trailing commas
assert extract_json_from_llm('{"a": 1, "b": 2,}') == {"a": 1, "b": 2}
assert extract_json_from_llm('[1, 2, 3,]') == {} or extract_json_from_llm('{"items": [1,2,]}') == {"items": [1, 2]}
# Python None → null
assert extract_json_from_llm('{"value": None}') == {"value": None}
# Garbage input
assert extract_json_from_llm("no json here at all") == {}
assert extract_json_from_llm("") == {}
print("PASS: extract_json_from_llm")
""",
max_tokens=1500,
),
Challenge(
id="dp_smart_split",
name="Smart text splitter with token budget",
category="data_processing",
difficulty="hard",
prompt="""Write a Python function:
def smart_split(text: str, max_chars: int = 500, overlap: int = 50) -> list[str]:
\"\"\"Split text into chunks respecting max_chars with overlap between chunks.
Rules:
- Split at paragraph boundaries (double newline) when possible
- If a single paragraph exceeds max_chars, split at sentence boundaries (. ! ?)
- If a single sentence exceeds max_chars, hard-cut at max_chars
- Each chunk (except the first) starts with the last `overlap` characters of the previous chunk
- Strip leading/trailing whitespace from each chunk
- Never return empty chunks
Returns list of string chunks.\"\"\"
""",
test_code="""
# Simple paragraphs within budget
text = "First paragraph.\\n\\nSecond paragraph.\\n\\nThird paragraph."
chunks = smart_split(text, max_chars=100)
assert len(chunks) == 1
assert text.strip() in chunks[0]
# Force split between paragraphs
text = "A" * 100 + "\\n\\n" + "B" * 100
chunks = smart_split(text, max_chars=120, overlap=10)
assert len(chunks) >= 2
assert "A" * 100 in chunks[0]
assert "B" * 100 in chunks[-1]
# Overlap present
text = "Hello world this is text.\\n\\nAnother paragraph here."
chunks = smart_split(text, max_chars=30, overlap=5)
assert len(chunks) >= 2
for c in chunks:
assert len(c.strip()) > 0 # no empty chunks
# Very long single paragraph splits at sentence
text = "Short sentence. " * 50 # ~850 chars
chunks = smart_split(text, max_chars=200, overlap=20)
assert all(len(c) <= 220 for c in chunks) # max_chars + overlap tolerance
# Hard cut when no sentence boundary
text = "A" * 600
chunks = smart_split(text, max_chars=200, overlap=20)
assert len(chunks) >= 3
assert all(len(c) <= 220 for c in chunks)
# Empty/whitespace
assert smart_split("") == [] or smart_split("") == [""]
assert smart_split(" \\n\\n ") == [] or len(smart_split(" \\n\\n ")) <= 1
print("PASS: smart_split")
""",
max_tokens=2048,
),
]
# ══════════════════════════════════════════════════════════════
# ALGORITHMS
# ══════════════════════════════════════════════════════════════
ALGORITHMS = [
Challenge(
id="alg_topo_sort",
name="Topological sort with cycle detection",
category="algorithm",
difficulty="hard",
prompt="""Write a Python function:
def topo_sort(graph: dict[str, list[str]]) -> tuple[list[str], bool]:
\"\"\"Topological sort of a directed acyclic graph using Kahn's algorithm.
graph is adjacency list: {"a": ["b", "c"]} means a → b, a → c.
Nodes with no edges should also be included.
Returns (sorted_list, has_cycle).
- If no cycle: (topologically_sorted_nodes, False)
- If cycle detected: (partial_result, True)
When multiple valid orderings exist, prefer lexicographic order.\"\"\"
""",
test_code="""
# Simple DAG
order, cycle = topo_sort({"a": ["b", "c"], "b": ["d"], "c": ["d"], "d": []})
assert not cycle
assert order.index("a") < order.index("b")
assert order.index("a") < order.index("c")
assert order.index("b") < order.index("d")
assert order.index("c") < order.index("d")
# Lexicographic preference
order, cycle = topo_sort({"c": [], "b": [], "a": []})
assert not cycle
assert order == ["a", "b", "c"]
# Cycle detection
_, cycle = topo_sort({"a": ["b"], "b": ["c"], "c": ["a"]})
assert cycle
# Single node
order, cycle = topo_sort({"x": []})
assert order == ["x"]
assert not cycle
# Empty graph
order, cycle = topo_sort({})
assert order == []
assert not cycle
# Linear chain
order, cycle = topo_sort({"a": ["b"], "b": ["c"], "c": []})
assert order == ["a", "b", "c"]
assert not cycle
print("PASS: topo_sort")
""",
),
Challenge(
id="alg_interval_merge",
name="Interval scheduler with priorities",
category="algorithm",
difficulty="hard",
prompt="""Write a Python function:
def schedule_intervals(intervals: list[dict]) -> list[dict]:
\"\"\"Schedule non-overlapping intervals maximizing total priority.
Each interval is {"id": str, "start": int, "end": int, "priority": int}.
Intervals are half-open: [start, end). Two intervals [1,3) and [3,5) do NOT overlap.
Use weighted interval scheduling (dynamic programming).
Returns list of selected intervals sorted by start time.\"\"\"
""",
test_code="""
# Basic: pick higher priority
result = schedule_intervals([
{"id": "a", "start": 0, "end": 3, "priority": 2},
{"id": "b", "start": 1, "end": 4, "priority": 5},
{"id": "c", "start": 3, "end": 6, "priority": 3},
])
ids = [r["id"] for r in result]
assert "b" in ids # highest single priority
# b conflicts with a and c's start, so either [b] (5) or [a,c] (5) is valid
total = sum(r["priority"] for r in result)
assert total == 5, f"got total={total}"
# Non-overlapping, take all
result = schedule_intervals([
{"id": "a", "start": 0, "end": 2, "priority": 3},
{"id": "b", "start": 2, "end": 4, "priority": 3},
{"id": "c", "start": 4, "end": 6, "priority": 3},
])
assert len(result) == 3
assert sum(r["priority"] for r in result) == 9
# Empty
assert schedule_intervals([]) == []
# Single
result = schedule_intervals([{"id": "x", "start": 0, "end": 10, "priority": 7}])
assert len(result) == 1 and result[0]["id"] == "x"
# Prefer two small over one big
result = schedule_intervals([
{"id": "big", "start": 0, "end": 10, "priority": 5},
{"id": "s1", "start": 0, "end": 5, "priority": 3},
{"id": "s2", "start": 5, "end": 10, "priority": 3},
])
total = sum(r["priority"] for r in result)
assert total == 6 # s1 + s2 beats big
# Result sorted by start
for i in range(len(result) - 1):
assert result[i]["start"] <= result[i+1]["start"]
print("PASS: schedule_intervals")
""",
max_tokens=2048,
),
Challenge(
id="alg_tree_ops",
name="Tree operations suite",
category="algorithm",
difficulty="expert",
prompt="""Write three Python functions for tree manipulation:
1. def flatten_tree(tree: dict) -> list[dict]:
\"\"\"Flatten nested tree to list. Each node is a dict with optional 'children' key.
DFS pre-order. Remove 'children' key from output nodes. Deep copy nodes.\"\"\"
2. def find_path(tree: dict, target_id: str) -> list[str] | None:
\"\"\"Find path from root to node with given 'id' field. Returns list of ids
from root to target (inclusive), or None if not found.\"\"\"
3. def map_tree(tree: dict, fn) -> dict:
\"\"\"Apply fn to each node (excluding 'children' key), return new tree with
same structure. fn receives a dict without 'children' and returns a new dict.
Must not mutate original.\"\"\"
""",
test_code="""
import copy
tree = {
"id": "root", "name": "Root",
"children": [
{"id": "a", "name": "A", "children": [
{"id": "a1", "name": "A1"},
{"id": "a2", "name": "A2"},
]},
{"id": "b", "name": "B"},
]
}
original = copy.deepcopy(tree)
# flatten_tree
flat = flatten_tree(tree)
ids = [n["id"] for n in flat]
assert ids == ["root", "a", "a1", "a2", "b"], f"got {ids}"
assert all("children" not in n for n in flat)
assert tree == original # no mutation
# find_path
assert find_path(tree, "a2") == ["root", "a", "a2"]
assert find_path(tree, "root") == ["root"]
assert find_path(tree, "b") == ["root", "b"]
assert find_path(tree, "nonexistent") is None
# map_tree
result = map_tree(tree, lambda n: {**n, "name": n["name"].lower()})
assert result["name"] == "root"
assert result["children"][0]["name"] == "a"
assert result["children"][0]["children"][0]["name"] == "a1"
assert tree == original # no mutation
assert result["id"] == "root"
# Edge: leaf node
leaf = {"id": "solo", "val": 1}
flat = flatten_tree(leaf)
assert flat == [{"id": "solo", "val": 1}]
assert find_path(leaf, "solo") == ["solo"]
print("PASS: tree_ops")
""",
max_tokens=2048,
),
]
# ══════════════════════════════════════════════════════════════
# REAL-WORLD PATTERNS
# ══════════════════════════════════════════════════════════════
REAL_WORLD = [
Challenge(
id="rw_retry",
name="Retry with exponential backoff",
category="real_world",
difficulty="hard",
prompt="""Write a Python function:
def compute_backoff_delays(max_retries: int, base_delay: float = 1.0,
max_delay: float = 60.0, jitter: bool = False) -> list[float]:
\"\"\"Compute the sequence of backoff delays for retry logic.
Formula: delay = min(base_delay * 2^attempt, max_delay)
If jitter=True, multiply each delay by a factor between 0.5 and 1.0
(use deterministic half-jitter: factor = 0.75 for testability).
attempt starts at 0.
Returns list of `max_retries` delay values.\"\"\"
Also write:
def classify_error(status_code: int) -> str:
\"\"\"Classify HTTP status code for retry decisions.
Returns: 'permanent' (4xx except 429), 'transient' (5xx, 429, 408), or 'success' (2xx).
Any other code returns 'unknown'.\"\"\"
""",
test_code="""
# Basic exponential backoff
delays = compute_backoff_delays(5, base_delay=1.0, max_delay=60.0)
assert delays == [1.0, 2.0, 4.0, 8.0, 16.0], f"got {delays}"
# Capped at max_delay
delays = compute_backoff_delays(4, base_delay=10.0, max_delay=30.0)
assert delays == [10.0, 20.0, 30.0, 30.0], f"got {delays}"
# With jitter (deterministic 0.75 factor)
delays = compute_backoff_delays(3, base_delay=4.0, jitter=True)
assert delays == [3.0, 6.0, 12.0], f"got {delays}"
# Zero retries
assert compute_backoff_delays(0) == []
# Error classification
assert classify_error(200) == "success"
assert classify_error(201) == "success"
assert classify_error(400) == "permanent"
assert classify_error(403) == "permanent"
assert classify_error(404) == "permanent"
assert classify_error(429) == "transient" # rate limit
assert classify_error(408) == "transient" # timeout
assert classify_error(500) == "transient"
assert classify_error(503) == "transient"
assert classify_error(100) == "unknown"
assert classify_error(302) == "unknown"
print("PASS: retry_backoff")
""",
),
Challenge(
id="rw_schema_validate",
name="Schema validator for dicts",
category="real_world",
difficulty="expert",
prompt="""Write a Python function:
def validate(data: dict, schema: dict) -> list[str]:
\"\"\"Validate a dict against a schema. Return list of error strings (empty = valid).
Schema format — each key maps to a rule dict:
{
"field_name": {
"type": "str" | "int" | "float" | "bool" | "list" | "dict",
"required": True | False, # default False
"min": number, # minimum value (for int/float) or min length (for str/list)
"max": number, # maximum value or max length
"choices": [...], # allowed values
"pattern": "regex", # regex pattern (for str only)
}
}
Error messages should be descriptive: "field_name: expected type str, got int"
Check in order: required → type → min/max → choices → pattern.\"\"\"
""",
test_code="""
import re
schema = {
"name": {"type": "str", "required": True, "min": 1, "max": 50},
"age": {"type": "int", "required": True, "min": 0, "max": 150},
"email": {"type": "str", "pattern": r".+@.+\\..+"},
"role": {"type": "str", "choices": ["admin", "user", "guest"]},
"tags": {"type": "list", "max": 5},
}
# Valid data
errors = validate({"name": "Alice", "age": 30, "email": "a@b.com", "role": "admin", "tags": ["a"]}, schema)
assert errors == [], f"got {errors}"
# Missing required
errors = validate({"age": 25}, schema)
assert any("name" in e and "required" in e.lower() for e in errors), f"got {errors}"
# Wrong type
errors = validate({"name": 123, "age": 25}, schema)
assert any("name" in e and "type" in e.lower() for e in errors)
# Min/max violation
errors = validate({"name": "", "age": 25}, schema)
assert any("name" in e for e in errors) # min length 1
errors = validate({"name": "Bob", "age": -5}, schema)
assert any("age" in e for e in errors) # min 0
# Invalid choice
errors = validate({"name": "X", "age": 1, "role": "superuser"}, schema)
assert any("role" in e and "choices" in e.lower() for e in errors)
# Pattern mismatch
errors = validate({"name": "X", "age": 1, "email": "invalid"}, schema)
assert any("email" in e and "pattern" in e.lower() for e in errors)
# Extra fields ignored (no error)
errors = validate({"name": "X", "age": 1, "extra": "ok"}, schema)
assert not any("extra" in e for e in errors)
# Optional missing is fine
errors = validate({"name": "Test", "age": 50}, schema)
assert not any("email" in e for e in errors)
print("PASS: schema_validate")
""",
max_tokens=2500,
),
Challenge(
id="rw_rate_limiter",
name="Token bucket rate limiter",
category="real_world",
difficulty="expert",
prompt="""Write a Python class:
class TokenBucket:
\"\"\"Token bucket rate limiter (non-threaded, for testing).
Args:
capacity: Maximum tokens in bucket.
refill_rate: Tokens added per second.
Methods:
consume(tokens: int = 1, current_time: float = None) -> bool:
Try to consume tokens. Returns True if allowed, False if not enough tokens.
current_time is injectable for testing (defaults to time.time()).
Before checking, refill based on elapsed time since last refill.
tokens_available(current_time: float = None) -> float:
Return current token count after refill.
wait_time(tokens: int = 1, current_time: float = None) -> float:
Return seconds to wait before `tokens` would be available.
Returns 0.0 if tokens are already available.
\"\"\"
""",
test_code="""
# Basic usage
bucket = TokenBucket(capacity=10, refill_rate=1.0)
# Starts full
assert bucket.tokens_available(current_time=0) == 10
# Consume some
assert bucket.consume(3, current_time=0) == True
assert bucket.tokens_available(current_time=0) == 7
# Consume more than available
assert bucket.consume(8, current_time=0) == False
assert bucket.tokens_available(current_time=0) == 7 # unchanged
# Refill over time
assert bucket.tokens_available(current_time=2) == 9 # 7 + 2*1.0
# Consume after refill
assert bucket.consume(9, current_time=2) == True
assert bucket.tokens_available(current_time=2) == 0
# Don't exceed capacity
assert bucket.tokens_available(current_time=100) == 10 # capped at capacity
# Wait time
bucket2 = TokenBucket(capacity=5, refill_rate=2.0)
bucket2.consume(5, current_time=0)
assert bucket2.tokens_available(current_time=0) == 0
wt = bucket2.wait_time(4, current_time=0)
assert abs(wt - 2.0) < 0.01 # need 4 tokens at 2/s = 2s
# Already available
bucket3 = TokenBucket(capacity=10, refill_rate=1.0)
assert bucket3.wait_time(5, current_time=0) == 0.0
# Consume more than capacity
assert bucket3.consume(11, current_time=0) == False
print("PASS: token_bucket")
""",
max_tokens=1500,
),
Challenge(
id="rw_diff",
name="Simple line differ",
category="real_world",
difficulty="expert",
prompt="""Write a Python function:
def line_diff(old: str, new: str) -> list[str]:
\"\"\"Compute line-by-line diff between old and new text.
Returns list of diff lines:
- Lines only in old: prefixed with "- "
- Lines only in new: prefixed with "+ "
- Common lines: prefixed with " " (two spaces)
Use longest common subsequence (LCS) to produce minimal diff.
Split input on newlines. Empty string = no lines.\"\"\"
""",
test_code="""
# No changes
result = line_diff("a\\nb\\nc", "a\\nb\\nc")
assert result == [" a", " b", " c"]
# Addition
result = line_diff("a\\nc", "a\\nb\\nc")
assert result == [" a", "+ b", " c"], f"got {result}"
# Deletion
result = line_diff("a\\nb\\nc", "a\\nc")
assert result == [" a", "- b", " c"], f"got {result}"
# Replacement
result = line_diff("a\\nb\\nc", "a\\nX\\nc")
assert result == [" a", "- b", "+ X", " c"], f"got {result}"
# Complete change
result = line_diff("a\\nb", "c\\nd")
assert result == ["- a", "- b", "+ c", "+ d"]
# Empty inputs
assert line_diff("", "") == []
assert line_diff("a", "") == ["- a"]
assert line_diff("", "a") == ["+ a"]
# Multiple additions and deletions
result = line_diff("a\\nb\\nc\\nd", "a\\nc\\nd\\ne")
assert "- b" in result
assert "+ e" in result
assert " a" in result
assert " c" in result
assert " d" in result
print("PASS: line_diff")
""",
max_tokens=2048,
),
]
# ── Todos ─────────────────────────────────────────────────
ALL_CHALLENGES = FUNCTIONAL + DATA_PROCESSING + ALGORITHMS + REAL_WORLD