Skip to content

Commit

Permalink
Fix binary search on footnotes and xref: arrays of arif should be tre…
Browse files Browse the repository at this point in the history
…ated as unsigned ints

Fixes #102
  • Loading branch information
yukuku committed Feb 14, 2024
1 parent eb4462f commit 67b770a
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 11 deletions.
8 changes: 5 additions & 3 deletions .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions AlkitabYes2/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ dependencies {
implementation project(':BintexReader')
implementation project(':BintexWriter')
implementation project(':Snappy')
testImplementation 'junit:junit:4.12'
}
repositories {
mavenCentral()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
package yuku.alkitab.yes2.section;

import android.util.Log;
import java.io.IOException;
import yuku.alkitab.model.FootnoteEntry;
import yuku.alkitab.yes2.io.RandomInputStream;
import yuku.alkitab.yes2.section.base.SectionContent;
import yuku.alkitab.yes2.util.UnsignedBinarySearchKt;
import yuku.bintex.BintexReader;

import java.io.IOException;
import java.util.Arrays;

// The writer is in another class (not here to save code amount for Alkitab app)
// Section format:
// {
Expand Down Expand Up @@ -56,7 +55,7 @@ public class FootnotesSection extends SectionContent {
}

public FootnoteEntry getFootnoteEntry(final int arif) {
final int pos = Arrays.binarySearch(index_arifs, arif);
final int pos = UnsignedBinarySearchKt.unsignedIntBinarySearch(index_arifs, arif);
if (pos < 0) {
return null;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
package yuku.alkitab.yes2.section;

import android.util.Log;
import java.io.IOException;
import yuku.alkitab.model.XrefEntry;
import yuku.alkitab.yes2.io.RandomInputStream;
import yuku.alkitab.yes2.section.base.SectionContent;
import yuku.alkitab.yes2.util.UnsignedBinarySearchKt;
import yuku.bintex.BintexReader;

import java.io.IOException;
import java.util.Arrays;

// The writer is in another class (not here to save code amount for Alkitab app)
// Section format:
// {
Expand Down Expand Up @@ -56,7 +55,7 @@ public class XrefsSection extends SectionContent {
}

public XrefEntry getXrefEntry(final int arif) {
final int pos = Arrays.binarySearch(index_arifs, arif);
final int pos = UnsignedBinarySearchKt.unsignedIntBinarySearch(index_arifs, arif);
if (pos < 0) {
return null;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package yuku.alkitab.yes2.util

/**
* Treat [a] as a sorted array of unsigned ints and do binary search on it.
*
* @param key element to look for, treated as an unsigned int.
*/
fun unsignedIntBinarySearch(a: IntArray, key: Int): Int {
var low = 0
var high = a.size - 1

while (low <= high) {
val mid = (low + high) ushr 1
val midVal = a[mid]

val cmp = uintCompare(midVal, key)
if (cmp < 0) {
low = mid + 1
} else if (cmp > 0) {
high = mid - 1
} else {
// key found
return mid
}
}

return -(low + 1) // key not found.
}

private fun uintCompare(v1: Int, v2: Int): Int = (v1 xor Int.MIN_VALUE).compareTo(v2 xor Int.MIN_VALUE)
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
@file:OptIn(ExperimentalUnsignedTypes::class)

package yuku.alkitab.yes2.util

import org.junit.Assert.assertArrayEquals
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test

class UnsignedBinarySearchKtTest {

private val allPositiveArray = IntArray(100) { it + 10 }
private val allNegativeArray = IntArray(100) { (0xc000_0000L + it.toLong()).toInt() }
private val mixedSignArray = IntArray(100) { it } + IntArray(100) { (0xc000_0000L + it.toLong()).toInt() }
private val skipArray = IntArray(100) { it * 2 } + IntArray(100) { (0xc000_0000L + (it * 2).toLong()).toInt() }


@Before
fun checkSorted() {
// all arrays under test must be sorted
fun assertSorted(a: IntArray) {
val u = UIntArray(a.size) { a[it].toUInt() }
assertArrayEquals(u.toTypedArray(), u.sorted().toTypedArray())
}

assertSorted(allPositiveArray)
assertSorted(allNegativeArray)
assertSorted(mixedSignArray)
}

@Test
fun unsignedBinarySearch() {
for (i in allPositiveArray.indices) {
assertEquals(i, unsignedIntBinarySearch(allPositiveArray, i + 10))
}
assertEquals(0.inv(), unsignedIntBinarySearch(allPositiveArray, 0))
assertEquals(100.inv(), unsignedIntBinarySearch(allPositiveArray, -1))
assertEquals(100.inv(), unsignedIntBinarySearch(allPositiveArray, -2))
assertEquals(100.inv(), unsignedIntBinarySearch(allPositiveArray, 400))

for (i in allNegativeArray.indices) {
assertEquals(i, unsignedIntBinarySearch(allNegativeArray, allNegativeArray[i]))
}
assertEquals(0.inv(), unsignedIntBinarySearch(allNegativeArray, 0))
assertEquals(100.inv(), unsignedIntBinarySearch(allNegativeArray, -1))
assertEquals(100.inv(), unsignedIntBinarySearch(allNegativeArray, -2))
assertEquals(0.inv(), unsignedIntBinarySearch(allNegativeArray, 400))

for (i in mixedSignArray.indices) {
assertEquals(i, unsignedIntBinarySearch(mixedSignArray, mixedSignArray[i]))
}
assertEquals(0, unsignedIntBinarySearch(mixedSignArray, 0))
assertEquals(200.inv(), unsignedIntBinarySearch(mixedSignArray, -1))
assertEquals(200.inv(), unsignedIntBinarySearch(mixedSignArray, -2))
// in the middle
assertEquals(100.inv(), unsignedIntBinarySearch(mixedSignArray, 400))

for (i in skipArray.indices) {
assertEquals(i, unsignedIntBinarySearch(skipArray, skipArray[i]))
}
assertEquals(0, unsignedIntBinarySearch(skipArray, 0))
assertEquals(2.inv(), unsignedIntBinarySearch(skipArray, 3))
assertEquals(103.inv(), unsignedIntBinarySearch(skipArray, (0xc000_0000L + 5L).toInt()))
}
}

0 comments on commit 67b770a

Please sign in to comment.