Skip to content

Instantly share code, notes, and snippets.

@timm
Last active November 24, 2025 15:02
Show Gist options
  • Select an option

  • Save timm/420659046073bfb336fb12c36dd2bf4f to your computer and use it in GitHub Desktop.

Select an option

Save timm/420659046073bfb336fb12c36dd2bf4f to your computer and use it in GitHub Desktop.
lua six (stochastic incremental XAI) . For multiple objective optimization.

📚 lua6.lua | ell

SIX = Stochastic incremental XAI

image link=tiny.cc/lua6


How to Contribute to This Code

This file follows a specific compact Lua coding style.
When modifying or adding code, please follow all rules below.

1. One-Line Function Comments

Place one comment line directly above every function.
Requirements:

  • Starts with uppercase
  • Ends with a period .
  • Short (3–8 words)

Example:

 -- Update numeric statistics.
 function welford(num,val) ...

2. Naming Conventions

Use short, consistent argument names:

  • t=table, s=string, n=number, val=s|n|"?"
  • row=list[val], rows=list[row]
  • col=NUM|SYM, num=NUM, bs=bins-list

Constructors are uppercase: DATA, NUM, SYM. Utilities are lowercase: push, csv, cells, shuffle, pause.

3. Compact, Minimal Formatting

Keep functions small and elegant. Avoid blank lines unless separating sections. Prefer expressions over statements when clear. Keep lines reasonably short.

4. Comment and Section Style

Section headers use:

--## section name

Do not write long essays in comments. Keep all comments concise and functional.

5. Table/Object Shape

Return tables in compact one-line form when possible: return {i=i or 0, is=is or "", n=0, mu=0, sd=0}

6. Bin and Count Structures

Use simple nested tables for best/rest counts: col.has[val] = col.has[val] or {[true]=0, [false]=0}

7. Function Signatures

Always include a compact return-type hint:

 function foo(x,y) --> nil

Use short hints such as: --> t, --> val, --> 0..1, --> iterator

8. Iterator Pattern

All iterators should follow the closure style:

 return function()
   local s = src:read()
   if s then return cells(s) end
   src:close()
   return nil end

9. Randomness + Determinism

Use:

 math.randomseed(The.seed or os.time())

Shuffle using the supplied shuffle() utility.

10. Overall Aesthetic

  • Aim for: compact, clean, deterministic, functional, elegant.
  • No OOP frameworks. No metatables unless essential.
  • Fewer globals, more clarity.
Clndrs Volume HpX Model origin Lbs- Acc+ Mpg+
8 304 193 70 1 4732 18.5 10
8 360 215 70 1 4615 14 10
8 307 200 70 1 4376 15 10
8 318 210 70 1 4382 13.5 10
8 429 208 72 1 4633 11 10
8 400 150 73 1 4997 14 10
8 350 180 73 1 3664 11 10
8 383 180 71 1 4955 11.5 10
8 350 160 72 1 4456 13.5 10
8 429 198 73 1 4952 11.5 10
8 455 225 73 1 4951 11 10
8 400 167 73 1 4906 12.5 10
8 350 180 73 1 4499 12.5 10
8 400 170 71 1 4746 12 10
8 400 175 71 1 5140 12 10
8 350 165 72 1 4274 12 10
8 350 155 72 1 4502 13.5 10
8 400 190 72 1 4422 12.5 10
8 307 130 72 1 4098 14 10
8 302 140 72 1 4294 16 10
8 350 175 73 1 4100 13 10
8 350 145 73 1 3988 13 10
8 400 150 73 1 4464 12 10
8 351 158 73 1 4363 13 10
8 440 215 73 1 4735 11 10
8 360 175 73 1 3821 11 10
8 360 170 73 1 4654 13 10
8 350 150 74 1 4699 14.5 10
8 302 129 75 1 3169 12 10
8 318 150 76 1 3940 13.2 10
8 350 145 76 1 4055 12 10
8 302 130 76 1 3870 15 10
8 318 150 76 1 3755 14 10
8 454 220 70 1 4354 9 10
8 440 215 70 1 4312 8.5 10
8 455 225 70 1 4425 10 10
8 340 160 70 1 3609 8 10
8 455 225 70 1 3086 10 10
8 350 165 71 1 4209 12 10
8 400 175 71 1 4464 11.5 10
8 351 153 71 1 4154 13.5 10
8 318 150 71 1 4096 13 10
8 400 175 72 1 4385 12 10
8 351 153 72 1 4129 13 10
8 318 150 72 1 4077 14 10
8 304 150 73 1 3672 11.5 10
8 302 137 73 1 4042 14.5 10
8 318 150 73 1 4237 14.5 10
8 318 150 74 1 4457 13.5 10
8 302 140 74 1 4638 16 10
8 304 150 74 1 4257 15.5 10
8 351 148 75 1 4657 13.5 10
8 351 152 76 1 4215 12.8 10
8 350 165 70 1 3693 11.5 20
8 429 198 70 1 4341 10 20
8 390 190 70 1 3850 8.5 20
8 383 170 70 1 3563 10 20
8 400 150 70 1 3761 9.5 20
8 318 150 72 1 4135 13.5 20
8 304 150 72 1 3892 12.5 20
8 318 150 73 1 3777 12.5 20
8 350 145 73 1 4082 13 20
8 318 150 73 1 3399 11 20
6 250 100 74 1 3336 17 20
6 250 72 75 1 3432 21 20
6 250 72 75 1 3158 19.5 20
8 350 145 75 1 4440 14 20
6 258 110 75 1 3730 19 20
8 302 130 77 1 4295 14.9 20
8 304 120 76 1 3962 13.9 20
8 318 145 77 1 4140 13.7 20
8 350 170 77 1 4165 11.4 20
8 400 190 77 1 4325 12.2 20
8 351 142 79 1 4054 14.3 20
8 304 150 70 1 3433 12 20
6 225 105 71 1 3439 15.5 20
6 250 100 73 1 3278 18 20
8 400 230 73 1 4278 9.5 20
6 250 100 74 1 3781 17 20
6 258 110 74 1 3632 18 20
8 302 140 74 1 4141 14 20
8 400 170 75 1 4668 11.5 20
8 318 150 75 1 4498 14.5 20
6 250 105 75 1 3897 18.5 20
8 318 150 76 1 4190 13 20
8 400 180 77 1 4220 11.1 20
8 351 149 77 1 4335 14.5 20
6 163 133 78 2 3410 15.8 20
6 168 120 76 2 3820 16.7 20
8 350 180 76 1 4380 12.1 20
8 351 138 79 1 3955 13.2 20
8 350 155 79 1 4360 14.9 20
8 302 140 70 1 3449 10.5 20
6 250 100 71 1 3329 15.5 20
8 304 150 72 1 3672 11.5 20
6 231 110 75 1 3907 21 20
8 260 110 77 1 4060 19 20
6 163 125 78 2 3140 13.6 20
8 305 130 79 1 3840 15.4 20
8 305 140 76 1 4215 13 20
6 258 95 76 1 3193 17.8 20
8 305 145 77 1 3880 12.5 20
6 250 110 77 1 3520 16.4 20
8 318 140 78 1 4080 13.7 20
8 302 129 79 1 3725 13.4 20
6 225 85 81 1 3465 16.6 20
6 231 165 78 1 3445 13.4 20
8 307 130 70 1 3504 12 20
8 318 150 70 1 3436 11 20
6 199 97 70 1 2774 15.5 20
6 232 100 71 1 3288 15.5 20
6 258 110 71 1 2962 13.5 20
6 250 88 71 1 3139 14.5 20
4 121 112 72 2 2933 14.5 20
6 225 105 73 1 3121 16.5 20
6 232 100 73 1 2945 16 20
6 250 88 73 1 3021 16.5 20
6 232 100 73 1 2789 15 20
3 70 90 73 3 2124 13.5 20
6 225 105 74 1 3613 16.5 20
6 250 105 75 1 3459 16 20
6 225 95 75 1 3785 19 20
6 171 97 75 1 2984 14.5 20
6 250 78 76 1 3574 21 20
6 258 120 78 1 3410 15.1 20
8 302 139 78 1 3205 11.2 20
8 318 135 79 1 3830 15.2 20
6 250 110 76 1 3645 16.2 20
6 250 98 77 1 3525 19 20
8 360 150 79 1 3940 13 20
6 225 110 78 1 3620 18.7 20
6 232 100 71 1 2634 13 20
6 250 88 71 1 3302 15.5 20
6 250 100 71 1 3282 15 20
3 70 97 72 3 2330 13.5 20
4 122 85 73 1 2310 18.5 20
4 121 112 73 2 2868 15.5 20
6 232 100 74 1 2901 16 20
6 225 95 75 1 3264 16 20
6 232 90 75 1 3211 17 20
4 120 88 76 2 3270 21.9 20
6 156 108 76 3 2930 15.5 20
6 225 100 77 1 3630 17.7 20
6 225 90 80 1 3381 18.7 20
6 231 105 78 1 3535 19.2 20
8 305 145 78 1 3425 13.2 20
8 267 125 79 1 3605 15 20
8 318 140 78 1 3735 13.2 20
6 232 90 78 1 3210 17.2 20
6 200 85 79 1 2990 18.2 20
8 260 110 78 1 3365 15.5 20
4 140 90 72 1 2408 19.5 20
4 97 88 73 3 2279 19 20
4 114 91 73 2 2582 14 20
6 156 122 73 3 2807 13.5 20
6 198 95 74 1 3102 16.5 20
8 262 110 75 1 3221 13.5 20
6 232 100 75 1 2914 16 20
6 225 100 76 1 3651 17.7 20
4 130 102 76 2 3150 15.7 20
8 302 139 78 1 3570 12.8 20
6 200 85 78 1 2965 15.8 20
6 232 90 79 1 3265 18.2 20
6 200 88 81 1 3060 17.1 20
5 131 103 78 2 2830 15.9 20
6 231 105 77 1 3425 16.9 20
6 200 95 78 1 3155 18.2 20
6 225 100 78 1 3430 17.2 20
6 231 105 78 1 3380 15.8 20
6 225 110 79 1 3360 16.6 20
6 200 85 78 1 3070 16.7 20
6 200 85 70 1 2587 16 20
6 199 90 70 1 2648 15 20
4 122 86 72 1 2226 16.5 20
4 120 87 72 2 2979 19.5 20
4 140 72 73 1 2401 19.5 20
6 155 107 73 1 2472 14 20
6 200 ? 74 1 2875 17 20
6 231 110 75 1 3039 15 20
4 134 95 78 3 2515 14.8 20
4 121 110 77 2 2600 12.8 20
3 80 110 77 3 2720 13.5 20
6 231 115 79 1 3245 15.4 20
4 121 115 78 2 2795 15.7 20
6 198 95 70 1 2833 15.5 20
4 140 72 71 1 2408 19 20
4 121 76 72 2 2511 18 20
4 122 86 72 1 2395 16 20
4 108 94 73 3 2379 16.5 20
4 121 98 75 2 2945 14.5 20
6 225 100 76 1 3233 15.4 20
6 250 105 76 1 3353 14.5 20
6 146 97 77 3 2815 14.5 20
6 232 112 82 1 2835 14.7 20
4 140 88 79 1 2890 17.3 20
6 231 110 81 1 3415 15.8 20
6 232 90 76 1 3085 17.6 20
4 122 86 71 1 2220 14 20
4 97 54 72 2 2254 23.5 20
4 120 97 72 3 2506 14.5 20
6 198 95 73 1 2904 16 20
4 140 83 75 1 2639 17 20
4 140 78 75 1 2592 18.5 20
4 115 95 75 2 2694 15 20
4 120 88 75 2 2957 17 20
8 350 125 79 1 3900 17.4 20
4 151 ? 82 1 3035 20.5 20
4 156 105 78 1 2745 16.7 20
6 173 110 81 1 2725 12.6 20
4 140 ? 80 1 2905 14.3 20
3 70 100 80 3 2420 12.5 20
4 151 85 78 1 2855 17.6 20
4 119 97 78 3 2405 14.9 20
8 260 90 79 1 3420 22.2 20
4 113 95 70 3 2372 15 20
4 107 90 70 2 2430 14.5 20
4 113 95 72 3 2278 15.5 20
4 116 75 73 2 2158 15.5 20
4 121 110 73 2 2660 14 20
4 90 75 74 2 2108 15.5 20
4 120 97 74 3 2489 15 20
4 134 96 75 3 2702 13.5 20
4 119 97 75 3 2545 17 20
6 200 81 76 1 3012 17.6 20
4 140 92 82 1 2865 16.4 20
6 146 120 81 3 2930 13.8 20
4 151 90 80 1 3003 20.1 20
4 98 60 76 1 2164 22.1 20
4 151 88 77 1 2740 16 20
4 110 87 70 2 2672 17.5 30
4 104 95 70 2 2375 17.5 30
4 113 95 71 3 2228 14 30
4 98 ? 71 1 2046 19 30
4 97.5 80 72 1 2126 17 30
4 140 75 74 1 2542 17 30
4 90 71 75 2 2223 16.5 30
4 121 115 75 2 2671 13.5 30
4 116 81 76 2 2220 16.9 30
4 140 92 76 1 2572 14.9 30
6 181 110 82 1 2945 16.4 30
4 140 88 78 1 2720 15.4 30
5 183 77 79 2 3530 20.1 30
6 168 116 81 3 2900 12.6 30
4 122 96 77 1 2300 15.5 30
4 140 89 77 1 2755 15.8 30
4 156 92 81 1 2620 14.4 30
4 97 46 70 2 1835 20.5 30
4 121 113 70 2 2234 12.5 30
4 91 70 71 1 1955 20.5 30
4 96 69 72 2 2189 18 30
4 97 46 73 2 1950 21 30
4 98 90 73 2 2265 15.5 30
4 122 80 74 1 2451 16.5 30
4 79 67 74 2 1963 15.5 30
4 97 78 74 2 2300 14.5 30
4 116 75 74 2 2246 14 30
4 108 93 74 3 2391 15.5 30
4 98 79 76 1 2255 17.7 30
4 97 75 77 3 2265 18.2 30
4 156 92 82 1 2585 14.5 30
4 140 88 80 1 2870 18.1 30
4 140 72 76 1 2565 13.6 30
4 151 84 81 1 2635 16.4 30
8 350 105 81 1 3725 19 30
6 173 115 79 1 2700 12.9 30
4 97 88 70 3 2130 14.5 30
4 97 88 71 3 2130 14.5 30
4 97 60 71 2 1834 19 30
4 97 88 72 3 2100 16.5 30
4 101 83 76 2 2202 15.3 30
4 112 88 82 1 2640 18.6 30
4 151 90 82 1 2735 18 30
4 151 90 82 1 2950 17.3 30
4 140 86 82 1 2790 15.6 30
4 119 97 78 3 2300 14.7 30
4 141 71 79 2 3190 24.8 30
4 135 84 81 1 2490 15.7 30
4 121 80 79 1 2670 15 30
4 134 95 78 3 2560 14.2 30
4 156 105 80 1 2800 14.4 30
4 140 90 71 1 2264 15.5 30
4 116 90 71 2 2123 14 30
4 97 92 72 3 2288 17 30
4 98 80 72 1 2164 15 30
4 90 75 74 1 2125 14.5 30
4 107 86 76 2 2464 15.5 30
4 97 75 76 3 2155 16.4 30
4 151 90 80 1 2678 16.5 30
4 112 88 82 1 2605 19.6 30
4 120 79 82 1 2625 18.6 30
4 141 80 81 2 3230 20.4 30
4 151 90 79 1 2670 16 30
6 173 115 79 1 2595 11.3 30
4 68 49 73 2 1867 19.5 30
4 98 83 74 2 2219 16.5 30
4 97 75 75 3 2171 16 30
4 90 70 75 2 1937 14 30
4 85 52 76 1 2035 22.2 30
4 90 70 76 2 1937 14.2 30
4 97 78 77 2 1940 14.5 30
4 135 84 82 1 2525 16 30
4 97 71 76 2 1825 12.2 30
4 98 68 78 3 2135 16.6 30
4 134 90 80 3 2711 15.5 30
4 89 62 80 2 1845 15.3 30
4 98 65 81 1 2380 20.7 30
4 79 70 71 2 2074 19.5 30
4 88 76 71 2 2065 14.5 30
4 111 80 77 1 2155 14.8 30
4 97 67 77 3 1985 16.4 30
4 98 68 78 1 2155 16.5 30
4 146 67 80 2 3250 21.8 30
4 135 84 81 1 2385 12.9 30
4 98 63 77 1 2051 17 30
4 97 78 77 2 2190 14.1 30
6 145 76 81 2 3160 19.6 30
4 105 75 78 1 2230 14.5 30
4 71 65 71 3 1773 19 30
4 79 67 74 3 1950 19 30
4 76 52 74 3 1649 16.5 30
4 79 67 74 2 2000 16 30
4 112 85 82 1 2575 16.2 30
4 91 68 82 3 1970 17.6 30
4 119 82 82 1 2720 19.4 30
4 120 75 80 3 2542 17.5 30
4 98 68 77 3 2045 18.5 30
4 89 71 78 2 1990 14.9 30
4 120 74 81 3 2635 18.3 30
4 85 65 79 3 2020 19.2 30
4 89 71 79 2 1925 14 30
4 71 65 74 3 1836 21 30
4 83 61 74 3 2003 19 30
4 85 70 76 3 1990 17 30
4 91 67 82 3 1965 15.7 30
4 144 96 82 3 2665 13.9 30
4 135 84 82 1 2295 11.6 30
4 98 70 80 1 2120 15.5 30
4 108 75 80 3 2265 15.2 30
4 97 67 81 3 2065 17.8 30
4 107 72 80 3 2290 17 30
4 108 75 81 3 2350 16.8 30
6 168 132 80 3 2910 11.4 30
4 78 52 78 3 1985 19.4 30
4 119 100 81 3 2615 14.8 30
4 91 53 75 3 1795 17.5 30
4 91 53 76 3 1795 17.4 30
4 105 74 81 2 2190 14.2 30
4 85 70 77 3 1945 16.8 30
4 98 83 77 1 2075 15.9 30
4 151 90 79 1 2556 13.2 30
4 107 75 81 3 2210 14.4 30
4 97 67 80 3 2145 18 30
4 112 88 82 1 2395 18 30
4 108 70 82 3 2245 16.9 30
4 86 65 79 3 1975 15.2 30
4 91 68 81 3 1985 16 30
4 105 70 79 1 2200 13.2 30
4 97 78 80 2 2188 15.8 30
4 98 65 81 1 2045 16.2 30
4 105 70 79 1 2150 14.9 30
4 100 ? 81 2 2320 15.8 30
4 105 63 81 1 2215 14.9 30
4 72 69 71 3 1613 18 40
4 122 88 80 2 2500 15.1 40
4 81 60 81 3 1760 16.1 40
4 98 80 79 1 1915 14.4 40
4 79 58 77 2 1825 18.6 40
4 105 74 82 2 1980 15.3 40
4 98 70 82 1 2125 17.3 40
4 120 88 82 3 2160 14.5 40
4 107 75 82 3 2205 14.5 40
4 135 84 82 1 2370 13 40
4 98 66 78 1 1800 14.4 40
4 91 60 78 3 1800 16.4 40
5 121 67 80 2 2950 19.9 40
4 119 92 80 3 2434 15 40
4 85 65 81 3 1975 19.4 40
4 91 68 82 3 2025 18.2 40
4 86 65 80 3 2019 16.4 40
4 91 69 79 2 2130 14.7 40
4 89 62 81 3 2050 17.3 40
4 105 63 82 1 2125 14.7 40
4 91 67 82 3 1965 15 40
4 91 67 82 3 1995 16.2 40
6 262 85 82 1 3015 17 40
4 89 60 80 3 1968 18.8 40
4 86 64 81 1 1875 16.4 40
4 79 58 81 3 1755 16.9 40
4 85 70 78 3 2070 18.6 40
4 85 65 80 3 2110 19.2 40
4 85 ? 80 2 1835 17.3 40
4 98 76 80 2 2144 14.7 40
4 90 48 78 2 1985 21.5 40
4 90 48 80 2 2335 23.7 40
4 97 52 82 2 2130 24.6 40
4 90 48 80 2 2085 21.7 40
4 91 67 80 3 1850 13.8 40
4 86 65 80 3 2110 17.9 50

Act as a Senior Technical Writer and Front-end Developer.

I have a Lua script (pasted below) that implements a specific algorithm. I need you to generate a single-file HTML documentation viewer for it.

Requirements:

Tech Stack: Use HTML5, Tailwind CSS (via CDN), and FontAwesome (via CDN). Use vanilla JavaScript for interactivity.

Layout: Create a top navigation bar with tabs. Below that, have a main content area that switches content based on the selected tab (do not reload the page).

Design: Use a modern, clean aesthetic. Use "Slate" and "Indigo" color palettes. Use "Inter" font for text and "JetBrains Mono" for code blocks.

Code Display: When showing code snippets, use distinct colors for keywords, strings, and comments to simulate syntax highlighting.

Sections (Tabs):

Overview: Explain what the script does based on the comments and header.

Lua 101: Analyze the code for unique or obscure Lua patterns (like defining locals in function arguments, metatable constructors, or ternary-style logic) and explain them for beginners using examples from the code.

API Reference: Document the key functions and classes (Data, Num, Sym) with parameters and descriptions.

CLI & Config: Explain how to run the script and what the command line flags do.

#!/usr/bin/env bash
# Copyright (c) 2025 Tim Menzies, MIT License
# https://opensource.org/licenses/MIT
Here=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
alias ls="\ls --color"
alias reload="source '$Here/ell' && echo ✅"
alias grep='grep --color=auto'
alias tree='tree -C'
export BASH_SILENCE_DEPRECATION_WARNING=1
[[ ":$PATH:" != *":$Here:"* ]] && export PATH="$Here:$PATH"
export HISTSIZE=10000
export HISTFILESIZE=20000
export HISTCONTROL=ignoredups:erasedups
Here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
branch() { git branch 2>/dev/null | awk '/^\*/ {print $2}'; }
dirty() { [[ -n $(git status -s 2>/dev/null) ]] && echo "*"; }
bold=$(tput bold) col0=$(tput sgr0) col1=$(tput setaf 6) col2=$(tput setaf 3)
PROMPT_COMMAND='PS1="${bold}${col1}$(basename "$(dirname "$PWD")")/$(basename "$PWD")${col0} ${col2}$(branch)$(dirty)${col0} ▶ "'
gc() { echo -n "Commit? Why: "; read -r m; git commit -am "$m" && git push && git status; }
vi() {
nvim --clean \
--cmd "let g:netrw_banner=0 | let g:netrw_liststyle=3 | let g:netrw_browse_split=4 | let g:netrw_winsize=15" \
--cmd "set number relativenumber cursorline mouse=a clipboard=unnamedplus ignorecase smartcase" \
--cmd "set statusline=%#StatusLine#\ ▶\ %f\ %m%r%=%y\ ❖\ %l:%c\ ❖\ %p%%\ " \
--cmd "set expandtab tabstop=2 shiftwidth=2 splitright splitbelow" \
--cmd "set undofile undodir=~/.vim/undo" \
--cmd "nnoremap Q :quitall<CR>" \
--cmd "colorscheme zaibatsu" \
--cmd "set laststatus=2" \
"$@"
}
hi() {
clear
printf "${col2}"
figlet -W -f larry3d "$(basename "$PWD")"
printf "${col0}"
}
inst() {
local m=""
for p in $1; do command -v "$p" &>/dev/null || m+="$p "; done
[ "$m" ] && case "$(uname -s)" in
Darwin*) brew install $m ;;
Linux*) sudo apt install -y $m ;;
MINGW*) winget install $m ;;
esac
}
# only run slow or verbose command at initial startup, not on reload
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
inst git nvim gawk tree figlet
hi
exec bash --init-file "${BASH_SOURCE[0]}" -i
fi

-- -- Lua 5.4 -- Purpose XAI -- Goal Multi-Obj -- Deps 0 -- LOC ~200
-- download :: -- © 2025 by timm --### In this code:

-- - f=function; n=number; s=string; b=boolean; "?"=unknown -- - a,d = array,dict (table with numeric, symbolic indexes) -- - t = d | a -- - v = n | s | b | "?" -- - row = array of v; rows = array of row -- - ds,dn,db,as,an,ab=dict,array of strings or nums or bools -- - UPPER CASE = types; e.g. DATA -- - Leading case = constructors; e.g. Data() -- - lower case constructor names = instances; e.g. data, data1 -- - the = settings (parsed from help) -- - go = CLI flag→handler map -- - 2/4 spaces = optional/local args

#!/usr/bin/env lua
-- Stochastic incremental XAI (for multi-objective XAI).
-- (c) 2025 Tim Menzies. MIT License:
-- https://opensource.org/licenses/MIT
local The = {bins = 7,
barw = 10,
file = "auto93.csv",
pause = 25,
seed = 937162211}
--## Batteries
local push,cells,csv,coerce,shuffle,pause,lt
-- Add a value to a table.
function push(t,val) --> val
t[#t+1] = val; return val end
-- Convert a string to number if possible.
function coerce(s) --> n or s
return tonumber(s) or s:match'^%s*(.*%S)' end
-- Shuffle a table in-place.
function shuffle(t, j) --> t shuffled
for i = #t,2,-1 do j = math.random(i);t[i],t[j] = t[j],t[i] end
return t end
-- Pause execution and wait for keypress.
function pause(s, tty) --> nil
io.write("\n"..(s or "")..": press enter...")
tty = io.open("/dev/tty","r")
tty:read() end
-- Sort comparison handling "?" and mixed types.
function lt(a,b) --> bool
return a == "?" and false or
b == "?" and true or
tonumber(a) and tonumber(b) and tonumber(a)<tonumber(b) or
tostring(a)<tostring(b) end
-- Split comma-separated string into cells.
function cells(s1, t) --> t
t = {}; for s2 in s1:gmatch"([^,]+)" do push(t,coerce(s2)) end
return t end
-- Return an iterator over CSV rows.
function csv(file, src) --> iterator
src = io.open(file,"r")
return function()
local s = src:read()
if s then return cells(s) end
src:close()
return nil end end
--## Constructors
local SYM,NUM,COLS,DATA = {},{},{},{}
-- Create a new COLS object from header row.
function COLS.new(row, self,col) --> COLS
COLS.__index = COLS
self = setmetatable({x={},y={},all={}}, COLS)
for i,word in ipairs(row) do
col = (word:match"^[A-Z]" and NUM or SYM).new(i,word)
if not word:match"X[+-]?$" then
push(word:find"[+-]$" and self.y or self.x,col) end
push(self.all,col) end
return self end
-- Create a new DATA object.
function DATA.new(rows) --> DATA
DATA.__index = DATA
local self = setmetatable({cols=nil,n=0,b=0,dist=NUM.new()}, DATA)
if rows and #rows > 0 then
self.cols = COLS.new(rows[1])
for _,row in ipairs(shuffle({table.unpack(rows,2)})) do
self:add(row) end end
return self end
-- Create a new SYM column.
function SYM.new(n,is) --> SYM
SYM.__index = SYM
return setmetatable({i=n or 0,is=is or"",has={}}, SYM) end
-- Map symbolic value to itself (bin).
function SYM:bin(val) --> val
return val end
-- Create a new NUM column.
function NUM.new(n,is) --> NUM
NUM.__index = NUM
return setmetatable({i=n or 0,is=is or"",num=true,n=0,mu=0,sd=0,m2=0,has={},
best=(is or""):find"-$" and 0 or 1}, NUM) end
--## Reasoning
-- Update numeric stats using Welford's algorithm.
function NUM:add(val, d) --> val
if val == "?" then return val end
self.n = self.n+1
d = val-self.mu
self.mu = self.mu+d/self.n
self.m2 = self.m2+d*(val-self.mu)
self.sd = self.n<2 and 0 or math.sqrt(self.m2/(self.n-1))
return val end
-- Normalize a numeric value to 0..1.
function NUM:norm(val) --> 0..1
return 1/(1+math.exp(-1.7*(val-self.mu)/(self.sd+1e-32))) end
-- Map numeric value to a bin.
function NUM:bin(val) --> 0..The.bins-1 or val
return val == "?" and val or math.floor(The.bins*self:norm(val)) end
-- Compute distance-to-best for a row.
function DATA:disty(row, d,n,val) --> 0..1
d,n = 0,0
for _,col in ipairs(self.cols.y) do
val = row[col.i]
if val ~= "?" then
n = n+1; d = d+(col:norm(val)-col.best)^2
end end
return math.sqrt(d/n) end
-- Update best/rest counts and feature bins.
function DATA:count(row, y,ny,isBest,val) --> nil
y = self.dist:add(self:disty(row))
ny = self.dist:norm(y)
self.n = self.n+1
isBest = ny <= math.sqrt(self.n)/self.n
if isBest then self.b = self.b+1 end
for _,col in ipairs(self.cols.x) do
val = col:bin(row[col.i])
col.has[val] = col.has[val] or {[true] = 0,[false] = 0}
col.has[val][isBest] = col.has[val][isBest]+1
end end
-- Add a row to DATA.
function DATA:add(row) --> nil
if not self.cols then
self.cols = COLS.new(row)
else
for _,col in ipairs(self.cols.all) do
if col.num then col:add(row[col.i]) end end
self:count(row)
if self.n%The.pause == 0 then self:report() end end end
-- Score bins by separation of best vs rest.
function DATA:score( scores,bad,good) --> scores
scores = {}
for _,col in ipairs(self.cols.x) do
for val,f in pairs(col.has) do
good = f[true]/self.b
bad = f[false]/(self.n-self.b)
push(scores,{score = (good-bad + 1)/2,col = col.is,val = val})
end end
table.sort(scores,function(a,b) return a.score>b.score end)
return scores end
--## Presentation
local bars, cols, spark, swatch
bars = {"","","","","","",""}
cols = {
"\027[38;5;196m", -- dark red (worst)
"\027[38;5;202m", -- light red
"\027[38;5;214m", -- orange (middle-low)
"\027[38;5;51m", -- cyan (middle) - easier to distinguish
"\027[38;5;117m", -- light blue (middle-high)
"\027[38;5;46m", -- bright green
"\027[38;5;22m" -- dark green (best)
}
-- Render a single sparkline.
function spark(cs,bs,lo,hi, s,i,x) --> sparkline string with color
s = ""
for _,b in ipairs(bs) do
x = (cs[b] - lo) / (hi - lo + 1e-32)
i = math.min(7, math.floor(x * 7) + 1)
s = s..cols[i]..bars[i]
end
return s.."\027[0m" end
-- Print color swatch legend.
function swatch() --> nil
io.write(string.rep(" ",16).."bad: ")
for i = 1,#bars do
io.write(cols[i]..bars[i])
end
io.write("\027[0m :good\n\n")
end
-- Print colored sparklines for all bins.
function DATA:report( scores,cols,lo,hi,bs) --> nil
io.write("\027[2J\027[H")
swatch()
scores = self:score()
cols,lo,hi = {}, math.huge, -math.huge
for _,one in ipairs(scores) do
cols[one.col] = cols[one.col] or{}
cols[one.col][one.val] = one.score
lo = math.min(lo, one.score)
hi = math.max(hi, one.score) end
for _,col in ipairs(self.cols.x) do
local cs = cols[col.is]
if cs then
bs = {}; for b in pairs(cs) do push(bs,b) end
table.sort(bs,lt)
local s = spark(cs,bs,lo,hi)
local pad = string.rep(" ",The.barw-#bs)
io.write(("%20s %s%s "):format(col.is..":",s,pad))
for _,b in ipairs(bs) do io.write(b.." ") end
io.write("\n") end end
pause(self.n) end
--## Main
local main
-- Main driver for reading, shuffling, and processing.
function main(file, rows) --> nil
rows = {}
for row in csv(file or The.file) do push(rows,row) end
math.randomseed(The.seed or os.time())
DATA.new(rows):report() end
for i,s in pairs(arg) do
s = s:sub(2)
for k,_ in pairs(The) do
if k:sub(1,1)==s then print(k,arg[i+1]); The[k]=coerce(arg[i+1]) end end end
main()
#!/usr/bin/env lua
local the, help = {},[[
seven : stochastic incremental XAI (v2)
(c) 2025 Tim Menzies. MIT License, https://opensource.org/licenses/MIT
Options:
-b bins=7 number of bins
-f file=auto93.csv csv data file
-s seed=1234567891 random number seeds
]]
-- In functions, for required args, t=table; s=string; n=number;
-- ts, tn = list of strings or numbers; v=string/number/?; row=list[v].
-- Also, num=Numeric columnl sym=Symbolic column; col=num or sym.
-- Functions names in UPPER CASE are constructors; e.g. DATA,NUM,SYM,COLS.
-- (and DATA stores the global context). Finally, `the` holds configuration,
--------------------------------------------------------------------------------
--## Batteries
local new, push, coerce, cells, csv, fmt, cat, cat4list, cat4dict, sort
function new(kl,t) --> t
kl.__index=kl; return setmetatable(t,kl) end
function push(t,v) --> v
t[#t+1] = v; return v end
function coerce(s) --> n or s
return tonumber(s) or s:match'^%s*(.-)%s*$' end
function cells(s, t) --> t
t = {}; for s1 in s:gmatch"([^,]+)" do t[1+#t]=coerce(s1) end; return t end
function csv(filename, src) --> iterator
src = io.open(filename,"r")
return function( s)
s = src:read()
if s then return cells(s) else src:close() end end end
function sort(t, fn) --> t
table.sort(t,fn); return t end
fmt=string.format --> str
function cat(x) --> str
return type(x)=="number" and fmt(math.floor(x)==x and "%s" or "%.3g",x)
or type(x)==type(cat) and "()"
or type(x)~="table" and tostring(x)
or "{"..table.concat(#x==0 and sort(cat4dict(x)) or cat4list(x)," ").."}" end
function cat4list(t, u) --> ts
u={}; for i,v in ipairs(t) do u[i]=cat(v) end; return u end
function cat4dict(t, u) --> ts
u={}; for k,v in pairs(t) do u[1+#u]=fmt(":%s %s",k,cat(v)) end; return u end
--------------------------------------------------------------------------------
--## Types
local Data,Cols,Num,Sym,adds,clone -- constructors
local DATA,COLS,NUM,SYM = {},{},{},{} -- types
function Sym(n,s) --> SYM
return new(SYM, {i=n or 0, is=s or"", n=0, has={}, mode=nil, most=0}) end
function Num(n,s) --> NUM
return new(NUM, {i=n or 0, is=s or"", n=0, mu=0, sd=0, m2=0, has=SYM(),
best=(s or""):find"-$" and 0 or 1}) end
function Data(src, data) --> DATA
data = new(DATA, {rows={}, cols=nil})
if type(src)=="string"
then for row in csv(src) do data:add(row) end
else for _,row in pairs(src or {}) do data:add(row) end end
return data end
function Cols(ts, col,x,y,all) --> COLS
x,y,all = {},{},{}
for n,s in ipairs(ts) do
col = push(all, (s:match"^[A-Z]" and Num or Sym)(n,s))
if not s:match"X[+-]?$" then
push(s:find"[+-]$" and y or x, col) end end
return new(COLS,{x=x, y=y, seen=Num(), all=all, names=ts}) end
function adds(t, it) --> it
it = it or Num()
for _,z in pairs(t) do it:add(z) end
return it end
function clone(data,rows) --> DATA
return adds(rows or {}, DATA({data.cols.names})) end
--------------------------------------------------------------------------------
function NUM:norm(n) --> 0..1
return 1/(1+math.exp(-1.7*(n-self.mu)/(self.sd+1e-32))) end
function SYM:bin(v) return v end
function NUM:bin(n) --> 0..the.bins-1 or n
return n == "?" and n or math.floor(the.bins*self:norm(n)) end
--------------------------------------------------------------------------------
function DATA:add(row) --> row
if self.cols
then push(self.rows, row)
for _,col in pairs(self.cols.all) do col:add(row[col.i]) end
else self.cols = Cols(row) end
return row end
function NUM:add(n, d) --> n
if n ~= "?" then
self.n = self.n+1
d = n - self.mu
self.mu = self.mu + d / self.n
self.m2 = self.m2 + d * (n - self.mu)
self.sd = self.n<2 and 0 or math.sqrt(self.m2/(self.n-1)) end
return n end
function SYM:add(v) --> v
if v ~= "?" then
self.n = 1 + self.n
self.has[v] = 1 + (self.has[v] or 0)
if self.has[v] > self.most then
self.most, self.mode = self.has[v],v end end
return v end
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
function DATA:disty(row, d,n) --> 0..1
d,n = 0,0
for _,num in pairs(self.nums.y) do
n = n + 1
d = d + (num:norm(row[num.i]) - num.best)^2 end
return (d/n) ^ 0.5 end
function DATA:distx(row1,row2, d,n) --> 0..1
d,n = 0,0
for _,col in pairs(self.cols.x) do
n = n + 1
d = d + col:distx(row1[col.i], row2[col.i])^2 end
return (d/n) ^ 0.5 end
function SYM:distx(v1,v2)
return v1=="?" and v2=="?" and 1 or (v1==v2 and 0 or 1) end
function NUM:distx(n1,n2)
if n1=="?" and n2=="?" then return 1 end
n1, n2 = self:norm(n1), self:norm(n2)
n1 = n1 ~= "?" and n1 or (n2 > 0.5 and 0 or 1)
n2 = n2 ~= "?" and n2 or (n1 > 0.5 and 0 or 1)
return math.abs(n1 - n2) end
function DATA:report()
for i=1,350,50 do print(cat(self.rows[i])) end
for _,col in pairs(self.cols.x) do print(cat(col)) end end
--------------------------------------------------------------------------------
function settings(s, t) --> t
t={}; for k,v in s:gmatch("(%S+)=(%S+)") do t[k]=coerce(v) end; return t end
function cli(t) --> nil
for i,s in pairs(arg) do
for k,_ in pairs(t) do
if k:sub(1,1)==s:sub(2) then t[k] = coerce(arg[i+1]) end end end
return t end
--------------------------------------------------------------------------------
the = settings(help)
math.randomseed(the.seed)
if arg[0]:find("lua7.lua$") then
the = cli(the)
math.randomseed(the.seed)
Data(the.file):report() end
#!/usr/bin/env lua
local the, help = {},[[
seven : stochastic incremental XAI (v2)
(c)2025, Tim Menzies. MIT License. opensource.org/licenses/MIT
Options
-b bins=7 number of bins
-f file=auto93.csv csv data file
-s seed=1234567891 random number seeds
-h show help ]]
--## Batteries
local new,push,map,sort,lt,coerce,cells,csv,fmt,cat,_a2as,_d2as
--### Lists
-- push(a,v) -> v;; Appends value to an array.
function push(a,v)
a[#a+1] = v; return v end
-- map(d,f) --> a;; Map a function over all items in `d`.
function map(d,f, u)
u={}; for k,v in pairs(d) do push(u,f(k,v)) end; return u end
-- sort(a, f=nil) -> a;; Sorts array in-place.
function sort(a, f)
table.sort(a, f); return a end
-- lt(n) --> f;; Return a function that sorts by index `n`.
function lt(n)
return function(a1,a2) return a1[n] < a2[n] end end
-- ### Thing to string
-- fmt(s, ...) -> s;; Alias for string.format.
fmt=string.format
-- cat(v) -> s;; Return a non-ugly string for a value or nested table.
function cat(v)
return type(v)=="number" and fmt(math.floor(v)==v and "%.0f" or "%.3g", v)
or type(v)==type(cat) and "()"
or type(v)~="table" and tostring(v)
or "{".. table.concat(#v==0 and sort(_d2as(v)) or _a2as(v), " ") .."}" end
-- _a2as(a) -> as;; Internal, makes array of strings for `cat`.
function _a2as(a)
local as={}; for i,v in ipairs(a) do as[i]=cat(v) end; return as end
-- _d2as(d) -> as;; Internal, makes dict of strings for `cat`.
function _d2as(d)
local as={}; for k,v in pairs(d) do
as[1+#as] = fmt(":%s %s",k,cat(v)) end; return as end
--### String to thing
-- coerce(s) -> n|s;; Converts a string to a number if possible.
function coerce(s)
return s==nil and "" or tonumber(s) or s:match'^%s*(.-)%s*$' end
-- cells(s) -> row;; Splits CSV string and coerces values.
function cells(s)
local a={}; for s1 in s:gmatch"([^,]+)" do a[1+#a]=coerce(s1) end; return a end
-- csv(file: s) -> iterator<i,row>;; Convert `file` to iterator for index,row
function csv(file, i,tream)
i,stream = 0,assert(io.open(file))
return function( s)
s= stream:read()
if s then i=i+1; return i,cells(s) else stream:close() end end end
--### Meta
-- iter(v: nil|t|f) -->iterator<i,Row>;; Convert `v` to iterator for index,row
function iter(v)
if type(v)=="string" then return csv(v) end
if type(v)=="table" then
if #v > 0 then return ipairs(v) else return pairs(v) end end
return pairs({}) end
-- new(meta:o, d) -> d;; Standard metatable constructor.
function new(meta,d)
meta.__index = meta; return setmetatable(d, meta) end
--## Constructors
local SYM,NUM,DATA,COLS = {},{},{},{} -- types
local Sym,Num,Data,Cols -- constructors for types
local adds
-- Sym(pos=0:n, txt="":s) -> SYM;; Symbolic column constructor.
function Sym( pos,txt)
return new(SYM,{pos=pos or 0, is=txt or"", n=0, has={}, mode=nil, most=0}) end
-- Num(pos=0:n, txt="":s) -> NUM;; Numeric column constructor.
function Num( pos,txt)
return new(NUM, {pos=pos or 0, is=txt or"", n=0, mu=0, sd=0, m2=0, has=Sym(),
best=(txt or""):find"-$" and 0 or 1}) end
-- Data(v:t|f|s) -> DATA;; Data table constructor.
function Data(v)
return adds(v, new(DATA, {rows={}, cols=nil, ys=Num()})) end
-- Cols(names:as) -> COLS;; Column metadata constructor.
function Cols(names, col)
local x, y, all = {},{},{}
for i,s in ipairs(names) do
col = (s:match"^[A-Z]" and Num or Sym)(i,s)
push(all, col)
if not s:match"X[+-]?$" then
push(s:find"[+-]$" and y or x, col) end end
return new(COLS,{x=x, y=y, seen=Num(), all=all, names=names}) end
--## Bins
function Bins(names)
return new(Guess,{all = Data({names}), best = Data({names}),
rest = Data({names})}) end
-- function BINS:add(row)
-- x = function(r1,r2) return all:distx(r1,r2) end
-- y = function(r) return all:disty(r) end
-- now = #self.all.rows
-- if now <= the.any then all:add(row) end
-- if now == the.any then
-- for i,row in ipairs(sort(self.all.rows, y)) do
-- (i <= the.any/2 and self.best or self.rest):add(row) end
-- if now > the.any then
-- if x(row, best:mid()) < x(row, rest:mid()) then
-- self.best:add(row)
-- adds(v:t|f|s, it=Num():d) -> d;; Adds a items to some object.
function adds(v, it)
it = it or Num()
for _,z in iter(v) do it:add(z) end
return it end
-- DATA:clone(rows={}:a) -> DATA;; Creates a new DATA structure.
function DATA:clone( rows)
return adds(rows or {}, Data({self.cols.names})) end
--## Data Layer Methods
-- NUM:norm(n) -> n;; Normalizes number to 0..1.
function NUM:norm(n)
return n=="?" and n or 1/(1+math.exp(-1.7*(n-self.mu)/(self.sd+1e-32))) end
-- DATA:mid() -> v;; Return expected value of this type.
function DATA:mid() return map(self.cols.all,function(c) return c:mid() end) end
function NUM:mid() return self.mu end
function SYM:mid() return self.mode end
-- DATA:bin(v) -> b;; Rows are binned if they are/are not at the least end.
function DATA:bin(row)
y = self.ys:add(self:disty(row))
return self.ys.norm(y) < 1/self.ys.n^0.5 end
-- NUM:bin(num) -> 0 .. the.bin-1;; Numeric binning returns an index to a bin.
function NUM:bin(n)
return n=="?" and n or math.floor(the.bins*self:norm(n)) end
-- SYM:bin(v) -> v;; Discrete binning returns v unchanged
function SYM:bin(v)
return v end
--## Add Methods
-- DATA:add(row) -> row;; Adds a new row to DATA.
function DATA:add(row)
if self.cols
then push(self.rows, row)
for i,col in pairs(self.cols.all) do col:add(row[col.pos]) end
else self.cols = Cols(row) end
return row end
-- NUM:add(n) -> n;; Updates mean/sd for a numeric column.
function NUM:add(n, d)
if n ~= "?" then
self.n = 1 + self.n
d = n - self.mu
self.mu = self.mu + d / self.n
self.m2 = self.m2 + d * (n - self.mu)
self.sd = self.n<2 and 0 or math.sqrt(self.m2/(self.n-1)) end
return n end
-- SYM:add(v) -> v;; Updates mode/count for a symbolic column.
function SYM:add(v)
if v ~= "?" then
self.n = 1 + self.n
self.has[v] = 1 + (self.has[v] or 0)
if self.has[v] > self.most then
self.most, self.mode = self.has[v],v end end
return v end
--## Distance/Report Methods
-- DATA:disty(row) -> n;; Calculates normalized Y-distance for a row.
function DATA:disty(row, d,n)
d,n = 0,0
for i,num in pairs(self.cols.y) do
n = n + 1
d = d + (num:norm(row[num.pos]) - num.best)^2 end
return (d/n) ^ 0.5 end
-- DATA:distx(row1,row2) -> n;; Normalized X-distance between rows.
function DATA:distx(row1,row2, d,n)
d,n = 0,0
for i,col in pairs(self.cols.x) do
n = n + 1
d = d + col:distx(row1[col.pos], row2[col.pos])^2 end
return (d/n) ^ 0.5 end
-- SYM:distx(v1,v2) -> n;; Distance metric for symbolic values.
function SYM:distx(v1,v2)
return v1=="?" and v2=="?" and 1 or (v1==v2 and 0 or 1) end
-- NUM:distx(n1,n2) -> n;; Distance metric for numeric values.
function NUM:distx(n1,n2)
if n1=="?" and n2=="?" then return 1 end
n1, n2 = self:norm(n1), self:norm(n2)
n1 = (n1 ~= "?") and n1 or (n2 > 0.5 and 0 or 1)
n2 = (n2 ~= "?") and n2 or (n1 > 0.5 and 0 or 1)
return math.abs(n1 - n2) end
--## Discretization
--## Reporting
local sparkline
-- sparkline(scores:a) --> s;; Return a sparkline string.
function sparkline(scores, s,labels,colors,reset)
s, labels = "", {"▁","▂","▃"," ","▄","▅","▆"}
colors = { "\27[97;101m", -- 1: bright white on bright red
"\27[30;103m", -- 2: black on bright yellow
"\27[30;43m", -- 3: black on yellow/orange
"\27[30;107m", -- 4: black on bright white (middle)
"\27[30;102m", -- 5: black on bright green
"\27[30;42m", -- 6: black on green
"\27[97;42m"} -- 7: bright white on green (darkest green)
for _,x in ipairs(scores) do
local i = math.min(7, math.floor(x*7)+1)
s = s .. colors[i] .. labels[i] .. "\27[0m " end
return s end
-- DATA:report() --> nil;; Print the sparklines
function DATA:report( kv,cols,vals,bins)
kv = function (t) return map(t, function(k,v) return {k,v} end) end
cols = self:score()
for _,col in ipairs(self.cols.x) do
vals, bins = {}, {}
for _,p in ipairs(sort(kv(cols[col.is] or {}), lt(1))) do
push(vals, p[2]); push(bins, p[1]) end
print(fmt("%15s: %s %s", col.is, sparkline(vals), cat(bins))) end end
--## CLI/Config
local settings, cli -- Locals for CLI/Config functions
local go = {}
-- settings(s) -> d;; Parses help string into a config dictionary.
function settings(s, d)
d={}; for k,v in s:gmatch("(%S+)=(%S+)") do d[k]=coerce(v) end; return d end
-- cli(settings:d) -> settings;; For CLI args, call a `go` or update settings.
function cli(settings)
for i,s in pairs(arg) do
if go[s]
then go[s](coerce(arg[i+1]))
else
for k,_ in pairs(settings) do
if k:sub(1,1)==s:sub(2) then settings[k]=coerce(arg[i+1]) end end end end
return settings end
-- ## Demos/ tests/ start-up functions
-- These `go` functions override the default variable setting.
go["-h"] = function(_) print(help) end
go["-s"] = function(n) the.seed=n; math.randomseed(n) end
the = settings(help)
math.randomseed(the.seed)
if arg[0]:find"lua7a.lua" then
the = cli(the)
local data1 = Data(the.file)
local data2 = data1:clone()
for _,row in pairs(data1.rows) do data2:add(row) end
print(cat(data1.cols.x[2]))
print(data2.cols.x[2]) end
return {Data=Data, Cols=Cols, Num=Num, Sym=Sym, adds=adds, clone=clone,
sparkline=sparkline, new=new, push=push, map=map, sort=sort, lt=lt,
coerce=coerce, cells=cells, csv=csv, fmt=fmt, cat=cat, the=the, help=help}
.SILENT:
docs: ~/tmp/lua7a.html
locs:
cat lua7a.lua \
| gawk '/^function/ { fun=NR } \
fun && /^[ \t]*$$/ { print NR-fun; fun=0 }' \
| sort -n | fmt -20
~/tmp/%.html: %.lua brain.png header.md
cp brain.png ~/tmp
cat $< | gawk 'BEGIN { FS=";;"} \
NR==1 { system("cat header.md") ; next } \
NF==2 && sub(/^-- /,"",$$1) { \
$$0= "-- <b>"$$1"</b><br>"$$2} 1' > ~/tmp/$<
cd ~/tmp; pycco -d ~/tmp $<
echo "p {text-align:right;}" >> ~/tmp/pycco.css
echo "h2 {padding-top: 3px; border-top: 1px solid #000;}" >> ~/tmp/pycco.css
#!/usr/bin/env gawk -f
BEGIN { SEED=1
ERA=100 }
NR==1 { srand(SEED); print }
NR >1 { a[rand()]=$0; if (length(a) > ERA) dump(a) }
END { dump(a) }
function dump(a, i) {
for(i in a) print a[i]
delete a }
function same(x,y, small, se)
if x.n < 2 or y.n < 2 then return false end
se = (x.sd^2/x.n + y.sd^2/y.n)^0.5 -- standard error of difference
return math.abs(x.mu - y.mu) < (small or 0.35) * se
end
-- local sqrt,max,abs,floor = math.sqrt,math.max,math.abs,math.floor
--
-- function bchop(lst,goal,fn, lo,hi,mid)
-- fn = fn or function(x) return x[1] end
-- lo,hi = 1,#lst
-- while hi-lo>1 do
-- mid = floor((lo+hi)/2)
-- if fn(lst[mid]) <= goal then lo=mid else hi=mid end end
-- return lo,hi end
--
-- local T95 = {{2,4.30},{3,3.18},{4,2.78},{5,2.57},{6,2.45},{7,2.36},
-- {8,2.31}, {9,2.26},{10,2.23},{12,2.18},{15,2.13},{20,2.09},{25,2.06},
-- {30,2.04},{40,2.02},{50,2.01},{60,2.00},{80,1.99},{100,1.98}}
--
-- -- significance test: how uncertain is the difference
-- -- effect size test: how big is the difference relative to typical variation
-- -- significance test: how uncertain is the difference
-- -- effect size test: how big is the difference relative to variation
-- function same(x,y,fxsize,thres) -- Welch t + Hedges g; true = "same"
-- local df,sp,diff,se,se2,cdf,lo,hi,d0,t0,d1,t1,sig,hedges,nx,ny,vx,vy,tcrit
-- fxsize = fxsize or 0.35
-- nx,ny = x.n,y.n; if nx<2 or ny<2 then return false end
-- diff = x.mu - y.mu -- raw mean difference
-- vx,vy = x.sd^2, y.sd^2 -- variances of samples
-- se2 = vx/nx + vy/ny -- Welch variance of mean diff
-- se = sqrt(se2) -- Welch standard error
-- df = se2^2 /
-- ((vx^2)/(nx^2*(nx-1)) + (vy^2)/(ny^2*(ny-1))) -- Welch df
-- cdf = max(2, min(100, df)) -- clamp df to t-table range
-- lo,hi = bchop(thres, cdf)
-- d0,t0 = thres[lo][1],thres[lo][2]
-- d1,t1 = thres[hi][1],thres[hi][2]
-- tcrit = t0 + (t1-t0)*(cdf-d0)/(d1-d0) -- interpolated critical t
-- sp = sqrt(((nx-1)*vx + (ny-1)*vy) / (nx + ny - 2)) -- pooled SD
-- hedges = (diff/sp) * (1 - 3/(4*df - 1)) -- Hedges g (signed)
-- local tooVague = tcrit -- threshold for uncertainty
-- local tooSmall = fxsize -- threshold for magnitude
-- return abs(diff/se) <= tooVague -- statistically same?
-- or abs(hedges) < tooSmall -- practically same?
-- end

Tua: Timm's Lua Annotations

A lightweight type hint system for Lua.

Type Abbreviations

  • s=string, n=number, v=string/number/"?"
  • a=array, d=dictionary, t=table (a|d), i=index
  • as, an = array of strings or numbers
  • row=array of values, col=Num|Sym instance
  • f=function

Function Signatures

Format: name(param=default:type, ...) -> returnType;; docstring

Examples:

  • Sym(pos=0:n, txt="":s) -> SYM;; Symbolic column constructor
  • csv(file:s) -> iterator<row>;; Returns iterator over CSV rows
  • DATA:distx(row1:row, row2:row) -> n;; Normalized X-distance

Naming Conventions

  • UPPERCASE names (e.g., DATA) denote class/type tables; matching CamelCase functions (e.g., Data) are constructors
  • CONSTRUCTORS (e.g.. `Data') are functions named for a class/type, but a single leading letter.
  • Instances use lowercase versions of constructor names (e.g., data, data1)
  • Leading underscore marks internal/private helpers (e.g., _a2as, _d2as)

Syntax Conventions

  • ;; separates signature from docstring in comments
  • Trailing locals after extra whitespace are function-local variables (e.g., function csv(file, i,f))
  • Union types use | (e.g., t|f|s = table or function or string)
  • Generic containers use <> (e.g., iterator<row>, list<v>)

Philosophy

Types added only when needed—not for obvious cases like add(n), but clarifying for csv(file:s).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment