a fast, general-purpose, persistent array structure for Gleam.
29

Configure Feed

Select the types of activity you want to include in your feed.

split_n/sized_chunk

yoshie (Dec 20, 2025, 5:44 PM +0100) 687a6174 f74cc10c

+229 -2
+11
.zed/settings.json
··· 1 + // Folder-specific settings 2 + // 3 + // For a full list of overridable settings, and general information on folder-specific settings, 4 + // see the documentation: https://zed.dev/docs/configuring-zed#settings-files 5 + { 6 + "languages": { 7 + "Erlang": { 8 + "format_on_save": "off" 9 + } 10 + } 11 + }
+2 -2
TODO
··· 1 1 [x] `swap_delete` 2 - [ ] `n_chunks` 3 - [ ] `sized_chunks` 2 + [x] `n_chunks` / split_n 3 + [x] `sized_chunks` 4 4 [x] deprecate `length` for `size` 5 5 [-] `reduce` 6 6 [x] deprecate first/last rest/leading
+144
create_concat_plan.py
··· 1 + #!/usr/bin/env python3 2 + """ 3 + Implementation of CREATE-CONCAT-PLAN algorithm from RRB-Vector paper. 4 + This shows how nodes should be rebalanced during concatenation. 5 + """ 6 + 7 + def create_concat_plan(T, M=16, emax=2): 8 + """ 9 + Create a rebalancing plan for concatenating nodes. 10 + 11 + Args: 12 + T: List of node sizes (number of elements/children in each node) 13 + M: Branching factor (default 16 for Erlang) 14 + emax: Maximum extra search steps (default 2) 15 + 16 + Returns: 17 + (c, n) where: 18 + c: List of rebalanced node sizes 19 + n: Number of nodes after rebalancing 20 + """ 21 + # Line 2: Create array to hold node sizes 22 + c = [0] * len(T) 23 + 24 + # Line 3-7: Initialize c with input sizes and calculate total 25 + S = 0 26 + for i in range(len(T)): 27 + c[i] = T[i] 28 + S += T[i] 29 + 30 + # Line 8: Calculate optimal number of nodes 31 + T_opt = (S + M - 1) // M # Ceiling division 32 + 33 + # Line 9: Current number of nodes 34 + n = len(T) 35 + 36 + # Line 10: Start from first node 37 + i = 0 38 + 39 + # Line 11: While we have too many nodes compared to optimal 40 + print(f"Initial: T_opt={T_opt}, emax={emax}, n={n}") 41 + print(f"Condition: T_opt + emax < n → {T_opt} + {emax} < {n} → {T_opt + emax < n}") 42 + print(f"Initial sizes: {c[:n]}") 43 + print() 44 + 45 + iteration = 0 46 + while T_opt + emax < n: 47 + iteration += 1 48 + print(f"--- Iteration {iteration} ---") 49 + print(f"Starting at i={i}, n={n}") 50 + 51 + # Line 12-14: Find first node that's too small 52 + while c[i] >= M - emax // 2: 53 + print(f" c[{i}]={c[i]} >= {M - emax//2}, skipping") 54 + i += 1 55 + 56 + print(f" Found small node at i={i}: c[{i}]={c[i]}") 57 + 58 + # Line 15: Remainder to distribute 59 + r = c[i] 60 + print(f" r={r} (will distribute this over remaining nodes)") 61 + 62 + # Line 16-21: Redistribute r over following nodes 63 + dist_start = i 64 + while r > 0: 65 + print(f" i={i}, r={r}, c[{i+1}]={c[i+1] if i+1 < n else 'N/A'}") 66 + MIN_SIZE = min(r + c[i + 1], M) 67 + c[i] = MIN_SIZE 68 + r = r + c[i + 1] - c[i] 69 + print(f" → c[{i}]={c[i]}, new r={r}") 70 + i += 1 71 + 72 + print(f" After redistribution from {dist_start}: {c[dist_start:i]}") 73 + 74 + # Line 22-25: Shift remaining nodes left 75 + print(f" Shifting nodes {i}..{n-1} left by 1") 76 + for j in range(i, n - 1): 77 + c[j] = c[j + 1] 78 + 79 + # Line 26-27: Adjust position and count 80 + i -= 1 81 + n -= 1 82 + 83 + print(f" After shift: n={n}, i={i}") 84 + print(f" Current sizes: {c[:n]}") 85 + print(f" Check: T_opt + emax < n → {T_opt} + {emax} < {n} → {T_opt + emax < n}") 86 + print() 87 + 88 + # Line 29: Return rebalanced plan 89 + return c[:n], n 90 + 91 + 92 + if __name__ == "__main__": 93 + print("=" * 60) 94 + print("Testing CREATE-CONCAT-PLAN with [14, 3, 14]") 95 + print("=" * 60) 96 + print() 97 + 98 + # Test case from the issue 99 + T = [14, 3, 14] 100 + M = 16 101 + emax = 2 102 + 103 + print(f"Input: {T}") 104 + print(f"M (branch factor): {M}") 105 + print(f"emax: {emax}") 106 + print() 107 + 108 + result, n = create_concat_plan(T, M, emax) 109 + 110 + print("=" * 60) 111 + print("RESULT") 112 + print("=" * 60) 113 + print(f"Original: {T} (sum={sum(T)})") 114 + print(f"Rebalanced: {result} (sum={sum(result)})") 115 + print(f"Number of nodes: {len(T)} → {n}") 116 + print() 117 + 118 + # Verify 119 + assert sum(result) == sum(T), "Total elements changed!" 120 + print("✓ Total elements preserved") 121 + 122 + # Check if all nodes satisfy minimum size 123 + min_size = M - emax 124 + print(f"\nChecking minimum size constraint (>= {min_size}):") 125 + for i, size in enumerate(result): 126 + status = "✓" if size >= min_size else "✗" 127 + print(f" Node {i}: {size} {status}") 128 + 129 + print("\n" + "=" * 60) 130 + print("Testing with other cases") 131 + print("=" * 60) 132 + 133 + test_cases = [ 134 + [14, 14], 135 + [14, 14, 14, 14], 136 + [10, 10, 10], 137 + [16, 3], 138 + ] 139 + 140 + for T in test_cases: 141 + print(f"\nInput: {T}") 142 + result, n = create_concat_plan(T, M, emax) 143 + print(f"Result: {result}") 144 + print()
+72
src/iv.gleam
··· 108 108 //// [concat_list](#concat_list "Concatenate many arrays"), 109 109 //// [flatten](#flatten "Concatenate nested arrays"), 110 110 //// [split](#split "Split an array at an index"), 111 + //// [split_n](#split_n "N-way split") 111 112 //// [slice](#slice "Get a slice of the array"), 112 113 //// [slice_clamped](#slice_clamped "Get a slice of the array"), 113 114 //// [drop_first](#drop_first "Remove the first elements"), 114 115 //// [drop_last](#drop_last "Remove the last elements"), 115 116 //// [take_first](#take_first "Take the first elements"), 116 117 //// [take_last](#take_last "Take the last elements"), 118 + //// [sized_chunk](#sized_chunk "Split an array into chunks") 117 119 //// 118 120 //// #### Transform 119 121 //// [reverse](#reverse "Reverse the order"), ··· 136 138 //// [fold_right](#fold_right "Loop end to start, with state"), 137 139 //// [index_fold_right](#index_fold_right "Loop end to start, with index and state") 138 140 141 + import gleam/int 139 142 import gleam/list 140 143 import gleam/yielder.{type Yielder} 141 144 import iv/internal/builder ··· 1739 1742 /// </div> 1740 1743 pub fn take_last(from array: Array(item), up_to n: Int) -> Array(item) { 1741 1744 drop_first(array, size(array) - n) 1745 + } 1746 + 1747 + /// Returns an array of chunks containing `count` elements each. 1748 + /// 1749 + /// If the last chunk does not have count elements, it is instead a partial 1750 + /// chunk, with less than count elements. 1751 + /// 1752 + /// For any count less than 1 this function behaves as if it was set to 1. 1753 + /// 1754 + /// <div style="text-align: right;"> 1755 + /// <a href="#"> 1756 + /// <small>Back to top ↑</small> 1757 + /// </a> 1758 + /// </div> 1759 + pub fn sized_chunk(in array: Array(item), into count: Int) -> Array(Array(item)) { 1760 + sized_chunk_loop(array, int.min(1, count), []) 1761 + } 1762 + 1763 + fn sized_chunk_loop( 1764 + array: Array(item), 1765 + count: Int, 1766 + chunks: List(Array(item)), 1767 + ) -> Array(Array(item)) { 1768 + let size = size(array) 1769 + case size <= count { 1770 + True -> 1771 + case size { 1772 + 0 -> from_reverse_list(chunks) 1773 + _ -> from_reverse_list([array, ..chunks]) 1774 + } 1775 + False -> { 1776 + let #(chunk, rest) = split(array, count) 1777 + sized_chunk_loop(rest, count, [chunk, ..chunks]) 1778 + } 1779 + } 1780 + } 1781 + 1782 + /// Returns an array distributing its elements evenly into `n` chunks. 1783 + /// 1784 + /// If there are less than `n` elements in the array, less chunks may be 1785 + /// returned. 1786 + /// 1787 + /// For any count less than 1 this function behaves as if it was set to 1. 1788 + /// 1789 + /// <div style="text-align: right;"> 1790 + /// <a href="#"> 1791 + /// <small>Back to top ↑</small> 1792 + /// </a> 1793 + /// </div> 1794 + pub fn split_n(in array: Array(item), into n_chunks: Int) -> Array(Array(item)) { 1795 + split_n_loop(array, size(array) / n_chunks, size(array) % n_chunks, []) 1796 + } 1797 + 1798 + fn split_n_loop(array, count, rest, chunks) { 1799 + let size = size(array) 1800 + case size <= count { 1801 + True -> 1802 + case size { 1803 + 0 -> from_reverse_list(chunks) 1804 + _ -> from_reverse_list([array, ..chunks]) 1805 + } 1806 + False -> { 1807 + let #(chunk, array) = case rest > 0 { 1808 + False -> split(array, count) 1809 + True -> split(array, count + 1) 1810 + } 1811 + split_n_loop(array, count, rest - 1, [chunk, ..chunks]) 1812 + } 1813 + } 1742 1814 } 1743 1815 1744 1816 /// Return the array without the first element. If the array is empty,