///|
/// Build a Fibonacci dynamic-programming trace for `F(n)`.
pub fn fibonacci_dp_trace(
n : Int,
title? : String = "Fibonacci DP",
object_id? : String = "dp",
label? : String = "Fibonacci states",
options? : TraceOptions = TraceOptions::default(),
) -> AlgorithmTrace raise TraceError {
if n < 0 {
raise InvalidStep("Fibonacci index must be nonnegative")
}
if n > 46 {
raise LimitExceeded("Fibonacci index exceeds the safe Int range (0..46)")
}
let state_count = n + 1
let update_count = if n < 2 { 0 } else { n - 1 }
ensure_adapter_capacity(state_count + 1, update_count + 1, options)
let values = Array::make(state_count, "?")
values[0] = "0"
if n >= 1 {
values[1] = "1"
}
let builder = TraceBuilder::new(
title~,
algorithm="fibonacci-dp",
description="Compute each Fibonacci state from the previous two states.",
initial_scene=dp_sequence_scene(values, object_id, label, []),
options~,
)
let numbers = Array::make(state_count, 0)
if n >= 1 {
numbers[1] = 1
}
for i in 2..<=n {
numbers[i] = numbers[i - 1] + numbers[i - 2]
values[i] = numbers[i].to_string()
builder.record(
event=Update(dp_sequence_target(object_id, i), values[i]),
scene=dp_sequence_scene(values, object_id, label, [
Highlight::new(
target=dp_sequence_target(object_id, i - 2),
role=Compared,
),
Highlight::new(
target=dp_sequence_target(object_id, i - 1),
role=Compared,
),
Highlight::new(target=dp_sequence_target(object_id, i), role=Changed),
]),
annotation=Annotation::new(
title="Compute F(\{i})",
body="F(\{i}) = F(\{i - 1}) + F(\{i - 2}) = \{numbers[i]}.",
),
)
}
builder.record(
event=Complete,
scene=dp_sequence_scene(values, object_id, label, [
Highlight::new(target=dp_sequence_target(object_id, n), role=Result),
]),
annotation=Annotation::new(
title="Fibonacci result",
body="F(\{n}) = \{numbers[n]}.",
),
)
builder.finish(summary=[
TraceAttribute::new(key="n", value=n.to_string()),
TraceAttribute::new(key="states", value=state_count.to_string()),
TraceAttribute::new(key="updates", value=update_count.to_string()),
TraceAttribute::new(key="result", value=numbers[n].to_string()),
])
}
///|
/// Build a one-dimensional minimum-coin-change dynamic-programming trace.
pub fn coin_change_dp_trace(
coins : Array[Int],
amount : Int,
title? : String = "Coin Change DP",
object_id? : String = "dp",
label? : String = "Minimum coins",
options? : TraceOptions = TraceOptions::default(),
) -> AlgorithmTrace raise TraceError {
if amount < 0 {
raise InvalidStep("Coin-change amount must be nonnegative")
}
for coin in coins {
if coin <= 0 {
raise InvalidStep("Coin denominations must be positive")
}
}
let state_count = amount + 1
ensure_adapter_capacity(state_count + 1, amount + 1, options)
let unreachable_bound = amount + 1
let minimum = Array::make(state_count, unreachable_bound)
let values = Array::make(state_count, "?")
minimum[0] = 0
values[0] = "0"
let builder = TraceBuilder::new(
title~,
algorithm="coin-change-dp",
description="Choose the fewest coins for every amount from zero to the target.",
initial_scene=dp_sequence_scene(values, object_id, label, []),
options~,
)
for current in 1..<=amount {
let mut best = unreachable_bound
let mut predecessor = -1
let mut chosen_coin = -1
for coin in coins {
if coin <= current && minimum[current - coin] != unreachable_bound {
let candidate = minimum[current - coin] + 1
if candidate < best {
best = candidate
predecessor = current - coin
chosen_coin = coin
}
}
}
minimum[current] = best
values[current] = if best == unreachable_bound {
"∞"
} else {
best.to_string()
}
let highlights : Array[Highlight] = []
if predecessor >= 0 {
highlights.push(
Highlight::new(
target=dp_sequence_target(object_id, predecessor),
role=Compared,
),
)
}
highlights.push(
Highlight::new(
target=dp_sequence_target(object_id, current),
role=Changed,
),
)
builder.record(
event=Update(dp_sequence_target(object_id, current), values[current]),
scene=dp_sequence_scene(values, object_id, label, highlights),
annotation=Annotation::new(
title="Amount \{current}",
body=if chosen_coin < 0 {
"No denomination can reach amount \{current}."
} else {
"Use coin \{chosen_coin}; the minimum is \{best}."
},
),
)
}
builder.record(
event=Complete,
scene=dp_sequence_scene(values, object_id, label, [
Highlight::new(
target=dp_sequence_target(object_id, amount),
role=if minimum[amount] == unreachable_bound { Error } else { Result },
),
]),
annotation=Annotation::new(
title=if minimum[amount] == unreachable_bound {
"Amount unreachable"
} else {
"Minimum found"
},
body=if minimum[amount] == unreachable_bound {
"The target amount cannot be formed from the supplied denominations."
} else {
"The target amount needs \{minimum[amount]} coin(s)."
},
),
)
builder.finish(summary=[
TraceAttribute::new(key="amount", value=amount.to_string()),
TraceAttribute::new(
key="coins",
value=coins.map(fn(coin) { coin.to_string() }).join(","),
),
TraceAttribute::new(
key="reachable",
value=(minimum[amount] != unreachable_bound).to_string(),
),
TraceAttribute::new(
key="minimum_coins",
value=if minimum[amount] == unreachable_bound {
"unreachable"
} else {
minimum[amount].to_string()
},
),
])
}
///|
/// Build a two-dimensional 0/1-knapsack dynamic-programming trace.
pub fn zero_one_knapsack_dp_trace(
weights : Array[Int],
values : Array[Int],
capacity : Int,
title? : String = "0/1 背包动态规划",
object_id? : String = "dp",
label? : String = "背包状态表",
options? : TraceOptions = TraceOptions::default(),
) -> AlgorithmTrace raise TraceError {
if weights.length() != values.length() {
raise InvalidStep("Knapsack weights and values must have the same length")
}
if capacity < 0 {
raise InvalidStep("Knapsack capacity must be nonnegative")
}
for weight in weights {
if weight <= 0 {
raise InvalidStep("Knapsack weights must be positive")
}
}
for value in values {
if value < 0 {
raise InvalidStep("Knapsack values must be nonnegative")
}
}
let rows = weights.length() + 1
let columns = capacity + 1
let cells = checked_grid_cell_count(columns, rows, options)
let updates = weights.length() * capacity
ensure_adapter_capacity(cells + 1, updates + 1, options)
let table = Array::makei(rows, fn(_) { Array::make(columns, 0) })
let labels = Array::makei(rows, fn(row) {
Array::makei(columns, fn(column) {
if row == 0 || column == 0 {
"0"
} else {
"?"
}
})
})
let builder = TraceBuilder::new(
title~,
algorithm="zero-one-knapsack-dp",
description="逐件考虑物品,在每个容量下比较选择与跳过。",
initial_scene=dp_grid_scene(labels, object_id, label, []),
options~,
)
for row in 1.. excluded { included } else { excluded }
labels[row][column] = table[row][column].to_string()
let highlights : Array[Highlight] = [
Highlight::new(
target=dp_grid_target(object_id, column, row - 1),
role=Compared,
),
]
if weight <= column {
highlights.push(
Highlight::new(
target=dp_grid_target(object_id, column - weight, row - 1),
role=Compared,
),
)
}
highlights.push(
Highlight::new(
target=dp_grid_target(object_id, column, row),
role=Changed,
),
)
builder.record(
event=Update(
dp_grid_target(object_id, column, row),
labels[row][column],
),
scene=dp_grid_scene(labels, object_id, label, highlights),
annotation=Annotation::new(
title="第 \{row} 件物品,容量 \{column}",
body=if included > excluded {
"选择第 \{row} 件物品,当前最优价值更新为 \{included}。"
} else {
"跳过第 \{row} 件物品,当前最优价值保持为 \{excluded}。"
},
),
)
}
}
let result_row = rows - 1
builder.record(
event=Complete,
scene=dp_grid_scene(labels, object_id, label, [
Highlight::new(
target=dp_grid_target(object_id, capacity, result_row),
role=Result,
),
]),
annotation=Annotation::new(
title="背包最优解",
body="容量 \{capacity} 下的最大价值为 \{table[result_row][capacity]}。",
),
)
builder.finish(summary=[
TraceAttribute::new(key="items", value=weights.length().to_string()),
TraceAttribute::new(key="capacity", value=capacity.to_string()),
TraceAttribute::new(key="states", value=cells.to_string()),
TraceAttribute::new(key="updates", value=updates.to_string()),
TraceAttribute::new(
key="result",
value=table[result_row][capacity].to_string(),
),
])
}
///|
/// Build a two-dimensional longest-common-subsequence trace.
pub fn lcs_dp_trace(
left : String,
right : String,
title? : String = "Longest Common Subsequence DP",
object_id? : String = "dp",
label? : String = "LCS table",
options? : TraceOptions = TraceOptions::default(),
) -> AlgorithmTrace raise TraceError {
let left_chars = left.to_array()
let right_chars = right.to_array()
let rows = left_chars.length() + 1
let columns = right_chars.length() + 1
let cells = checked_grid_cell_count(columns, rows, options)
let updates = left_chars.length() * right_chars.length()
let maximum_backtrack = left_chars.length() + right_chars.length()
ensure_adapter_capacity(cells + 1, updates + maximum_backtrack + 1, options)
let table = Array::makei(rows, fn(_) { Array::make(columns, 0) })
let labels = Array::makei(rows, fn(row) {
Array::makei(columns, fn(column) {
if row == 0 || column == 0 {
"0"
} else {
"?"
}
})
})
let builder = TraceBuilder::new(
title~,
algorithm="longest-common-subsequence-dp",
description="Fill the LCS table, then backtrack a deterministic subsequence.",
initial_scene=dp_grid_scene(labels, object_id, label, []),
options~,
)
for row in 1..= table[row][column - 1] {
table[row - 1][column]
} else {
table[row][column - 1]
}
labels[row][column] = table[row][column].to_string()
let highlights : Array[Highlight] = []
if matches {
highlights.push(
Highlight::new(
target=dp_grid_target(object_id, column - 1, row - 1),
role=Compared,
),
)
} else {
highlights.push(
Highlight::new(
target=dp_grid_target(object_id, column, row - 1),
role=Compared,
),
)
highlights.push(
Highlight::new(
target=dp_grid_target(object_id, column - 1, row),
role=Compared,
),
)
}
highlights.push(
Highlight::new(
target=dp_grid_target(object_id, column, row),
role=Changed,
),
)
builder.record(
event=Update(
dp_grid_target(object_id, column, row),
labels[row][column],
),
scene=dp_grid_scene(labels, object_id, label, highlights),
annotation=Annotation::new(
title="LCS cell (\{row}, \{column})",
body=if matches {
"Characters match: \{left_chars[row - 1].to_string()}."
} else {
"Characters differ; keep the longer preceding subsequence."
},
),
)
}
}
let result_chars : Array[Char] = []
let result_cells : Array[(Int, Int)] = []
let mut row = left_chars.length()
let mut column = right_chars.length()
while row > 0 && column > 0 {
let current_row = row
let current_column = column
if left_chars[row - 1] == right_chars[column - 1] {
result_chars.push(left_chars[row - 1])
result_cells.push((column, row))
row -= 1
column -= 1
} else if table[row - 1][column] >= table[row][column - 1] {
row -= 1
} else {
column -= 1
}
let highlights = dp_result_cell_highlights(object_id, result_cells)
highlights.push(
Highlight::new(
target=dp_grid_target(object_id, current_column, current_row),
role=Current,
),
)
builder.record(
event=Visit(dp_grid_target(object_id, current_column, current_row)),
scene=dp_grid_scene(labels, object_id, label, highlights),
annotation=Annotation::new(
title="Backtrack LCS",
body="Follow the deterministic LCS reconstruction path.",
),
)
}
let sequence = String::from_array(result_chars.rev())
builder.record(
event=Complete,
scene=dp_grid_scene(
labels,
object_id,
label,
dp_result_cell_highlights(object_id, result_cells),
),
annotation=Annotation::new(
title="LCS complete",
body="The longest common subsequence is \{sequence}.",
),
)
builder.finish(summary=[
TraceAttribute::new(
key="left_length",
value=left_chars.length().to_string(),
),
TraceAttribute::new(
key="right_length",
value=right_chars.length().to_string(),
),
TraceAttribute::new(key="updates", value=updates.to_string()),
TraceAttribute::new(
key="length",
value=table[left_chars.length()][right_chars.length()].to_string(),
),
TraceAttribute::new(key="sequence", value=sequence),
])
}
///|
fn dp_sequence_scene(
values : Array[String],
object_id : String,
label : String,
highlights : Array[Highlight],
) -> Scene raise TraceError {
Scene::new(
objects=[
Sequence(
SequenceState::new(
id=object_id,
label~,
items=values.mapi(fn(index, value) {
SequenceItem::new(id="state-\{index}", value~)
}),
),
),
],
highlights~,
)
}
///|
fn dp_sequence_target(object_id : String, index : Int) -> TargetRef {
TargetRef::entity(object_id, "state-\{index}")
}
///|
fn dp_grid_scene(
labels : Array[Array[String]],
object_id : String,
label : String,
highlights : Array[Highlight],
) -> Scene raise TraceError {
let height = labels.length()
let width = if height == 0 { 0 } else { labels[0].length() }
let cells : Array[GridCellState] = []
for row in 0.. String {
"cell-\{column}-\{row}"
}
///|
fn dp_grid_target(object_id : String, column : Int, row : Int) -> TargetRef {
TargetRef::entity(object_id, dp_grid_cell_id(column, row))
}
///|
fn dp_result_cell_highlights(
object_id : String,
cells : Array[(Int, Int)],
) -> Array[Highlight] {
cells.map(fn(cell) {
Highlight::new(
target=dp_grid_target(object_id, cell.0, cell.1),
role=Result,
)
})
}
///|
fn checked_grid_cell_count(
width : Int,
height : Int,
options : TraceOptions,
) -> Int raise TraceError {
if width <= 0 || height <= 0 {
raise InvalidGrid("DP table dimensions must be positive")
}
let maximum_cells = options.max_entities_per_scene - 1
if maximum_cells < 0 || height > maximum_cells / width {
raise LimitExceeded("DP table exceeds the scene entity limit")
}
width * height
}
///|
fn ensure_adapter_capacity(
entities : Int,
steps : Int,
options : TraceOptions,
) -> Unit raise TraceError {
if entities > options.max_entities_per_scene {
raise LimitExceeded("Adapter scene entity limit exceeded")
}
if steps > options.max_steps {
raise LimitExceeded("Adapter trace step limit exceeded")
}
}