Let's say we want to make sure that a small number of separate values/variables/constants (ie not an Array
) are ordered (because eg the remaining code becomes easier to write if a
b
and c
are known to be in ascending order).
For two elements it's just:
let (a, b) = p > q ? (q, p) : (p, q)
or ( for mutable p q ):
if p > q { swap(&p, &q) }
For three elements it's not as straight forward, but if we know that our code won't be executed super frequently and just want to get it done, we might write something like:
let tmpAr = [p, q, r].sorted()
let (a, b, c) = (tmpAr[0], tmpAr[1], tmpAr[2])
or ( for mutable p q r ):
if p > q { swap(&p, &q) }
if q > r { swap(&q, &r) }
if p > q { swap(&p, &q) }
It's a lot of code for something as simple as that, it might be hard to spot a mistake in it and if efficiency matters we would need to spend some more time on improving it.
More than a couple of times, I've found myself needing this kind of sorting (for 3 or 4 elements).
Here's an example, for 2 and 3 values.
extension Disk {
/// Returns the smallest disk enclosing two other disks `a` and `b`.
init(smallestDiskEnclosing a: Disk, and b: Disk) {
// Start by sorting the disks by ascending radius, smallest first:
let (a, b) = a.radius < b.radius ? (a, b) : (b, a)
let cDist = simd_distance(a.center, b.center)
// If one of the disks (b) encloses the other (a), we are done:
guard cDist + a.radius > b.radius else { self = b; return }
let enclosingDiskR = (cDist + a.radius + b.radius) * 0.5
let t = (enclosingDiskR - a.radius) / cDist
self = Disk(center: simd_mix(a.center, b.center, .init(t, t)),
radius: enclosingDiskR)
}
/// Returns the smallest disk enclosing three other disks `a`, `b`, and `c`.
init(smallestDiskEnclosing a: Disk, b: Disk, and c: Disk) {
// Start by sorting the disks by ascending radius, smallest first:
let (a, b, c) = ...
// ...
}
/// Returns the smallest disk enclosing a sequence of disks.
init(smallestDiskEnclosing: S) where S: Sequence, S.Element == Disk {
// Problem is solved recursively (terminal case is 3 disks or less) ...
// ...
}
}
It would be nice to solve this once and for all, and why not in the form of a group challenge here?
Challange:
Come up with something that makes this kind of sorting, for 2, 3 and 4 elements (of the same type), nice and efficient!
Ideally, it should probably support both immutable and mutable (inout
) usage, perhaps even unsafe usage on raw memory (for sorting eg successive chunks of four Float
values in a big memory block).
I haven't thought much about design, but perhaps something like this on the call site:
let (a, b, c) = sorted(p, q, r) // ascending order
let (a, b, c) = sorted(p, q, r, by: >) // descending order
sort(&p, &q, &r)
sort(&p, &q, &r, by: >)
and maybe even something like:
sort4(elementsOfType: Float.self, startingAt: ptr)