diff --git a/docs/Collection.md b/docs/Collection.md index e4d28a0..f4f7820 100644 --- a/docs/Collection.md +++ b/docs/Collection.md @@ -6,6 +6,12 @@ Is a [Iterable] with known `size`, a finite [Iterable]: 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] [Iterable]: Iterable.md diff --git a/docs/Iterable.md b/docs/Iterable.md index aa8c4d0..fd7686e 100644 --- a/docs/Iterable.md +++ b/docs/Iterable.md @@ -1,9 +1,13 @@ # 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. +Please see also [Collection] interface: many iterables are also collections, and it adds important features. + ## Definition: 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. -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! assertEquals( [9,10], r.takeLast(2).toList() ) @@ -34,12 +39,13 @@ Having `Iterable` in base classes allows to use it in for loop. Also, each `Iter ## 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) - if `Iterable` `isEmpty`, the empty string `""` is returned. -- `suffix` is inserted between items when there are more than one. +- `suffix` is inserted between items when there are more than one. - `transform` of specified is applied to each element, otherwise its `toString()` method is used. Here is the sample: @@ -49,34 +55,56 @@ Here is the sample: assertEquals( (1..3).joinToString { it * 10 }, "10 20 30") >>> 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: - -| fun/method | description | -|-------------------|---------------------------------------------------------------------------| -| toList() | create a list from iterable | -| toSet() | create a set from iterable | -| contains(i) | check that iterable contains `i` | -| `i in iterator` | same as `contains(i)` | -| isEmpty() | check iterable is empty | -| forEach(f) | call f for each element | -| 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 | -| 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 | -| first | first element (1) | -| last | last element (1) | -| take(n) | return [Iterable] of up to n first elements | -| taleLast(n) | return [Iterable] of up to n last elements | -| drop(n) | return new [Iterable] without first n elements | -| dropLast(n) | return new [Iterable] without last n elements | -| joinToString(s,t) | convert iterable to string, see (2) | +| fun/method | description | +|------------------------|---------------------------------------------------------------------------------| +| toList() | create a list from iterable | +| toSet() | create a set from iterable | +| contains(i) | check that iterable contains `i` | +| `i in iterator` | same as `contains(i)` | +| isEmpty() | check iterable is empty | +| forEach(f) | call f for each element | +| 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 | +| 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 | +| first | first element (1) | +| last | last element (1) | +| take(n) | return [Iterable] of up to n first elements | +| taleLast(n) | return [Iterable] of up to n last elements | +| drop(n) | return new [Iterable] without first n elements | +| dropLast(n) | return new [Iterable] without last n elements | +| sum() | return sum of the collection applying `+` to its elements (3) | +| 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) -: throws `NoSuchElementException` if there is no such element +: throws `NoSuchElementException` if there is no such element (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.toSet(): Set @@ -86,7 +114,6 @@ Here is the sample: fun Iterable.forEach(block: (Any?)->Void ): Void fun Iterable.map(block: (Any?)->Void ): List fun Iterable.associateBy( keyMaker: (Any?)->Any): Map - ## Abstract methods: @@ -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] +[Collection]: Collection.md + [List]: List.md + [Range]: Range.md + [Set]: Set.md + [RingBuffer]: RingBuffer.md \ No newline at end of file diff --git a/docs/List.md b/docs/List.md index 36dbdd9..2e48f97 100644 --- a/docs/List.md +++ b/docs/List.md @@ -92,22 +92,44 @@ Open end ranges remove head and tail elements: assert( [1, 2, 3] !== [1, 2, 3]) >>> 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 -| name | meaning | type | -|-------------------------------|---------------------------------------|-------------| -| `size` | current size | Int | -| `add(elements...)` | add one or more elements to the end | Any | -| `insertAt(index,elements...)` | insert elements at position | Int, Any | -| `removeAt(index)` | remove element at position | Int | -| `remove(from,toNonInclusive)` | remove range from (incl) to (nonincl) | Int, Int | -| `remove(Range)` | remove range | Range | -| `removeLast()` | remove last element | | -| `removeLast(n)` | remove n last elements | Int | -| `contains(element)` | check the element is in the list (1) | | -| `[index]` | get or set element at index | Int | -| `[Range]` | get slice of the array (copy) | Range | -| `+=` | append element(s) | List or Obj | +| name | meaning | type | +|-------------------------------|----------------------------------------------|-------------| +| `size` | current size | Int | +| `add(elements...)` | add one or more elements to the end | Any | +| `insertAt(index,elements...)` | insert elements at position | Int, Any | +| `removeAt(index)` | remove element at position | Int | +| `remove(from,toNonInclusive)` | remove range from (incl) to (nonincl) | Int, Int | +| `remove(Range)` | remove range | Range | +| `removeLast()` | remove last element | | +| `removeLast(n)` | remove n last elements | Int | +| `contains(element)` | check the element is in the list (1) | | +| `[index]` | get or set element at index | Int | +| `[Range]` | get slice of the array (copy) | Range | +| `+=` | 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) : 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 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 @@ -134,4 +165,5 @@ It inherits from [Iterable] too. [Range]: Range.md + [Iterable]: Iterable.md \ No newline at end of file diff --git a/docs/tutorial.md b/docs/tutorial.md index 4326918..5744c1a 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -764,15 +764,15 @@ thrown. Typical builtin types that are containers (e.g. support `contains`): -| class | notes | -|------------|------------------------------------------------| -| Collection | contains an element (1) | -| Array | faster maybe that Collection's | -| List | faster than Array's | -| String | character in string or substring in string (3) | -| Range | object is included in the range (2) | -| Buffer | byte is in buffer | -| RingBuffer | object is in buffer | +| class | notes | +|--------------|------------------------------------------------| +| [Collection] | contains an element (1) | +| Array | faster maybe that Collection's | +| List | faster than Array's | +| String | character in string or substring in string (3) | +| Range | object is included in the range (2) | +| Buffer | byte is in buffer | +| RingBuffer | object is in buffer | (1) : Iterable is not the container as it can be infinite @@ -1361,4 +1361,6 @@ See [math functions](math.md). Other general purpose functions are: [parallelism]: parallelism.md -[RingBuffer]: RingBuffer.md \ No newline at end of file +[RingBuffer]: RingBuffer.md + +[Collection]: Collection.md \ No newline at end of file diff --git a/lynglib/build.gradle.kts b/lynglib/build.gradle.kts index 2cb4cd3..61ad726 100644 --- a/lynglib/build.gradle.kts +++ b/lynglib/build.gradle.kts @@ -21,7 +21,7 @@ import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl import org.jetbrains.kotlin.gradle.dsl.JvmTarget group = "net.sergeych" -version = "0.8.14-SNAPSHOT" +version = "0.8.15-SNAPSHOT" buildscript { repositories { diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjCollection.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjCollection.kt index a12a238..a73e076 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjCollection.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjCollection.kt @@ -18,8 +18,8 @@ package net.sergeych.lyng.obj /** - * Collection is an iterator with `size`] + * Collection is an iterator with `size` */ val ObjCollection = ObjClass("Collection", ObjIterable).apply { +} -} \ No newline at end of file diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjIterable.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjIterable.kt index f668e94..422c48b 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjIterable.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjIterable.kt @@ -119,12 +119,6 @@ val ObjIterable by lazy { ObjList(result) } -// addFn("drop" ) { -// var n = requireOnlyArg().value.toInt() -// if( n < 0 ) raiseIllegalArgument("drop($n): should be positive") -// val it = callMethod<>() -// } - addFn("isEmpty") { ObjBool( thisObj.invokeInstanceMethod(this, "iterator") @@ -132,5 +126,21 @@ val ObjIterable by lazy { .not() ) } + + addFn("sortedWith") { + val list = thisObj.callMethod(this, "toList") + val comparator = requireOnlyArg() + list.quicksort { a, b -> + comparator.call(this, a, b).toInt() + } + list + } + + addFn("reversed") { + val list = thisObj.callMethod(this, "toList") + list.list.reverse() + list + } + } } \ No newline at end of file diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjList.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjList.kt index 185fa91..ed97867 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjList.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjList.kt @@ -18,6 +18,7 @@ package net.sergeych.lyng.obj import net.sergeych.lyng.Scope +import net.sergeych.lyng.Statement import net.sergeych.lyng.statement import net.sergeych.lynon.LynonDecoder import net.sergeych.lynon.LynonEncoder @@ -127,6 +128,33 @@ class ObjList(val list: MutableList = mutableListOf()) : Obj() { 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 { // check? return list.hashCode() @@ -235,8 +263,23 @@ class ObjList(val list: MutableList = mutableListOf()) : Obj() { } self } + + addFn("sortWith") { + val comparator = requireOnlyArg() + thisAs().quicksort { a, b -> comparator.call(this, a, b).toInt() } + ObjVoid + } } } } +// Расширение MutableList для удобного обмена элементами +fun MutableList.swap(i: Int, j: Int) { + if (i in indices && j in indices) { + val temp = this[i] + this[i] = this[j] + this[j] = temp + } +} + diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/stdlib_included/root_lyng.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/stdlib_included/root_lyng.kt index 3370384..ebdcf48 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/stdlib_included/root_lyng.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/stdlib_included/root_lyng.kt @@ -103,10 +103,46 @@ fun Iterable.all(predicate): Bool { !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() { "[" + joinToString(",") + "]" } +fun List.sortBy(predicate) { + sortWith { a, b -> predicate(a) <=> predicate(b) } +} + +fun List.sort() { + sortWith { a, b -> a <=> b } +} + class StackTraceEntry( val sourceName: String, val line: Int, @@ -124,6 +160,7 @@ fun Exception.printStackTrace() { println("\tat "+entry) } } + """.trimIndent() diff --git a/lynglib/src/commonTest/kotlin/ScriptTest.kt b/lynglib/src/commonTest/kotlin/ScriptTest.kt index 2157a20..edc82ad 100644 --- a/lynglib/src/commonTest/kotlin/ScriptTest.kt +++ b/lynglib/src/commonTest/kotlin/ScriptTest.kt @@ -3095,10 +3095,12 @@ class ScriptTest { @Test fun testOverridenListToString() = runTest { - eval(""" + eval( + """ val x = [1,2,3] assertEquals( "[1,2,3]", x.toString() ) - """.trimIndent()) + """.trimIndent() + ) } @Test @@ -3131,7 +3133,8 @@ class ScriptTest { @Test fun testThisInClosure() = runTest { - eval(""" + eval( + """ fun Iterable.sum2by(f) { var acc = null for( x in this ) { @@ -3147,12 +3150,14 @@ class ScriptTest { } } assertEquals(60, T([1,2,3], 10).sum()) - """.trimIndent()) + """.trimIndent() + ) } @Test fun testThisInFlowClosure() = runTest { - eval(""" + eval( + """ class T(val coll, val factor) { fun seq() { flow { @@ -3163,7 +3168,50 @@ class ScriptTest { } } assertEquals([10,20,30], T([1,2,3], 10).seq().toList()) - """.trimIndent()) + """.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) + """) + } } \ No newline at end of file