Compare commits

...

2 Commits

Author SHA1 Message Date
0ec0ed96ee sorted* moved to Iterable
in-place List sort
v0.8.15-SNAPSHOT
2025-08-23 22:53:10 +03:00
a5e51a3f90 more tests 2025-08-23 09:41:23 +03:00
12 changed files with 335 additions and 75 deletions

View File

@ -6,6 +6,12 @@ Is a [Iterable] with known `size`, a finite [Iterable]:
val size val size
} }
| name | description |
|------------------------|------------------------------------------------------|
(1)
: `comparator(a,b)` should return -1 if `a < b`, +1 if `a > b` or zero.
See [List], [Set] and [Iterable] See [List], [Set] and [Iterable]
[Iterable]: Iterable.md [Iterable]: Iterable.md

View File

@ -1,9 +1,13 @@
# Iterable interface # Iterable interface
The interface for anything that can be iterated, e.g. finite or infinite ordered set of data that can be accessed sequentially. Almost any data container in `Lyng` implements it: `List`, `Set`, `Buffer`, `RingBuffer`, `BitBuffer`, `Range` and many others are `Iterable`, also `Collection` and `Array` interfaces inherit it. The interface for anything that can be iterated, e.g. finite or infinite ordered set of data that can be accessed
sequentially. Almost any data container in `Lyng` implements it: `List`, `Set`, `Buffer`, `RingBuffer`, `BitBuffer`,
`Range` and many others are `Iterable`, also `Collection` and `Array` interfaces inherit it.
`Map` and `String` have `Iterable` members to access its contents too. `Map` and `String` have `Iterable` members to access its contents too.
Please see also [Collection] interface: many iterables are also collections, and it adds important features.
## Definition: ## Definition:
Iterable is a class that provides function that creates _the iterator_: Iterable is a class that provides function that creates _the iterator_:
@ -23,7 +27,8 @@ Iterator itself is a simple interface that should provide only to method:
Just remember at this stage typed declarations are not yet supported. Just remember at this stage typed declarations are not yet supported.
Having `Iterable` in base classes allows to use it in for loop. Also, each `Iterable` has some utility functions available, for example Having `Iterable` in base classes allows to use it in for loop. Also, each `Iterable` has some utility functions
available, for example
val r = 1..10 // Range is Iterable! val r = 1..10 // Range is Iterable!
assertEquals( [9,10], r.takeLast(2).toList() ) assertEquals( [9,10], r.takeLast(2).toList() )
@ -34,7 +39,8 @@ Having `Iterable` in base classes allows to use it in for loop. Also, each `Iter
## joinToString ## joinToString
This methods convert any iterable to a string joining string representation of each element, optionally transforming it and joining using specified suffix. This methods convert any iterable to a string joining string representation of each element, optionally transforming it
and joining using specified suffix.
Iterable.joinToString(suffux=' ', transform=null) Iterable.joinToString(suffux=' ', transform=null)
@ -49,34 +55,56 @@ Here is the sample:
assertEquals( (1..3).joinToString { it * 10 }, "10 20 30") assertEquals( (1..3).joinToString { it * 10 }, "10 20 30")
>>> void >>> void
## `sum` and `sumBy`
These, again, does the thing:
assertEquals( 6, [1,2,3].sum() )
assertEquals( 12, [1,2,3].sumOf { it*2 } )
// sum of empty collections is null:
assertEquals( null, [].sum() )
assertEquals( null, [].sumOf { 2*it } )
>>> void
## Instance methods: ## Instance methods:
| fun/method | description |
| fun/method | description | |------------------------|---------------------------------------------------------------------------------|
|-------------------|---------------------------------------------------------------------------| | toList() | create a list from iterable |
| toList() | create a list from iterable | | toSet() | create a set from iterable |
| toSet() | create a set from iterable | | contains(i) | check that iterable contains `i` |
| contains(i) | check that iterable contains `i` | | `i in iterator` | same as `contains(i)` |
| `i in iterator` | same as `contains(i)` | | isEmpty() | check iterable is empty |
| isEmpty() | check iterable is empty | | forEach(f) | call f for each element |
| forEach(f) | call f for each element | | toMap() | create a map from list of key-value pairs (arrays of 2 items or like) |
| toMap() | create a map from list of key-value pairs (arrays of 2 items or like) | | map(f) | create a list of values returned by `f` called for each element of the iterable |
| map(f) | create a list of values returned by `f` called for each element of the iterable | | indexOf(i) | return index if the first encounter of i or a negative value if not found |
| indexOf(i) | return index if the first encounter of i or a negative value if not found | | associateBy(kf) | create a map where keys are returned by kf that will be called for each element |
| associateBy(kf) | create a map where keys are returned by kf that will be called for each element | | first | first element (1) |
| first | first element (1) | | last | last element (1) |
| last | last element (1) | | take(n) | return [Iterable] of up to n first elements |
| take(n) | return [Iterable] of up to n first elements | | taleLast(n) | return [Iterable] of up to n last elements |
| taleLast(n) | return [Iterable] of up to n last elements | | drop(n) | return new [Iterable] without first n elements |
| drop(n) | return new [Iterable] without first n elements | | dropLast(n) | return new [Iterable] without last n elements |
| dropLast(n) | return new [Iterable] without last n elements | | sum() | return sum of the collection applying `+` to its elements (3) |
| joinToString(s,t) | convert iterable to string, see (2) | | sumOf(predicate) | sum of the modified collection items (3) |
| sorted() | return [List] with collection items sorted naturally |
| sortedWith(comparator) | sort using a comparator that compares elements (1) |
| sortedBy(predicate) | sort by comparing results of the predicate function |
| joinToString(s,t) | convert iterable to string, see (2) |
| reversed() | create a list containing items from this in reverse order |
(1) (1)
: throws `NoSuchElementException` if there is no such element : throws `NoSuchElementException` if there is no such element
(2) (2)
: `joinToString(suffix=" ",transform=null)`: suffix is inserted between items if there are more than one, trasnfom is optional function applied to each item that must return result string for an item, otherwise `item.toString()` is used. : `joinToString(suffix=" ",transform=null)`: suffix is inserted between items if there are more than one, trasnfom is
optional function applied to each item that must return result string for an item, otherwise `item.toString()` is used.
(3)
: sum of empty collection is `null`
fun Iterable.toList(): List fun Iterable.toList(): List
fun Iterable.toSet(): Set fun Iterable.toSet(): Set
@ -87,7 +115,6 @@ Here is the sample:
fun Iterable.map(block: (Any?)->Void ): List fun Iterable.map(block: (Any?)->Void ): List
fun Iterable.associateBy( keyMaker: (Any?)->Any): Map fun Iterable.associateBy( keyMaker: (Any?)->Any): Map
## Abstract methods: ## Abstract methods:
fun iterator(): Iterator fun iterator(): Iterator
@ -102,7 +129,12 @@ Creates a list by iterating to the end. So, the Iterator should be finite to be
- [List], [Range], [Buffer](Buffer.md), [BitBuffer], [Buffer], [Set], [RingBuffer] - [List], [Range], [Buffer](Buffer.md), [BitBuffer], [Buffer], [Set], [RingBuffer]
[Collection]: Collection.md
[List]: List.md [List]: List.md
[Range]: Range.md [Range]: Range.md
[Set]: Set.md [Set]: Set.md
[RingBuffer]: RingBuffer.md [RingBuffer]: RingBuffer.md

View File

@ -92,22 +92,44 @@ Open end ranges remove head and tail elements:
assert( [1, 2, 3] !== [1, 2, 3]) assert( [1, 2, 3] !== [1, 2, 3])
>>> void >>> void
## In-place sort
List could be sorted in place, just like [Collection] provide sorted copies, in a very like way:
val l1 = [6,3,1,9]
l1.sort()
assertEquals( [1,3,6,9], l1)
l1.sortBy { -it }
assertEquals( [1,3,6,9].reversed(), l1)
l1.sort() // 1 3 6 9
l1.sortBy { it % 4 }
// 1,3,6,9 gives, mod 4:
// 1 3 2 1
// we hope we got it also stable:
assertEquals( [1,9,6,3], l1)
>>> void
## Members ## Members
| name | meaning | type | | name | meaning | type |
|-------------------------------|---------------------------------------|-------------| |-------------------------------|----------------------------------------------|-------------|
| `size` | current size | Int | | `size` | current size | Int |
| `add(elements...)` | add one or more elements to the end | Any | | `add(elements...)` | add one or more elements to the end | Any |
| `insertAt(index,elements...)` | insert elements at position | Int, Any | | `insertAt(index,elements...)` | insert elements at position | Int, Any |
| `removeAt(index)` | remove element at position | Int | | `removeAt(index)` | remove element at position | Int |
| `remove(from,toNonInclusive)` | remove range from (incl) to (nonincl) | Int, Int | | `remove(from,toNonInclusive)` | remove range from (incl) to (nonincl) | Int, Int |
| `remove(Range)` | remove range | Range | | `remove(Range)` | remove range | Range |
| `removeLast()` | remove last element | | | `removeLast()` | remove last element | |
| `removeLast(n)` | remove n last elements | Int | | `removeLast(n)` | remove n last elements | Int |
| `contains(element)` | check the element is in the list (1) | | | `contains(element)` | check the element is in the list (1) | |
| `[index]` | get or set element at index | Int | | `[index]` | get or set element at index | Int |
| `[Range]` | get slice of the array (copy) | Range | | `[Range]` | get slice of the array (copy) | Range |
| `+=` | append element(s) | List or Obj | | `+=` | append element(s) (2) | List or Obj |
| `sort()` | in-place sort, natural order | void |
| 'sortBy(predicate)` | in place sort bu `predicate` call result (3) | void |
| `SortWith(comparator) | in place sort using `comarator` function (4) | void |
(1) (1)
: optimized implementation that override `Array` one : optimized implementation that override `Array` one
@ -116,7 +138,16 @@ Open end ranges remove head and tail elements:
: `+=` append either a single element, or all elements if the List or other Iterable : `+=` append either a single element, or all elements if the List or other Iterable
instance is appended. If you want to append an Iterable object itself, use `add` instead. instance is appended. If you want to append an Iterable object itself, use `add` instead.
It inherits from [Iterable] too. (3)
: predicate is called on each element, and the returned values are used to sort in natural
order, e.g. is same as `list.sortWith { a,b -> predicate(a) <=> predicate(b) }`
(4)
: comparator callable takes tho arguments and must return: negative value when first is less,
positive if first is greater, and zero if they are equal. For example, the equvalent comparator
for `sort()` will be `sort { a, b -> a <=> b }
It inherits from [Iterable] too and thus all iterable methods are applicable to any list.
## Member inherited from Array ## Member inherited from Array
@ -134,4 +165,5 @@ It inherits from [Iterable] too.
[Range]: Range.md [Range]: Range.md
[Iterable]: Iterable.md [Iterable]: Iterable.md

View File

@ -16,9 +16,15 @@ fun naiveCountHappyNumbers() {
count count
} }
import lyng.time
// //
// After all optimizations it takes ~120ms. // After all optimizations it takes ~120ms.
// //
val found = naiveCountHappyNumbers() //for( r in 1..100 ) {
println("Found happy numbers: "+found) // val start = Instant.now()
assert( found == 55252 ) val found = naiveCountHappyNumbers()
// println("Found happy numbers: %d time %s"(found, Instant.now() - start))
assert( found == 55252 )
// delay(0.01)
//}

View File

@ -764,15 +764,15 @@ thrown.
Typical builtin types that are containers (e.g. support `contains`): Typical builtin types that are containers (e.g. support `contains`):
| class | notes | | class | notes |
|------------|------------------------------------------------| |--------------|------------------------------------------------|
| Collection | contains an element (1) | | [Collection] | contains an element (1) |
| Array | faster maybe that Collection's | | Array | faster maybe that Collection's |
| List | faster than Array's | | List | faster than Array's |
| String | character in string or substring in string (3) | | String | character in string or substring in string (3) |
| Range | object is included in the range (2) | | Range | object is included in the range (2) |
| Buffer | byte is in buffer | | Buffer | byte is in buffer |
| RingBuffer | object is in buffer | | RingBuffer | object is in buffer |
(1) (1)
: Iterable is not the container as it can be infinite : Iterable is not the container as it can be infinite
@ -1362,3 +1362,5 @@ See [math functions](math.md). Other general purpose functions are:
[parallelism]: parallelism.md [parallelism]: parallelism.md
[RingBuffer]: RingBuffer.md [RingBuffer]: RingBuffer.md
[Collection]: Collection.md

View File

@ -21,7 +21,7 @@ import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.dsl.JvmTarget
group = "net.sergeych" group = "net.sergeych"
version = "0.8.14-SNAPSHOT" version = "0.8.15-SNAPSHOT"
buildscript { buildscript {
repositories { repositories {

View File

@ -225,7 +225,7 @@ class Compiler(
// very special case chained calls: call()<NL>.call2 {}.call3() // very special case chained calls: call()<NL>.call2 {}.call3()
Token.Type.NEWLINE -> { Token.Type.NEWLINE -> {
val saved = cc.savePos() val saved = cc.savePos()
if( cc.peekNextNonWhitespace().type == Token.Type.DOT) { if (cc.peekNextNonWhitespace().type == Token.Type.DOT) {
// chained call continue from it // chained call continue from it
continue continue
} else { } else {
@ -492,10 +492,11 @@ class Compiler(
// if it is an open end range, then the end of line could be here that we do not want // if it is an open end range, then the end of line could be here that we do not want
// to skip in parseExpression: // to skip in parseExpression:
val current = cc.current() val current = cc.current()
val right = if( current.type == Token.Type.NEWLINE || current.type == Token.Type.SINLGE_LINE_COMMENT) val right =
null if (current.type == Token.Type.NEWLINE || current.type == Token.Type.SINLGE_LINE_COMMENT)
else null
parseExpression() else
parseExpression()
operand = Accessor { operand = Accessor {
ObjRange( ObjRange(
left?.getter?.invoke(it)?.value ?: ObjNull, left?.getter?.invoke(it)?.value ?: ObjNull,
@ -1383,7 +1384,13 @@ class Compiler(
loopIterable(forContext, sourceObj, loopSO, body, elseStatement, label, canBreak) loopIterable(forContext, sourceObj, loopSO, body, elseStatement, label, canBreak)
} else { } else {
val size = runCatching { sourceObj.invokeInstanceMethod(forContext, "size").toInt() } val size = runCatching { sourceObj.invokeInstanceMethod(forContext, "size").toInt() }
.getOrElse { throw ScriptError(tOp.pos, "object is not enumerable: no size", it) } .getOrElse {
throw ScriptError(
tOp.pos,
"object is not enumerable: no size in $sourceObj",
it
)
}
var result: Obj = ObjVoid var result: Obj = ObjVoid
var breakCaught = false var breakCaught = false

View File

@ -18,8 +18,8 @@
package net.sergeych.lyng.obj package net.sergeych.lyng.obj
/** /**
* Collection is an iterator with `size`] * Collection is an iterator with `size`
*/ */
val ObjCollection = ObjClass("Collection", ObjIterable).apply { val ObjCollection = ObjClass("Collection", ObjIterable).apply {
} }

View File

@ -119,12 +119,6 @@ val ObjIterable by lazy {
ObjList(result) ObjList(result)
} }
// addFn("drop" ) {
// var n = requireOnlyArg<ObjInt>().value.toInt()
// if( n < 0 ) raiseIllegalArgument("drop($n): should be positive")
// val it = callMethod<>()
// }
addFn("isEmpty") { addFn("isEmpty") {
ObjBool( ObjBool(
thisObj.invokeInstanceMethod(this, "iterator") thisObj.invokeInstanceMethod(this, "iterator")
@ -132,5 +126,21 @@ val ObjIterable by lazy {
.not() .not()
) )
} }
addFn("sortedWith") {
val list = thisObj.callMethod<ObjList>(this, "toList")
val comparator = requireOnlyArg<Statement>()
list.quicksort { a, b ->
comparator.call(this, a, b).toInt()
}
list
}
addFn("reversed") {
val list = thisObj.callMethod<ObjList>(this, "toList")
list.list.reverse()
list
}
} }
} }

View File

@ -18,6 +18,7 @@
package net.sergeych.lyng.obj package net.sergeych.lyng.obj
import net.sergeych.lyng.Scope import net.sergeych.lyng.Scope
import net.sergeych.lyng.Statement
import net.sergeych.lyng.statement import net.sergeych.lyng.statement
import net.sergeych.lynon.LynonDecoder import net.sergeych.lynon.LynonDecoder
import net.sergeych.lynon.LynonEncoder import net.sergeych.lynon.LynonEncoder
@ -127,6 +128,33 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
return list.map { it.toKotlin(scope) } return list.map { it.toKotlin(scope) }
} }
suspend fun quicksort(compare: suspend (Obj, Obj) -> Int) = quicksort(compare, 0, list.size - 1)
suspend fun quicksort(compare: suspend (Obj, Obj) -> Int, left: Int, right: Int) {
if (left >= right) return
var i = left
var j = right
val pivot = list[left]
while (i < j) {
// Сдвигаем j влево, пока элемент меньше pivot
while (i < j && compare(list[j], pivot) >= 0) {
j--
}
// Сдвигаем i вправо, пока элемент больше pivot
while (i < j && compare(list[i], pivot) <= 0) {
i++
}
if (i < j) {
list.swap(i, j)
}
}
// После завершения i == j, ставим pivot на своё место
list.swap(left, i)
// Рекурсивно сортируем левую и правую части
quicksort(compare, left, i - 1)
quicksort(compare, i + 1, right)
}
override fun hashCode(): Int { override fun hashCode(): Int {
// check? // check?
return list.hashCode() return list.hashCode()
@ -235,8 +263,23 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
} }
self self
} }
addFn("sortWith") {
val comparator = requireOnlyArg<Statement>()
thisAs<ObjList>().quicksort { a, b -> comparator.call(this, a, b).toInt() }
ObjVoid
}
} }
} }
} }
// Расширение MutableList для удобного обмена элементами
fun <T>MutableList<T>.swap(i: Int, j: Int) {
if (i in indices && j in indices) {
val temp = this[i]
this[i] = this[j]
this[j] = temp
}
}

View File

@ -77,9 +77,8 @@ fun Iterable.dropLast(n) {
} }
fun Iterable.takeLast(n) { fun Iterable.takeLast(n) {
val list = this
val buffer = RingBuffer(n) val buffer = RingBuffer(n)
for( item in list ) buffer += item for( item in this ) buffer += item
buffer buffer
} }
@ -104,10 +103,46 @@ fun Iterable.all(predicate): Bool {
!any { !predicate(it) } !any { !predicate(it) }
} }
fun Iterable.sum() {
val i = iterator()
if( i.hasNext() ) {
var result = i.next()
while( i.hasNext() ) result += i.next()
result
}
else null
}
fun Iterable.sumOf(f) {
val i = iterator()
if( i.hasNext() ) {
var result = f(i.next())
while( i.hasNext() ) result += f(i.next())
result
}
else null
}
fun Iterable.sorted() {
sortedWith { a, b -> a <=> b }
}
fun Iterable.sortedBy(predicate) {
sortedWith { a, b -> predicate(a) <=> predicate(b) }
}
fun List.toString() { fun List.toString() {
"[" + joinToString(",") + "]" "[" + joinToString(",") + "]"
} }
fun List.sortBy(predicate) {
sortWith { a, b -> predicate(a) <=> predicate(b) }
}
fun List.sort() {
sortWith { a, b -> a <=> b }
}
class StackTraceEntry( class StackTraceEntry(
val sourceName: String, val sourceName: String,
val line: Int, val line: Int,
@ -126,5 +161,6 @@ fun Exception.printStackTrace() {
} }
} }
""".trimIndent() """.trimIndent()

View File

@ -3095,10 +3095,12 @@ class ScriptTest {
@Test @Test
fun testOverridenListToString() = runTest { fun testOverridenListToString() = runTest {
eval(""" eval(
"""
val x = [1,2,3] val x = [1,2,3]
assertEquals( "[1,2,3]", x.toString() ) assertEquals( "[1,2,3]", x.toString() )
""".trimIndent()) """.trimIndent()
)
} }
@Test @Test
@ -3123,9 +3125,93 @@ class ScriptTest {
println(e.toString()) println(e.toString())
println("-------------------- dee") println("-------------------- dee")
println(decoded.toString()) println(decoded.toString())
// assertEquals( e.toString(), decoded.toString() ) assertEquals( e.toString(), decoded.toString() )
} }
""".trimIndent() """.trimIndent()
) )
} }
@Test
fun testThisInClosure() = runTest {
eval(
"""
fun Iterable.sum2by(f) {
var acc = null
for( x in this ) {
println(x)
println(f(x))
acc = acc?.let { acc + f(x) } ?: f(x)
}
}
class T(val coll, val factor) {
fun sum() {
// here we use ths::T and it must be available:
coll.sum2by { it * factor }
}
}
assertEquals(60, T([1,2,3], 10).sum())
""".trimIndent()
)
}
@Test
fun testThisInFlowClosure() = runTest {
eval(
"""
class T(val coll, val factor) {
fun seq() {
flow {
for( x in coll ) {
emit(x*factor)
}
}
}
}
assertEquals([10,20,30], T([1,2,3], 10).seq().toList())
""".trimIndent()
)
}
@Test
fun testSum() = runTest {
eval(
"""
assertEquals(1, [1].sum())
assertEquals(null, [].sum())
assertEquals(6, [1,2,3].sum())
assertEquals(30, [3].sumOf { it * 10 })
assertEquals(null, [].sumOf { it * 10 })
assertEquals(60, [1,2,3].sumOf { it * 10 })
""".trimIndent()
)
}
@Test
fun testSort() = runTest {
eval("""
val coll = [5,4,1,7]
assertEquals( [1,4,5,7], coll.sortedWith { a,b -> a <=> b })
assertEquals( [1,4,5,7], coll.sorted())
assertEquals( [7,5,4,1], coll.sortedBy { -it })
assertEquals( [1,4,5,7], coll.sortedBy { -it }.reversed())
""".trimIndent()
)
}
@Test
fun testListSortInPlace() = runTest {
eval("""
val l1 = [6,3,1,9]
l1.sort()
assertEquals( [1,3,6,9], l1)
l1.sortBy { -it }
assertEquals( [1,3,6,9].reversed(), l1)
l1.sort()
l1.sortBy { it % 4 }
// 1,3,6,9
// 1 3 2 1
// we hope we got it also stable:
assertEquals( [1,9,6,3], l1)
""")
}
} }