feat(stdlib): sorting toolkit — sort_by/sort_desc_by/sort_with + stable merge sort (#11)
Some checks are pending
docs / build-and-deploy (push) Waiting to run
Some checks are pending
docs / build-and-deploy (push) Waiting to run
Grow List sorting from a numeric-only insertion sort into a small, game-friendly toolkit that sorts records and query results by a key or a full comparator, stably and in O(n log n). - List.sort_by(s, keyfn) ascending by a key (draw order, price) - List.sort_desc_by(s, keyfn) descending (leaderboards) - List.sort_with(s, cmpfn) full cmp(a,b)->int comparator (multi-field) Comparators/keys are passed as named top-level functions rather than lambdas, so the toolkit ships without waiting on closures (#1). Engine: a stable bottom-up merge sort. emit_takeright is the single place stability is decided ("take the right run's head only on a strict win" -> equal keys keep prior order). List.sort becomes a hybrid: insertion sort for n<32, merge sort above; both stable, so output is unchanged. Key functions must return an integer-ish type; record slices hold pointer elements, so the key/comparator receives the record pointer. Tests: selfhost/tests/sort.ludic (scalar large-n, sort_by, sort_desc_by, stability, sort_with). Docs: list-sort_by/desc_by/with + updated list-sort. All suites green (28 self-host / 46 test / 29 test-tools); reseeded, C-free bootstrap fixpoint holds. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
b5455cd550
commit
4aa2012231
8 changed files with 9622 additions and 7747 deletions
|
|
@ -11,7 +11,7 @@ ns: List
|
|||
member: sort
|
||||
---
|
||||
|
||||
Sorts the elements of <code>s</code> in ascending order in place, using an insertion sort. It is intended for slices of scalar elements (ints, entities, fixed) where <code><</code> is meaningful; reference elements are ordered by identity, which is rarely useful. Insertion sort is simple and fast for the small, nearly-sorted lists games usually hold.
|
||||
Sorts the elements of <code>s</code> in ascending order in place. It is intended for slices of scalar elements (ints, entities, fixed) where <code><</code> is meaningful; reference elements are ordered by identity, which is rarely useful. Small lists — the nearly-sorted, per-frame kind games usually hold — take a simple insertion sort; larger lists fall to a stable O(n log n) merge sort. Both are stable, so the result is identical either way. To sort records by a field, or in descending order, or with a custom comparator, use <code>List.sort_by</code>, <code>List.sort_desc_by</code>, or <code>List.sort_with</code>.
|
||||
|
||||
```ludic
|
||||
program Demo {
|
||||
|
|
|
|||
25
docs/language/list/list-sort_by.md
Normal file
25
docs/language/list/list-sort_by.md
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
---
|
||||
id: list-sort_by
|
||||
name: List.sort_by
|
||||
category: list
|
||||
kind: namespace-method
|
||||
tokens: List.sort_by
|
||||
sig: List.sort_by(s, key) -> void
|
||||
tip: Sort a slice ascending by a key each element maps to.
|
||||
order: 14
|
||||
ns: List
|
||||
member: sort_by
|
||||
---
|
||||
|
||||
Sorts <code>s</code> in place, ascending, by the number each element maps to through <code>key</code> — a function taking one element and returning an <code>int</code>, <code>fixed</code>, or <code>long</code>. This is how you sort records and entities rather than bare scalars: draw order by <code>y</code>, an inventory by price, nearest targets by distance. The sort is <strong>stable</strong> (O(n log n) merge sort), so elements with equal keys keep the order they already had — the tie-break that keeps sprites from flickering when their <code>y</code> matches.
|
||||
|
||||
```ludic
|
||||
# doc-check: skip — illustrative; `key` is any fn(element) -> number in scope
|
||||
function draw_key(e: Sprite) -> int { return e.y }
|
||||
|
||||
program Demo {
|
||||
handler Step phase Update {
|
||||
List.sort_by(sprites, draw_key) # back-to-front draw order
|
||||
}
|
||||
}
|
||||
```
|
||||
25
docs/language/list/list-sort_desc_by.md
Normal file
25
docs/language/list/list-sort_desc_by.md
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
---
|
||||
id: list-sort_desc_by
|
||||
name: List.sort_desc_by
|
||||
category: list
|
||||
kind: namespace-method
|
||||
tokens: List.sort_desc_by
|
||||
sig: List.sort_desc_by(s, key) -> void
|
||||
tip: Sort a slice descending by a key each element maps to.
|
||||
order: 15
|
||||
ns: List
|
||||
member: sort_desc_by
|
||||
---
|
||||
|
||||
Like <code>List.sort_by</code>, but descending — highest key first. The everyday case is a leaderboard: sort players by score so the top scorer lands at index 0. <code>key</code> is a function taking one element and returning a number (<code>int</code>, <code>fixed</code>, or <code>long</code>). The sort is <strong>stable</strong> (O(n log n) merge sort): players tied on score keep their prior order, so a stable tie-break (say, who reached the score first) survives the sort.
|
||||
|
||||
```ludic
|
||||
# doc-check: skip — illustrative; `key` is any fn(element) -> number in scope
|
||||
function score_of(p: Player) -> int { return p.score }
|
||||
|
||||
program Demo {
|
||||
handler Step phase Update {
|
||||
List.sort_desc_by(players, score_of) # players[0] is the leader
|
||||
}
|
||||
}
|
||||
```
|
||||
25
docs/language/list/list-sort_with.md
Normal file
25
docs/language/list/list-sort_with.md
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
---
|
||||
id: list-sort_with
|
||||
name: List.sort_with
|
||||
category: list
|
||||
kind: namespace-method
|
||||
tokens: List.sort_with
|
||||
sig: List.sort_with(s, cmp) -> void
|
||||
tip: Sort a slice with a full two-argument comparator.
|
||||
order: 16
|
||||
ns: List
|
||||
member: sort_with
|
||||
---
|
||||
|
||||
Sorts <code>s</code> in place with a full comparator <code>cmp(a, b) -> int</code>: return a negative number when <code>a</code> should come before <code>b</code>, positive when after, and zero when they tie. Reach for this when the order is not a single key — a multi-field sort (by rarity, then by name), or a comparison that mixes fields. For the common "subtract two numbers" comparator, returning <code>a.field - b.field</code> gives ascending order. The sort is <strong>stable</strong> (O(n log n) merge sort), so ties (a zero result) keep the elements' prior order.
|
||||
|
||||
```ludic
|
||||
# doc-check: skip — illustrative; `cmp` is any fn(a, b) -> int in scope
|
||||
function by_price(a: Item, b: Item) -> int { return a.price - b.price }
|
||||
|
||||
program Demo {
|
||||
handler Step phase Update {
|
||||
List.sort_with(shop, by_price) # cheapest first
|
||||
}
|
||||
}
|
||||
```
|
||||
Loading…
Add table
Add a link
Reference in a new issue