chore: initial sync
This commit is contained in:
+892
@@ -0,0 +1,892 @@
|
||||
"""
|
||||
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
|
||||
Reference in New Issue
Block a user