diff --git a/lib/minikanren/relations.sx b/lib/minikanren/relations.sx index 4d78f1e1..768dff92 100644 --- a/lib/minikanren/relations.sx +++ b/lib/minikanren/relations.sx @@ -81,3 +81,18 @@ (conde ((nullo l) (nullo p)) ((fresh (a d perm-d) (conso a d l) (permuteo d perm-d) (inserto a perm-d p)))))) + +(define + flatteno + (fn + (tree flat) + (conde + ((nullo tree) (nullo flat)) + ((pairo tree) + (fresh + (h t hf tf) + (conso h t tree) + (flatteno h hf) + (flatteno t tf) + (appendo hf tf flat))) + ((nafc (nullo tree)) (nafc (pairo tree)) (== flat (list tree)))))) diff --git a/lib/minikanren/tests/flatteno.sx b/lib/minikanren/tests/flatteno.sx new file mode 100644 index 00000000..4a01780a --- /dev/null +++ b/lib/minikanren/tests/flatteno.sx @@ -0,0 +1,42 @@ +(mk-test "flatteno-empty" (run* q (flatteno (list) q)) (list (list))) + +(mk-test + "flatteno-atom" + (run* q (flatteno 5 q)) + (list (list 5))) + +(mk-test + "flatteno-flat-list" + (run* q (flatteno (list 1 2 3) q)) + (list (list 1 2 3))) + +(mk-test + "flatteno-singleton" + (run* q (flatteno (list 1) q)) + (list (list 1))) + +(mk-test + "flatteno-nested-once" + (run* + q + (flatteno (list 1 (list 2 3) 4) q)) + (list (list 1 2 3 4))) + +(mk-test + "flatteno-nested-twice" + (run* + q + (flatteno + (list + 1 + (list 2 (list 3 4)) + 5) + q)) + (list (list 1 2 3 4 5))) + +(mk-test + "flatteno-keywords" + (run* q (flatteno (list :a (list :b :c) :d) q)) + (list (list :a :b :c :d))) + +(mk-tests-run!) diff --git a/plans/minikanren-on-sx.md b/plans/minikanren-on-sx.md index 4151863d..1b71ac0f 100644 --- a/plans/minikanren-on-sx.md +++ b/plans/minikanren-on-sx.md @@ -111,7 +111,11 @@ Key semantic mappings: - [x] `caro` / `cdro` / `conso` / `firsto` / `resto` - [x] `reverseo` `l` `r` — reverse of list. Forward is fast; backward is `run 1`-clean, `run*` diverges due to interleaved unbounded list search (canonical TRS issue). -- [ ] `flatteno` `l` `f` — flatten nested lists (deferred — needs atom predicate) +- [x] `flatteno` `l` `f` — flatten nested lists. Three conde clauses: + empty → empty; pair → recurse on car & cdr + appendo; otherwise atom → + `(== flat (list tree))`. Atom-ness is asserted via `nafc (nullo tree) + + nafc (pairo tree)` — both succeed only when tree is a ground non-list. + Works on ground inputs. - [x] `permuteo` `l` `p` — permutation of list. Built on `inserto` (insert at any position) and recursive permutation of tail. All 6 perms of (1 2 3) generated forward; backward `run 1 q (permuteo q (a b c))` finds the input. @@ -169,6 +173,11 @@ _(none yet)_ _Newest first._ +- **2026-05-08** — **flatteno**: nested-list flattener via conde + appendo. + Atom-ness is detected via `nafc (nullo ...) + nafc (pairo ...)` so the + third clause only fires when the input is a ground non-list. Works for + `()` / atoms / flat lists / arbitrarily-nested lists. 7 new tests, + 253/253 cumulative. Phase 4 list relations all complete. - **2026-05-08** — **Laziness tests**: explicitly verifies the Zzz-on-conde-clauses + mk-mplus-swap-on-paused machinery: an infinitely-recursive relation truncated via `run 4 q (listo-aux q)`