1 /* 2 * Copyright (C) 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.utils; 18 19 import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE; 20 21 import android.annotation.Nullable; 22 import android.annotation.Size; 23 24 import com.android.internal.annotations.VisibleForTesting; 25 import com.android.internal.util.ArrayUtils; 26 import com.android.internal.util.GrowingArrayUtils; 27 28 import java.util.Arrays; 29 30 /** 31 * A {@link WatchedSparseBooleanMatrix} is an compact NxN array of booleans. The rows and 32 * columns of the array are indexed by integers, which need not be contiguous. The matrix 33 * is square and the row and column indices are identical. This matrix is intended to be 34 * very memory efficient. 35 * 36 * The matrix contains a map from indices to columns: this map requires 2*N integers. The 37 * boolean array is bit-packed and requires N*N/8 bytes. The memory required for an 38 * order-N matrix is therefore 2*N*4 + N*N bytes. 39 * 40 * See {@link SparseBooleanArray} for a discussion of sparse arrays. 41 */ 42 public class WatchedSparseBooleanMatrix extends WatchableImpl implements Snappable { 43 44 /** 45 * The matrix is implemented through four arrays. First, the matrix of booleans is 46 * stored in a two-dimensional {@code mValues} array of bit-packed booleans. 47 * {@code mValues} is always of size {@code mOrder * mOrder / 8}. The factor of 8 is 48 * present because there are 8 bits in a byte. Elements of {@code mValues} are 49 * addressed with arithmetic: the element {@code {row, col}} is bit {@code col % 8} in 50 * byte * {@code (row * mOrder + col) / 8}. The term "storage index" applies to 51 * {@code mValues}. A storage index designates a row (column) in the underlying 52 * storage. This is not the same as the row seen by client code. 53 * 54 * Client code addresses the matrix through indices. These are integers that need not 55 * be contiguous. Client indices are mapped to storage indices through two linear 56 * integer arrays. {@code mKeys} is a sorted list of client indices. 57 * {@code mIndices} is a parallel array that contains storage indices. The storage 58 * index of a client index {@code k} is {@code mIndices[i]}, where 59 * {@code mKeys[i] == k}. 60 * 61 * A final array, {@code mInUse} records if storage indices are free or in use. This 62 * array is of size {@code mOrder}. A client index is deleted by removing it from 63 * {@code mKeys} and {@code mIndices} and then setting the original storage index 64 * false in {@code mInUse}. 65 * 66 * Some notes: 67 * <ul> 68 * <li> The matrix does not automatically shrink but there is a compress() method that 69 * will recover unused space. 70 * <li> Equality is a very, very expensive operation because it must walk the matrices 71 * beimg compared element by element. 72 * </ul> 73 */ 74 75 /** 76 * mOrder is always a multiple of this value. A minimal matrix therefore holds 2^12 77 * values and requires 1024 bytes. The value is visible for testing. 78 */ 79 @VisibleForTesting(visibility = PRIVATE) 80 static final int STEP = 64; 81 82 /** 83 * The number of bits in the mValues array element. 84 */ 85 private static final int PACKING = 32; 86 87 /** 88 * Constants that index into the string array returned by matrixToString. The primary 89 * consumer is test code. 90 */ 91 static final int STRING_KEY_INDEX = 0; 92 static final int STRING_MAP_INDEX = 1; 93 static final int STRING_INUSE_INDEX = 2; 94 95 /** 96 * The order of the matrix storage, including any padding. The matrix is always 97 * square. mOrder is always greater than or equal to mSize. 98 */ 99 private int mOrder; 100 101 /** 102 * The number of client keys. This is always less than or equal to mOrder. It is the 103 * order of the matrix as seen by the client. 104 */ 105 private int mSize; 106 107 /** 108 * The in-use list. 109 */ 110 private boolean[] mInUse; 111 112 /** 113 * The array of client keys (indices), in sorted order. 114 */ 115 private int[] mKeys; 116 117 /** 118 * The mapping from a client key to an storage index. If client key K is at index N 119 * in mKeys, then the storage index for K is at mMap[N]. 120 */ 121 private int[] mMap; 122 123 /** 124 * The boolean array. This array is always {@code mOrder x mOrder} in size. 125 */ 126 private int[] mValues; 127 128 /** 129 * A convenience function called when the elements are added to or removed from the storage. 130 * The watchable is always {@link this}. 131 */ onChanged()132 private void onChanged() { 133 dispatchChange(this); 134 } 135 136 /** 137 * Creates a new WatchedSparseBooleanMatrix containing no mappings. 138 */ WatchedSparseBooleanMatrix()139 public WatchedSparseBooleanMatrix() { 140 this(STEP); 141 } 142 143 /** 144 * Creates a new SparseBooleanMatrix containing no mappings that will not require any 145 * additional memory allocation to store the specified number of mappings. The 146 * capacity is always rounded up to a non-zero multiple of STEP. 147 */ WatchedSparseBooleanMatrix(int initialCapacity)148 public WatchedSparseBooleanMatrix(int initialCapacity) { 149 mOrder = initialCapacity; 150 if (mOrder < STEP) { 151 mOrder = STEP; 152 } 153 if (mOrder % STEP != 0) { 154 mOrder = ((initialCapacity / STEP) + 1) * STEP; 155 } 156 if (mOrder < STEP || (mOrder % STEP != 0)) { 157 throw new RuntimeException("mOrder is " + mOrder + " initCap is " + initialCapacity); 158 } 159 160 mInUse = ArrayUtils.newUnpaddedBooleanArray(mOrder); 161 mKeys = ArrayUtils.newUnpaddedIntArray(mOrder); 162 mMap = ArrayUtils.newUnpaddedIntArray(mOrder); 163 mValues = ArrayUtils.newUnpaddedIntArray(mOrder * mOrder / PACKING); 164 mSize = 0; 165 } 166 167 /** 168 * A copy constructor that can be used for snapshotting. 169 */ WatchedSparseBooleanMatrix(WatchedSparseBooleanMatrix r)170 private WatchedSparseBooleanMatrix(WatchedSparseBooleanMatrix r) { 171 mOrder = r.mOrder; 172 mSize = r.mSize; 173 mKeys = r.mKeys.clone(); 174 mMap = r.mMap.clone(); 175 mInUse = r.mInUse.clone(); 176 mValues = r.mValues.clone(); 177 } 178 179 /** 180 * Return a copy of this object. 181 */ snapshot()182 public WatchedSparseBooleanMatrix snapshot() { 183 return new WatchedSparseBooleanMatrix(this); 184 } 185 186 /** 187 * Gets the boolean mapped from the specified key, or <code>false</code> 188 * if no such mapping has been made. 189 */ get(int row, int col)190 public boolean get(int row, int col) { 191 return get(row, col, false); 192 } 193 194 /** 195 * Gets the boolean mapped from the specified key, or the specified value 196 * if no such mapping has been made. 197 */ get(int row, int col, boolean valueIfKeyNotFound)198 public boolean get(int row, int col, boolean valueIfKeyNotFound) { 199 int r = indexOfKey(row, false); 200 int c = indexOfKey(col, false); 201 if (r >= 0 && c >= 0) { 202 return valueAt(r, c); 203 } else { 204 return valueIfKeyNotFound; 205 } 206 } 207 208 /** 209 * Adds a mapping from the specified keys to the specified value, replacing the 210 * previous mapping from the specified keys if there was one. 211 */ put(int row, int col, boolean value)212 public void put(int row, int col, boolean value) { 213 int r = indexOfKey(row); 214 int c = indexOfKey(col); 215 if (r < 0 || c < 0) { 216 // One or both of the keys has not be installed yet. Install them now. 217 // Installing either key may shift the other key. The safest course is to 218 // install the keys that are not present and then recompute both indices. 219 if (r < 0) { 220 r = indexOfKey(row, true); 221 } 222 if (c < 0) { 223 c = indexOfKey(col, true); 224 } 225 r = indexOfKey(row); 226 c = indexOfKey(col); 227 } 228 if (r >= 0 && c >= 0) { 229 setValueAt(r, c, value); 230 // setValueAt() will call onChanged(). 231 } else { 232 throw new RuntimeException("matrix overflow"); 233 } 234 } 235 236 /** 237 * Removes the mapping from the specified key, if there was any. Note that deletion 238 * applies to a single index, not to an element. The matrix never shrinks but the 239 * space will be reused the next time an index is added. 240 */ deleteKey(int key)241 public void deleteKey(int key) { 242 int i = indexOfKey(key, false); 243 if (i >= 0) { 244 removeAt(i); 245 } 246 } 247 248 /** 249 * Removes the mapping at the specified index. The matrix does not shrink. This 250 * throws ArrayIndexOutOfBounds if the index out outside the range {@code 0..size()-1}. 251 */ removeAt(int index)252 public void removeAt(int index) { 253 validateIndex(index); 254 mInUse[mMap[index]] = false; 255 // Remove the specified index and ensure that unused words in mKeys and mMap are 256 // always zero, to simplify the equality function. 257 System.arraycopy(mKeys, index + 1, mKeys, index, mSize - (index + 1)); 258 mKeys[mSize - 1] = 0; 259 System.arraycopy(mMap, index + 1, mMap, index, mSize - (index + 1)); 260 mMap[mSize - 1] = 0; 261 mSize--; 262 onChanged(); 263 } 264 265 /** 266 * Returns the number of key-value mappings that this WatchedSparseBooleanMatrix 267 * currently stores. 268 */ size()269 public int size() { 270 return mSize; 271 } 272 273 /** 274 * Removes all key-value mappings from this WatchedSparseBooleanMatrix. 275 */ clear()276 public void clear() { 277 mSize = 0; 278 Arrays.fill(mInUse, false); 279 onChanged(); 280 } 281 282 /** 283 * Given an index in the range <code>0...size()-1</code>, returns the key from the 284 * <code>index</code>th key-value mapping that this WatchedSparseBooleanMatrix stores. 285 * 286 * <p>The keys corresponding to indices in ascending order are guaranteed to be in 287 * ascending order, e.g., <code>keyAt(0)</code> will return the smallest key and 288 * <code>keyAt(size()-1)</code> will return the largest key.</p> 289 * 290 * <p>{@link ArrayIndexOutOfBoundsException} is thrown for indices outside of the 291 * range <code>0...size()-1</code></p> 292 */ keyAt(int index)293 public int keyAt(int index) { 294 validateIndex(index); 295 return mKeys[index]; 296 } 297 298 /** 299 * An internal method to fetch the boolean value given the mValues row and column 300 * indices. These are not the indices used by the *At() methods. 301 */ valueAtInternal(int row, int col)302 private boolean valueAtInternal(int row, int col) { 303 int element = row * mOrder + col; 304 int offset = element / PACKING; 305 int mask = 1 << (element % PACKING); 306 return (mValues[offset] & mask) != 0; 307 } 308 309 /** 310 * Given a row and column, each in the range <code>0...size()-1</code>, returns the 311 * value from the <code>index</code>th key-value mapping that this WatchedSparseBooleanMatrix 312 * stores. 313 */ valueAt(int rowIndex, int colIndex)314 public boolean valueAt(int rowIndex, int colIndex) { 315 validateIndex(rowIndex, colIndex); 316 int r = mMap[rowIndex]; 317 int c = mMap[colIndex]; 318 return valueAtInternal(r, c); 319 } 320 321 /** 322 * An internal method to set the boolean value given the mValues row and column 323 * indices. These are not the indices used by the *At() methods. 324 */ setValueAtInternal(int row, int col, boolean value)325 private void setValueAtInternal(int row, int col, boolean value) { 326 int element = row * mOrder + col; 327 int offset = element / PACKING; 328 int mask = 1 << (element % PACKING); 329 if (value) { 330 mValues[offset] |= mask; 331 } else { 332 mValues[offset] &= ~mask; 333 } 334 } 335 336 /** 337 * Directly set the value at a particular index. 338 */ setValueAt(int rowIndex, int colIndex, boolean value)339 public void setValueAt(int rowIndex, int colIndex, boolean value) { 340 validateIndex(rowIndex, colIndex); 341 int r = mMap[rowIndex]; 342 int c = mMap[colIndex]; 343 setValueAtInternal(r, c, value); 344 onChanged(); 345 } 346 347 /** 348 * Returns the index for which {@link #keyAt} would return the specified key, or a 349 * negative number if the specified key is not mapped. 350 */ indexOfKey(int key)351 public int indexOfKey(int key) { 352 return binarySearch(mKeys, mSize, key); 353 } 354 355 /** 356 * Return true if the matrix knows the user index. 357 */ contains(int key)358 public boolean contains(int key) { 359 return indexOfKey(key) >= 0; 360 } 361 362 /** 363 * Fetch the index of a key. If the key does not exist and grow is true, then add the 364 * key. If the does not exist and grow is false, return -1. 365 */ indexOfKey(int key, boolean grow)366 private int indexOfKey(int key, boolean grow) { 367 int i = binarySearch(mKeys, mSize, key); 368 if (i < 0 && grow) { 369 i = ~i; 370 if (mSize >= mOrder) { 371 // Preemptively grow the matrix, which also grows the free list. 372 growMatrix(); 373 } 374 int newIndex = nextFree(); 375 mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key); 376 mMap = GrowingArrayUtils.insert(mMap, mSize, i, newIndex); 377 mSize++; 378 379 // Initialize the row and column corresponding to the new index. 380 int valueRow = mOrder / PACKING; 381 int offset = newIndex / PACKING; 382 int mask = ~(1 << (newIndex % PACKING)); 383 Arrays.fill(mValues, newIndex * valueRow, (newIndex + 1) * valueRow, 0); 384 for (int n = 0; n < mSize; n++) { 385 mValues[n * valueRow + offset] &= mask; 386 } 387 // Do not report onChanged() from this private method. onChanged() is the 388 // responsibility of public methods that call this one. 389 } 390 return i; 391 } 392 393 /** 394 * Validate the index. This can throw. 395 */ validateIndex(int index)396 private void validateIndex(int index) { 397 if (index >= mSize) { 398 // The array might be slightly bigger than mSize, in which case, indexing won't fail. 399 throw new ArrayIndexOutOfBoundsException(index); 400 } 401 } 402 403 /** 404 * Validate two indices. 405 */ validateIndex(int row, int col)406 private void validateIndex(int row, int col) { 407 validateIndex(row); 408 validateIndex(col); 409 } 410 411 /** 412 * Expand the 2D array. This also extends the free list. 413 */ growMatrix()414 private void growMatrix() { 415 resizeMatrix(mOrder + STEP); 416 } 417 418 /** 419 * Resize the values array to the new dimension. 420 */ resizeMatrix(int newOrder)421 private void resizeMatrix(int newOrder) { 422 if (newOrder % STEP != 0) { 423 throw new IllegalArgumentException("matrix order " + newOrder 424 + " is not a multiple of " + STEP); 425 } 426 int minOrder = Math.min(mOrder, newOrder); 427 428 boolean[] newInUse = ArrayUtils.newUnpaddedBooleanArray(newOrder); 429 System.arraycopy(mInUse, 0, newInUse, 0, minOrder); 430 int[] newMap = ArrayUtils.newUnpaddedIntArray(newOrder); 431 System.arraycopy(mMap, 0, newMap, 0, minOrder); 432 int[] newKeys = ArrayUtils.newUnpaddedIntArray(newOrder); 433 System.arraycopy(mKeys, 0, newKeys, 0, minOrder); 434 435 int[] newValues = ArrayUtils.newUnpaddedIntArray(newOrder * newOrder / PACKING); 436 for (int i = 0; i < minOrder; i++) { 437 int row = mOrder * i / PACKING; 438 int newRow = newOrder * i / PACKING; 439 System.arraycopy(mValues, row, newValues, newRow, minOrder / PACKING); 440 } 441 442 mInUse = newInUse; 443 mMap = newMap; 444 mKeys = newKeys; 445 mValues = newValues; 446 mOrder = newOrder; 447 } 448 449 /** 450 * Find an unused storage index, mark it in-use, and return it. 451 */ nextFree()452 private int nextFree() { 453 for (int i = 0; i < mInUse.length; i++) { 454 if (!mInUse[i]) { 455 mInUse[i] = true; 456 return i; 457 } 458 } 459 throw new RuntimeException(); 460 } 461 462 /** 463 * Return the index of the key that uses the highest row index in use. This returns 464 * -1 if the matrix is empty. Note that the return is an index suitable for the *At() 465 * methods. It is not the index in the mInUse array. 466 */ lastInuse()467 private int lastInuse() { 468 for (int i = mOrder - 1; i >= 0; i--) { 469 if (mInUse[i]) { 470 for (int j = 0; j < mSize; j++) { 471 if (mMap[j] == i) { 472 return j; 473 } 474 } 475 throw new IndexOutOfBoundsException(); 476 } 477 } 478 return -1; 479 } 480 481 /** 482 * Compress the matrix by packing keys into consecutive indices. If the compression 483 * is sufficient, the mValues array can be shrunk. 484 */ pack()485 private void pack() { 486 if (mSize == 0 || mSize == mOrder) { 487 return; 488 } 489 // dst and src are identify raw (row, col) in mValues. srcIndex is the index (as 490 // in the result of keyAt()) of the key being relocated. 491 for (int dst = nextFree(); dst < mSize; dst = nextFree()) { 492 int srcIndex = lastInuse(); 493 int src = mMap[srcIndex]; 494 mInUse[src] = false; 495 mMap[srcIndex] = dst; 496 System.arraycopy(mValues, src * mOrder / PACKING, 497 mValues, dst * mOrder / PACKING, 498 mOrder / PACKING); 499 int srcOffset = (src / PACKING); 500 int srcMask = 1 << (src % PACKING); 501 int dstOffset = (dst / PACKING); 502 int dstMask = 1 << (dst % PACKING); 503 for (int i = 0; i < mOrder; i++) { 504 if ((mValues[srcOffset] & srcMask) == 0) { 505 mValues[dstOffset] &= ~dstMask; 506 } else { 507 mValues[dstOffset] |= dstMask; 508 } 509 srcOffset += mOrder / PACKING; 510 dstOffset += mOrder / PACKING; 511 } 512 } 513 } 514 515 /** 516 * Shrink the matrix, if possible. 517 */ compact()518 public void compact() { 519 pack(); 520 int unused = (mOrder - mSize) / STEP; 521 if (unused > 0) { 522 resizeMatrix(mOrder - (unused * STEP)); 523 } 524 } 525 526 /** 527 * Return a copy of the keys that are in use by the matrix. 528 */ keys()529 public int[] keys() { 530 return Arrays.copyOf(mKeys, mSize); 531 } 532 533 /** 534 * Return the size of the 2D matrix. This is always greater than or equal to size(). 535 * This does not reflect the sizes of the meta-information arrays (such as mKeys). 536 */ capacity()537 public int capacity() { 538 return mOrder; 539 } 540 541 /** 542 * {@inheritDoc} 543 */ 544 @Override hashCode()545 public int hashCode() { 546 int hashCode = mSize; 547 hashCode = 31 * hashCode + Arrays.hashCode(mKeys); 548 hashCode = 31 * hashCode + Arrays.hashCode(mMap); 549 for (int i = 0; i < mSize; i++) { 550 int row = mMap[i]; 551 for (int j = 0; j < mSize; j++) { 552 hashCode = 31 * hashCode + (valueAtInternal(row, mMap[j]) ? 1 : 0); 553 } 554 } 555 return hashCode; 556 } 557 558 /** 559 * {@inheritDoc} 560 */ 561 @Override equals(@ullable Object that)562 public boolean equals(@Nullable Object that) { 563 if (this == that) { 564 return true; 565 } 566 567 if (!(that instanceof WatchedSparseBooleanMatrix)) { 568 return false; 569 } 570 571 WatchedSparseBooleanMatrix other = (WatchedSparseBooleanMatrix) that; 572 if (mSize != other.mSize) { 573 return false; 574 } 575 if (!Arrays.equals(mKeys, other.mKeys)) { 576 // mKeys is zero padded at the end and is sorted, so the arrays can always be 577 // directly compared. 578 return false; 579 } 580 for (int i = 0; i < mSize; i++) { 581 int row = mMap[i]; 582 for (int j = 0; j < mSize; j++) { 583 int col = mMap[j]; 584 if (valueAtInternal(row, col) != other.valueAtInternal(row, col)) { 585 return false; 586 } 587 } 588 } 589 return true; 590 } 591 592 /** 593 * Return the matrix meta information. This is always three strings long. The 594 * strings are indexed by the constants STRING_KEY_INDEX, STRING_MAP_INDEX, and 595 * STRING_INUSE_INDEX. 596 */ 597 @VisibleForTesting(visibility = PRIVATE) matrixToStringMeta()598 @Size(3) String[] matrixToStringMeta() { 599 String[] result = new String[3]; 600 601 StringBuilder k = new StringBuilder(); 602 for (int i = 0; i < mSize; i++) { 603 k.append(mKeys[i]); 604 if (i < mSize - 1) { 605 k.append(" "); 606 } 607 } 608 result[STRING_KEY_INDEX] = k.substring(0); 609 610 StringBuilder m = new StringBuilder(); 611 for (int i = 0; i < mSize; i++) { 612 m.append(mMap[i]); 613 if (i < mSize - 1) { 614 m.append(" "); 615 } 616 } 617 result[STRING_MAP_INDEX] = m.substring(0); 618 619 StringBuilder u = new StringBuilder(); 620 for (int i = 0; i < mOrder; i++) { 621 u.append(mInUse[i] ? "1" : "0"); 622 } 623 result[STRING_INUSE_INDEX] = u.substring(0); 624 return result; 625 } 626 627 /** 628 * Return the matrix as an array of strings. There is one string per row. Each 629 * string has a '1' or a '0' in the proper column. This is the raw data indexed by 630 * row/column disregarding the key map. 631 */ 632 @VisibleForTesting(visibility = PRIVATE) matrixToStringRaw()633 String[] matrixToStringRaw() { 634 String[] result = new String[mOrder]; 635 for (int i = 0; i < mOrder; i++) { 636 StringBuilder line = new StringBuilder(mOrder); 637 for (int j = 0; j < mOrder; j++) { 638 line.append(valueAtInternal(i, j) ? "1" : "0"); 639 } 640 result[i] = line.substring(0); 641 } 642 return result; 643 } 644 645 /** 646 * Return the matrix as an array of strings. There is one string per row. Each 647 * string has a '1' or a '0' in the proper column. This is the cooked data indexed by 648 * keys, in key order. 649 */ 650 @VisibleForTesting(visibility = PRIVATE) matrixToStringCooked()651 String[] matrixToStringCooked() { 652 String[] result = new String[mSize]; 653 for (int i = 0; i < mSize; i++) { 654 int row = mMap[i]; 655 StringBuilder line = new StringBuilder(mSize); 656 for (int j = 0; j < mSize; j++) { 657 line.append(valueAtInternal(row, mMap[j]) ? "1" : "0"); 658 } 659 result[i] = line.substring(0); 660 } 661 return result; 662 } 663 matrixToString(boolean raw)664 public String[] matrixToString(boolean raw) { 665 String[] meta = matrixToStringMeta(); 666 String[] data; 667 if (raw) { 668 data = matrixToStringRaw(); 669 } else { 670 data = matrixToStringCooked(); 671 } 672 String[] result = new String[meta.length + data.length]; 673 System.arraycopy(meta, 0, result, 0, meta.length); 674 System.arraycopy(data, 0, result, meta.length, data.length); 675 return result; 676 } 677 678 /** 679 * {@inheritDoc} 680 * 681 * <p>This implementation creates a string that describes the size of the array. A 682 * string with all the values could easily exceed 1Mb. 683 */ 684 @Override toString()685 public String toString() { 686 return "{" + mSize + "x" + mSize + "}"; 687 } 688 689 // Copied from android.util.ContainerHelpers, which is not visible outside the 690 // android.util package. binarySearch(int[] array, int size, int value)691 private static int binarySearch(int[] array, int size, int value) { 692 int lo = 0; 693 int hi = size - 1; 694 695 while (lo <= hi) { 696 final int mid = (lo + hi) >>> 1; 697 final int midVal = array[mid]; 698 699 if (midVal < value) { 700 lo = mid + 1; 701 } else if (midVal > value) { 702 hi = mid - 1; 703 } else { 704 return mid; // value found 705 } 706 } 707 return ~lo; // value not present 708 } 709 } 710