···11+// Folder-specific settings
22+//
33+// For a full list of overridable settings, and general information on folder-specific settings,
44+// see the documentation: https://zed.dev/docs/configuring-zed#settings-files
55+{
66+ "languages": {
77+ "Erlang": {
88+ "format_on_save": "off"
99+ }
1010+ }
1111+}
···11+#!/usr/bin/env python3
22+"""
33+Implementation of CREATE-CONCAT-PLAN algorithm from RRB-Vector paper.
44+This shows how nodes should be rebalanced during concatenation.
55+"""
66+77+def create_concat_plan(T, M=16, emax=2):
88+ """
99+ Create a rebalancing plan for concatenating nodes.
1010+1111+ Args:
1212+ T: List of node sizes (number of elements/children in each node)
1313+ M: Branching factor (default 16 for Erlang)
1414+ emax: Maximum extra search steps (default 2)
1515+1616+ Returns:
1717+ (c, n) where:
1818+ c: List of rebalanced node sizes
1919+ n: Number of nodes after rebalancing
2020+ """
2121+ # Line 2: Create array to hold node sizes
2222+ c = [0] * len(T)
2323+2424+ # Line 3-7: Initialize c with input sizes and calculate total
2525+ S = 0
2626+ for i in range(len(T)):
2727+ c[i] = T[i]
2828+ S += T[i]
2929+3030+ # Line 8: Calculate optimal number of nodes
3131+ T_opt = (S + M - 1) // M # Ceiling division
3232+3333+ # Line 9: Current number of nodes
3434+ n = len(T)
3535+3636+ # Line 10: Start from first node
3737+ i = 0
3838+3939+ # Line 11: While we have too many nodes compared to optimal
4040+ print(f"Initial: T_opt={T_opt}, emax={emax}, n={n}")
4141+ print(f"Condition: T_opt + emax < n → {T_opt} + {emax} < {n} → {T_opt + emax < n}")
4242+ print(f"Initial sizes: {c[:n]}")
4343+ print()
4444+4545+ iteration = 0
4646+ while T_opt + emax < n:
4747+ iteration += 1
4848+ print(f"--- Iteration {iteration} ---")
4949+ print(f"Starting at i={i}, n={n}")
5050+5151+ # Line 12-14: Find first node that's too small
5252+ while c[i] >= M - emax // 2:
5353+ print(f" c[{i}]={c[i]} >= {M - emax//2}, skipping")
5454+ i += 1
5555+5656+ print(f" Found small node at i={i}: c[{i}]={c[i]}")
5757+5858+ # Line 15: Remainder to distribute
5959+ r = c[i]
6060+ print(f" r={r} (will distribute this over remaining nodes)")
6161+6262+ # Line 16-21: Redistribute r over following nodes
6363+ dist_start = i
6464+ while r > 0:
6565+ print(f" i={i}, r={r}, c[{i+1}]={c[i+1] if i+1 < n else 'N/A'}")
6666+ MIN_SIZE = min(r + c[i + 1], M)
6767+ c[i] = MIN_SIZE
6868+ r = r + c[i + 1] - c[i]
6969+ print(f" → c[{i}]={c[i]}, new r={r}")
7070+ i += 1
7171+7272+ print(f" After redistribution from {dist_start}: {c[dist_start:i]}")
7373+7474+ # Line 22-25: Shift remaining nodes left
7575+ print(f" Shifting nodes {i}..{n-1} left by 1")
7676+ for j in range(i, n - 1):
7777+ c[j] = c[j + 1]
7878+7979+ # Line 26-27: Adjust position and count
8080+ i -= 1
8181+ n -= 1
8282+8383+ print(f" After shift: n={n}, i={i}")
8484+ print(f" Current sizes: {c[:n]}")
8585+ print(f" Check: T_opt + emax < n → {T_opt} + {emax} < {n} → {T_opt + emax < n}")
8686+ print()
8787+8888+ # Line 29: Return rebalanced plan
8989+ return c[:n], n
9090+9191+9292+if __name__ == "__main__":
9393+ print("=" * 60)
9494+ print("Testing CREATE-CONCAT-PLAN with [14, 3, 14]")
9595+ print("=" * 60)
9696+ print()
9797+9898+ # Test case from the issue
9999+ T = [14, 3, 14]
100100+ M = 16
101101+ emax = 2
102102+103103+ print(f"Input: {T}")
104104+ print(f"M (branch factor): {M}")
105105+ print(f"emax: {emax}")
106106+ print()
107107+108108+ result, n = create_concat_plan(T, M, emax)
109109+110110+ print("=" * 60)
111111+ print("RESULT")
112112+ print("=" * 60)
113113+ print(f"Original: {T} (sum={sum(T)})")
114114+ print(f"Rebalanced: {result} (sum={sum(result)})")
115115+ print(f"Number of nodes: {len(T)} → {n}")
116116+ print()
117117+118118+ # Verify
119119+ assert sum(result) == sum(T), "Total elements changed!"
120120+ print("✓ Total elements preserved")
121121+122122+ # Check if all nodes satisfy minimum size
123123+ min_size = M - emax
124124+ print(f"\nChecking minimum size constraint (>= {min_size}):")
125125+ for i, size in enumerate(result):
126126+ status = "✓" if size >= min_size else "✗"
127127+ print(f" Node {i}: {size} {status}")
128128+129129+ print("\n" + "=" * 60)
130130+ print("Testing with other cases")
131131+ print("=" * 60)
132132+133133+ test_cases = [
134134+ [14, 14],
135135+ [14, 14, 14, 14],
136136+ [10, 10, 10],
137137+ [16, 3],
138138+ ]
139139+140140+ for T in test_cases:
141141+ print(f"\nInput: {T}")
142142+ result, n = create_concat_plan(T, M, emax)
143143+ print(f"Result: {result}")
144144+ print()
+72
src/iv.gleam
···108108//// [concat_list](#concat_list "Concatenate many arrays"),
109109//// [flatten](#flatten "Concatenate nested arrays"),
110110//// [split](#split "Split an array at an index"),
111111+//// [split_n](#split_n "N-way split")
111112//// [slice](#slice "Get a slice of the array"),
112113//// [slice_clamped](#slice_clamped "Get a slice of the array"),
113114//// [drop_first](#drop_first "Remove the first elements"),
114115//// [drop_last](#drop_last "Remove the last elements"),
115116//// [take_first](#take_first "Take the first elements"),
116117//// [take_last](#take_last "Take the last elements"),
118118+//// [sized_chunk](#sized_chunk "Split an array into chunks")
117119////
118120//// #### Transform
119121//// [reverse](#reverse "Reverse the order"),
···136138//// [fold_right](#fold_right "Loop end to start, with state"),
137139//// [index_fold_right](#index_fold_right "Loop end to start, with index and state")
138140141141+import gleam/int
139142import gleam/list
140143import gleam/yielder.{type Yielder}
141144import iv/internal/builder
···17391742/// </div>
17401743pub fn take_last(from array: Array(item), up_to n: Int) -> Array(item) {
17411744 drop_first(array, size(array) - n)
17451745+}
17461746+17471747+/// Returns an array of chunks containing `count` elements each.
17481748+///
17491749+/// If the last chunk does not have count elements, it is instead a partial
17501750+/// chunk, with less than count elements.
17511751+///
17521752+/// For any count less than 1 this function behaves as if it was set to 1.
17531753+///
17541754+/// <div style="text-align: right;">
17551755+/// <a href="#">
17561756+/// <small>Back to top ↑</small>
17571757+/// </a>
17581758+/// </div>
17591759+pub fn sized_chunk(in array: Array(item), into count: Int) -> Array(Array(item)) {
17601760+ sized_chunk_loop(array, int.min(1, count), [])
17611761+}
17621762+17631763+fn sized_chunk_loop(
17641764+ array: Array(item),
17651765+ count: Int,
17661766+ chunks: List(Array(item)),
17671767+) -> Array(Array(item)) {
17681768+ let size = size(array)
17691769+ case size <= count {
17701770+ True ->
17711771+ case size {
17721772+ 0 -> from_reverse_list(chunks)
17731773+ _ -> from_reverse_list([array, ..chunks])
17741774+ }
17751775+ False -> {
17761776+ let #(chunk, rest) = split(array, count)
17771777+ sized_chunk_loop(rest, count, [chunk, ..chunks])
17781778+ }
17791779+ }
17801780+}
17811781+17821782+/// Returns an array distributing its elements evenly into `n` chunks.
17831783+///
17841784+/// If there are less than `n` elements in the array, less chunks may be
17851785+/// returned.
17861786+///
17871787+/// For any count less than 1 this function behaves as if it was set to 1.
17881788+///
17891789+/// <div style="text-align: right;">
17901790+/// <a href="#">
17911791+/// <small>Back to top ↑</small>
17921792+/// </a>
17931793+/// </div>
17941794+pub fn split_n(in array: Array(item), into n_chunks: Int) -> Array(Array(item)) {
17951795+ split_n_loop(array, size(array) / n_chunks, size(array) % n_chunks, [])
17961796+}
17971797+17981798+fn split_n_loop(array, count, rest, chunks) {
17991799+ let size = size(array)
18001800+ case size <= count {
18011801+ True ->
18021802+ case size {
18031803+ 0 -> from_reverse_list(chunks)
18041804+ _ -> from_reverse_list([array, ..chunks])
18051805+ }
18061806+ False -> {
18071807+ let #(chunk, array) = case rest > 0 {
18081808+ False -> split(array, count)
18091809+ True -> split(array, count + 1)
18101810+ }
18111811+ split_n_loop(array, count, rest - 1, [chunk, ..chunks])
18121812+ }
18131813+ }
17421814}
1743181517441816/// Return the array without the first element. If the array is empty,