Implement Ranks — gapped fractional ordering
Pure ordering math: append at max+1024, head-insert at min−1024, midpoint insertion with precision-exhaustion detection (nil on ties and rounding onto an endpoint), renumbering to whole multiples of 1024, the shared display-order tie-break (order, then folder name), and tombstone-excluding overloads. 18 tests. Claude-Session: https://claude.ai/code/session_018BjQRYBR6jQja3jCRi5S3A
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
import Foundation
|
||||
|
||||
/// Pure gapped-fractional-ordering math: append, insert, midpoint, and the
|
||||
/// renumber target, plus the display-order tie-break rule shared by the
|
||||
/// loader and the writer. No filesystem or model dependency — see
|
||||
/// DESIGN/01-storage-format.md § Ordering, Deletion.
|
||||
enum Ranks: Sendable {
|
||||
|
||||
/// Gap between successive ranks on append/head-insert, and the multiple
|
||||
/// used by `renumbered(count:)`.
|
||||
private static let gap: Double = 1024
|
||||
|
||||
// MARK: - Append / insert
|
||||
|
||||
/// Rank for a new item appended after all visible siblings.
|
||||
/// An empty lane's first item lands at `1024` (the board convention).
|
||||
static func append(toVisible orders: some Sequence<Double>) -> Double {
|
||||
(orders.max() ?? 0) + gap
|
||||
}
|
||||
|
||||
/// Rank for a new item inserted before all visible siblings.
|
||||
/// An empty lane's first item lands at `1024` (the board convention).
|
||||
static func insertAtHead(ofVisible orders: some Sequence<Double>) -> Double {
|
||||
guard let minOrder = orders.min() else { return gap }
|
||||
return minOrder - gap
|
||||
}
|
||||
|
||||
/// Rank strictly between `a` and `b`, or `nil` if no `Double` is
|
||||
/// representable between them — including when `a == b` (a duplicate
|
||||
/// order, the tie case). Never returns a value ≤ min(a, b) or
|
||||
/// ≥ max(a, b).
|
||||
static func midpoint(between a: Double, and b: Double) -> Double? {
|
||||
let lower = min(a, b)
|
||||
let upper = max(a, b)
|
||||
guard lower < upper else { return nil }
|
||||
let mid = lower + (upper - lower) / 2
|
||||
guard mid > lower, mid < upper else { return nil }
|
||||
return mid
|
||||
}
|
||||
|
||||
/// `count` fresh ranks, whole multiples of 1024 in ascending order
|
||||
/// (1024, 2048, …) — the renumber target when midpoint precision is
|
||||
/// exhausted. Deterministic by construction; the writer applies these,
|
||||
/// in order, to the current visible siblings in display order.
|
||||
static func renumbered(count: Int) -> [Double] {
|
||||
guard count > 0 else { return [] }
|
||||
return (1...count).map { Double($0) * gap }
|
||||
}
|
||||
|
||||
// MARK: - Display order
|
||||
|
||||
/// Ascending display order: primary key `order`, ties broken by folder
|
||||
/// name (lexicographic) for deterministic rendering. Shared by the
|
||||
/// loader and the writer so both apply the same tie-break rule.
|
||||
static func isOrderedForDisplay<T>(
|
||||
_ lhs: T, before rhs: T,
|
||||
order: (T) -> Double, name: (T) -> String
|
||||
) -> Bool {
|
||||
let lhsOrder = order(lhs)
|
||||
let rhsOrder = order(rhs)
|
||||
return lhsOrder != rhsOrder ? lhsOrder < rhsOrder : name(lhs) < name(rhs)
|
||||
}
|
||||
|
||||
/// Sorts siblings into display order — ascending `order`, ties broken by
|
||||
/// folder name.
|
||||
static func sortedForDisplay<T>(
|
||||
_ items: [T],
|
||||
order: (T) -> Double,
|
||||
name: (T) -> String
|
||||
) -> [T] {
|
||||
items.sorted { isOrderedForDisplay($0, before: $1, order: order, name: name) }
|
||||
}
|
||||
|
||||
// MARK: - Tombstone exclusion
|
||||
|
||||
/// `append(toVisible:)`, ignoring tombstoned siblings. Tombstones are
|
||||
/// inert to ordering — appends operate on visible siblings only.
|
||||
static func append(toVisible items: some Sequence<(order: Double, isDeleted: Bool)>) -> Double {
|
||||
append(toVisible: items.filter { !$0.isDeleted }.map { $0.order })
|
||||
}
|
||||
|
||||
/// `insertAtHead(ofVisible:)`, ignoring tombstoned siblings. Tombstones
|
||||
/// are inert to ordering — inserts operate on visible siblings only.
|
||||
static func insertAtHead(ofVisible items: some Sequence<(order: Double, isDeleted: Bool)>) -> Double {
|
||||
insertAtHead(ofVisible: items.filter { !$0.isDeleted }.map { $0.order })
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user