diff --git a/.idea/misc.xml b/.idea/misc.xml index ef2471e29..c33d6afab 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -9,7 +9,7 @@ - + diff --git a/AlkitabYes2/build.gradle b/AlkitabYes2/build.gradle index 602aa5d79..92dbd3212 100644 --- a/AlkitabYes2/build.gradle +++ b/AlkitabYes2/build.gradle @@ -32,6 +32,7 @@ dependencies { implementation project(':BintexReader') implementation project(':BintexWriter') implementation project(':Snappy') + testImplementation 'junit:junit:4.12' } repositories { mavenCentral() diff --git a/AlkitabYes2/src/main/java/yuku/alkitab/yes2/section/FootnotesSection.java b/AlkitabYes2/src/main/java/yuku/alkitab/yes2/section/FootnotesSection.java index 3f924d974..8309f2bad 100644 --- a/AlkitabYes2/src/main/java/yuku/alkitab/yes2/section/FootnotesSection.java +++ b/AlkitabYes2/src/main/java/yuku/alkitab/yes2/section/FootnotesSection.java @@ -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: // { @@ -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; } diff --git a/AlkitabYes2/src/main/java/yuku/alkitab/yes2/section/XrefsSection.java b/AlkitabYes2/src/main/java/yuku/alkitab/yes2/section/XrefsSection.java index cbae4f948..8ca47cf15 100644 --- a/AlkitabYes2/src/main/java/yuku/alkitab/yes2/section/XrefsSection.java +++ b/AlkitabYes2/src/main/java/yuku/alkitab/yes2/section/XrefsSection.java @@ -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: // { @@ -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; } diff --git a/AlkitabYes2/src/main/java/yuku/alkitab/yes2/util/unsignedBinarySearch.kt b/AlkitabYes2/src/main/java/yuku/alkitab/yes2/util/unsignedBinarySearch.kt new file mode 100644 index 000000000..2db476037 --- /dev/null +++ b/AlkitabYes2/src/main/java/yuku/alkitab/yes2/util/unsignedBinarySearch.kt @@ -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) diff --git a/AlkitabYes2/src/test/java/yuku/alkitab/yes2/util/UnsignedBinarySearchKtTest.kt b/AlkitabYes2/src/test/java/yuku/alkitab/yes2/util/UnsignedBinarySearchKtTest.kt new file mode 100644 index 000000000..66ac65185 --- /dev/null +++ b/AlkitabYes2/src/test/java/yuku/alkitab/yes2/util/UnsignedBinarySearchKtTest.kt @@ -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())) + } +}