stdlib_collections.kt
src/jvmMain/kotlin/afield/data/collections/stdlib_collections.kt · 203 lines
package and imports — 77 lines, from line 1
@file:afield.runtime.completion.CompletionCategory("Workspace")
@file:Suppress("UNCHECKED_CAST")
package afield.data.collections
import afield.geometry.mesh.repeated
import afield.graphics.render.rangeTo
import afield.graphics.vulkan.buffers.add
import afield.lang.stacksl.h
import afield.math.autodiff.compareTo
import afield.math.autodiff.cos
import afield.math.autodiff.div
import afield.math.autodiff.minus
import afield.math.autodiff.plus
import afield.math.autodiff.sin
import afield.math.units.div
import afield.math.units.minus
import afield.math.units.plus
import afield.math.autodiff.ln
import afield.math.linalg.i
import afield.simulation.automata.slice
import afield.math.linalg.Vector2f
import afield.math.linalg.Vector3f
import afield.math.linalg.Vector4f
import afield.math.linalg.Matrix4f
import afield.math.linalg.Matrix3f
import afield.math.linalg.Quaternionf
import afield.ai.agent.*
import afield.ai.conversation.*
import afield.ai.tools.*
import afield.animation.gesture.*
import afield.animation.interpolation.*
import afield.animation.keyframes.*
import afield.app.diagnostics.*
import afield.app.notifications.*
import afield.app.protocol.*
import afield.app.server.*
import afield.app.settings.*
import afield.audio.synth.*
import afield.data.table.*
import afield.document.context.*
import afield.document.graph.*
import afield.document.history.*
import afield.document.persistence.*
import afield.document.properties.*
import afield.document.workspace.*
import afield.graphics.color.*
import afield.graphics.paint.*
import afield.graphics.vulkan.*
import afield.math.functions.*
import afield.media.capture.*
import afield.media.stream.*
import afield.runtime.completion.*
import afield.runtime.execution.*
import afield.runtime.kotlin.*
import afield.runtime.lifecycle.*
import afield.tools.automation.*
import afield.tools.docs.*
import afield.ui.controls.*
import afield.ui.layout.*
import afield.ui.notation.*
import afield.ui.output.*
import afield.ui.output.templates.*
import afield.ui.slides.*
import afield.ui.visualization.*
import afield.app.server.Server.Companion.stop
import afield.graphics.vulkan.util.FramePresentationLog
import afield.graphics.vulkan.util.Options
import afield.graphics.vulkan.util.floatToHalfBits
import kotlinx.coroutines.DelicateCoroutinesApi
import afield.math.linalg.*
import java.nio.BufferOverflowException
import java.nio.ByteBuffer
import java.nio.FloatBuffer
import java.nio.IntBuffer
import kotlin.reflect.KProperty
Loop, collection and string shorthands, and the truthiness test for a raw property value.
10 { i -> ... } and (0..9) { i -> ... } call a block once per index and collect the results,
and 20 alphas { a -> ... } does the same over evenly spaced values from 0.0 to 1.0 inclusive,
which is the loop that parameterises a curve so that it closes. The rest are conversions and
slices: list[2..5] for an inclusive copy, List.m and List.s for a mutable copy and a set,
String.r for a compiled Regex, "ab" * 3 for repetition. isTrue reads an Any? as a yes
or a no, and is true only for true, a non-zero Int and a non-empty String.
One of the stdlib files, which the intro preamble star-imports into every box, so everything
here can be called unqualified, with Box as the implicit receiver.
fun Any?.isTrue(): Boolean
JavaScript-ish truthiness for a value out of a property bag, where you have an Any? and want a
yes/no.
True for true, for a non-zero Int, and for a non-empty String. Everything else — null, false,
0, and every other type, including a non-empty list or a Long of 1 — is false. That last part is
the trap: this is not a general-purpose "is set" test.
fun Any?.isTrue(): Boolean {
if (this == null) return false
return when (this) {
is Boolean -> this
is Int -> if (this != 0) return true else false
is String -> this.length > 0
else -> false
}
}
inline operator fun <T> IntRange.invoke(to: (Int) -> T): List<T> = this.map { to(it) }
(0..9) { i -> … } — calls the block once per value in the range and collects the results.
map written as a call, which reads better when the whole point of the loop is to build geometry:
(0..9) { i -> vec(i, 0) }. Inclusive of both ends, like any Kotlin range.
inline operator fun <T> Int.invoke(to: (Int) -> T): List<T> = (0 until this).map { to(it) }
10 { i -> … } — calls the block for i in 0 until 10 and collects the results.
The terse "do this n times and keep the answers" form. Note the range is exclusive of the number
itself, so 10 { it } is 0..9.
inline infix fun <T> Int.alphas(to: (Double) -> T): List<T> = (0 until this).map { to(it / (this - 1.0)) }
10 alphas { a -> … } — calls the block with that many evenly spaced values from 0.0 to 1.0
inclusive, and collects the results.
The parameterise-a-curve loop: 20 alphas { a -> vec(cos(a * 2 * PI), sin(a * 2 * PI)) } closes exactly,
because the last value really is 1.0. Beware 1 alphas { }: it divides zero by zero and hands the block
a single NaN — which will not be caught by an isInfinite guard. The no-argument alphas does not
have that edge.
inline infix fun <T> Int.alphas(to: (Double) -> T): List<T> = (0 until this).map { to(it / (this - 1.0)) }
fun Int.alphas(): SizedIterable<Double>
This many values evenly spaced from 0.0 to 1.0 inclusive, as a lazy sized sequence.
for (a in 20.alphas()) when you want the fractions themselves rather than a mapped list. Nothing is
allocated per element, and the size is known up front, so it can drive a buffer allocation.
Two edges to know: 1.alphas() yields a single 0.0 (rather than the NaN the infix alphas would
give), and a negative count yields nothing instead of throwing.
fun Int.alphas(): SizedIterable<Double> {
val n = this.coerceAtLeast(0)
return sizedIterable(n) {
var i = 0
object : Iterator<Double> {
override fun hasNext(): Boolean = i < n
override fun next(): Double {
if (!hasNext()) throw NoSuchElementException("No more alpha values")
val value = if (n <= 1) 0.0 else i / (n - 1.0)
i++
return value
}
}
}
}
operator fun String.times(ln: Int): String
"ab" * 3 — the string repeated ln times. Zero or fewer gives the empty string.
operator fun String.times(ln: Int): String {
// Not just an early-out: `StringBuilder(negative)` throws NegativeArraySizeException before the loop
// runs, so without this `"ab" * -1` crashed instead of returning the documented empty string.
if (ln <= 0) return ""
var s = StringBuilder(this.length * ln)
val thisString = this // why has this@String stopped working?
ln { s.append(this) }
return s.toString()
}
operator fun <T> List<T>.get(range: IntRange) = this.slice(range)
list[2..5] — a new list of the elements in that inclusive range, Python-slice syntax with Kotlin's range
semantics.
A copy, not a view. Throws IndexOutOfBoundsException if the range runs off the end, unlike Python, which
would return a short list with no error.
val <T> List<T>.m: MutableList<T>
This list as a MutableList — a copy, so editing it leaves the original alone.
val <T> List<T>.m: MutableList<T>
get() = this.toMutableList()
val <T> List<T>.s: Set<T>
This list as a Set, dropping duplicates and keeping first-seen order. A copy.
val <T> List<T>.s: Set<T>
get() = this.toSet()
val String.r: Regex
This string compiled as a Regex — "\\d+".r.
Compiles on every read, so hoist it out of a loop. Throws PatternSyntaxException on a malformed pattern.
val String.r: Regex
get() = Regex(this)