+List.binarySearch
This commit is contained in:
Sergey Chernov 2025-08-24 00:09:38 +03:00
parent 0ec0ed96ee
commit dc837e2095
10 changed files with 110 additions and 9 deletions

37
docs/Array.md Normal file
View File

@ -0,0 +1,37 @@
# Array
It's an interface if the [Collection] that provides indexing access, like `array[3] = 0`.
Array therefore implements [Iterable] too. The well known implementatino of the `Array` is
[List].
Array adds the following methods:
## Binary search
When applied to sorted arrays, binary search allow to quicly find an index of the element in the array, or where to insert it to keep order:
val coll = [1,2,3,4,5]
assertEquals( 2, coll.binarySearch(3) )
assertEquals( 0, coll.binarySearch(1) )
assertEquals( 4, coll.binarySearch(5) )
val src = (1..50).toList().shuffled()
val result = []
for( x in src ) {
val i = result.binarySearch(x)
assert( i < 0 )
result.insertAt(-i-1, x)
}
assertEquals( src.sorted(), result )
>>> void
So `binarySearch(x)` returns:
- index of `x`, a non-negative number
- negative: `x` not found, but if inserted at position `-returnedValue-1` will leave array sorted.
To pre-sort and array use `Iterable.sorted*` or in-place `List.sort*` families, see [List] and [Iterable] docs.
[Collection]: Collection.md
[Iterable]: Iterable.md
[List]: List.md

View File

@ -95,6 +95,7 @@ These, again, does the thing:
| sortedBy(predicate) | sort by comparing results of the predicate function | | sortedBy(predicate) | sort by comparing results of the predicate function |
| joinToString(s,t) | convert iterable to string, see (2) | | joinToString(s,t) | convert iterable to string, see (2) |
| reversed() | create a list containing items from this in reverse order | | reversed() | create a list containing items from this in reverse order |
| shuffled() | create a listof shiffled elements |
(1) (1)
: throws `NoSuchElementException` if there is no such element : throws `NoSuchElementException` if there is no such element

View File

@ -128,8 +128,9 @@ List could be sorted in place, just like [Collection] provide sorted copies, in
| `[Range]` | get slice of the array (copy) | Range | | `[Range]` | get slice of the array (copy) | Range |
| `+=` | append element(s) (2) | List or Obj | | `+=` | append element(s) (2) | List or Obj |
| `sort()` | in-place sort, natural order | void | | `sort()` | in-place sort, natural order | void |
| 'sortBy(predicate)` | in place sort bu `predicate` call result (3) | void | | `sortBy(predicate)` | in-place sort bu `predicate` call result (3) | void |
| `SortWith(comparator) | in place sort using `comarator` function (4) | void | | `sortWith(comparator)` | in-place sort using `comarator` function (4) | void |
| `shiffle()` | in-place shiffle contents | |
(1) (1)
: optimized implementation that override `Array` one : optimized implementation that override `Array` one
@ -143,7 +144,7 @@ instance is appended. If you want to append an Iterable object itself, use `add`
order, e.g. is same as `list.sortWith { a,b -> predicate(a) <=> predicate(b) }` order, e.g. is same as `list.sortWith { a,b -> predicate(a) <=> predicate(b) }`
(4) (4)
: comparator callable takes tho arguments and must return: negative value when first is less, : 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 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 } for `sort()` will be `sort { a, b -> a <=> b }

View File

@ -324,10 +324,8 @@ open class Obj {
addFn("apply") { addFn("apply") {
val body = args.firstAndOnly() val body = args.firstAndOnly()
(thisObj as? ObjInstance)?.let { (thisObj as? ObjInstance)?.let {
println("apply in ${thisObj is ObjInstance}, ${it.instanceScope}")
body.callOn(ApplyScope(this, it.instanceScope)) body.callOn(ApplyScope(this, it.instanceScope))
} ?: run { } ?: run {
println("apply on non-instance $thisObj")
body.callOn(this) body.callOn(this)
} }
thisObj thisObj

View File

@ -50,5 +50,26 @@ val ObjArray by lazy {
addFn("indices") { addFn("indices") {
ObjRange(0.toObj(), thisObj.invokeInstanceMethod(this, "size"), false) ObjRange(0.toObj(), thisObj.invokeInstanceMethod(this, "size"), false)
} }
addFn("binarySearch") {
val target = args.firstAndOnly()
var low = 0
var high = thisObj.invokeInstanceMethod(this, "size").toInt() - 1
while (low <= high) {
val mid = (low + high) / 2
val midVal = thisObj.getAt(this, ObjInt(mid.toLong()))
val cmp = midVal.compareTo(this, target)
when {
cmp == 0 -> return@addFn (mid).toObj()
cmp > 0 -> high = mid - 1
else -> low = mid + 1
}
}
// Элемент не найден, возвращаем -(точка вставки) - 1
(-low - 1).toObj()
}
} }
} }

View File

@ -141,6 +141,5 @@ val ObjIterable by lazy {
list.list.reverse() list.list.reverse()
list list
} }
} }
} }

View File

@ -269,6 +269,10 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
thisAs<ObjList>().quicksort { a, b -> comparator.call(this, a, b).toInt() } thisAs<ObjList>().quicksort { a, b -> comparator.call(this, a, b).toInt() }
ObjVoid ObjVoid
} }
addFn("shuffle") {
thisAs<ObjList>().list.shuffle()
ObjVoid
}
} }
} }
} }

View File

@ -131,6 +131,10 @@ fun Iterable.sortedBy(predicate) {
sortedWith { a, b -> predicate(a) <=> predicate(b) } sortedWith { a, b -> predicate(a) <=> predicate(b) }
} }
fun Iterable.shuffled() {
toList().apply { shuffle() }
}
fun List.toString() { fun List.toString() {
"[" + joinToString(",") + "]" "[" + joinToString(",") + "]"
} }

View File

@ -3188,7 +3188,8 @@ class ScriptTest {
@Test @Test
fun testSort() = runTest { fun testSort() = runTest {
eval(""" eval(
"""
val coll = [5,4,1,7] val coll = [5,4,1,7]
assertEquals( [1,4,5,7], coll.sortedWith { a,b -> a <=> b }) assertEquals( [1,4,5,7], coll.sortedWith { a,b -> a <=> b })
assertEquals( [1,4,5,7], coll.sorted()) assertEquals( [1,4,5,7], coll.sorted())
@ -3200,7 +3201,8 @@ class ScriptTest {
@Test @Test
fun testListSortInPlace() = runTest { fun testListSortInPlace() = runTest {
eval(""" eval(
"""
val l1 = [6,3,1,9] val l1 = [6,3,1,9]
l1.sort() l1.sort()
assertEquals( [1,3,6,9], l1) assertEquals( [1,3,6,9], l1)
@ -3212,6 +3214,35 @@ class ScriptTest {
// 1 3 2 1 // 1 3 2 1
// we hope we got it also stable: // we hope we got it also stable:
assertEquals( [1,9,6,3], l1) assertEquals( [1,9,6,3], l1)
""") """
)
} }
@Test
fun binarySearchTest() = runTest {
eval(
"""
val coll = [1,2,3,4,5]
assertEquals( 2, coll.binarySearch(3) )
assertEquals( 0, coll.binarySearch(1) )
assertEquals( 4, coll.binarySearch(5) )
""".trimIndent()
)
}
@Test
fun binarySearchTest2() = runTest {
eval(
"""
val src = (1..50).toList().shuffled()
val result = []
for( x in src ) {
val i = result.binarySearch(x)
assert( i < 0 )
result.insertAt(-i-1, x)
}
assertEquals( src.sorted(), result )
""".trimIndent())
}
} }

View File

@ -327,4 +327,9 @@ class BookTest {
runDocTests("../docs/serialization.md") runDocTests("../docs/serialization.md")
} }
@Test
fun testArray() = runBlocking {
runDocTests("../docs/Array.md")
}
} }