HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavaModerate

Find the value nearest to k in the sorted array

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
thearraynearestvaluefindsorted

Problem

Given a sorted array, return the 'closest' element to the input 'x'.

I do understand the merits of unit testing in separate files, but I deliberately added it to the main method for personal convenience, so don't consider that in your feedback.

I'm looking for request code review, optimizations and best practices.

```
public final class ClosestToK {

private ClosestToK() { }

/**
* Given a sorted array returns the 'closest' element to the input 'x'.
* 'closest' is defined as Math.min(Math.abs(x), Math.abs(y))
*
* Expects a sorted array, and if array is not sorted then results are unpredictable.
*
* If two values are equi-distant then the greater value is returned.
*
* @param a The sorted array a
* @return The nearest element
*/
public static int getClosestK(int[] a, int x) {

int low = 0;
int high = a.length - 1;

while (low x) {
return Math.min(Math.abs(a[mid] - x), Math.abs(a[mid + 1] - x)) + x;
}

// keep searching.
if (a[mid] < x) {
low = mid + 1;
} else {
high = mid;
}
}

throw new IllegalArgumentException("The array cannot be empty");
}

public static void main(String[] args) {
// normal case.
int[] a1 = {10, 20, 30, 40};
assertEquals(30, getClosestK(a1, 28));

// equidistant
int[] a2 = {10, 20, 30, 40};
assertEquals(30, getClosestK(a2, 25));

// edge case lower boundary
int[] a3 = {10, 20, 30, 40};
assertEquals(10, getClosestK(a3, 5));
int[] a4 = {10, 20, 30, 40};
assertEquals(10, getClosestK(a4, 10));

// edge case higher boundary
int[] a5 = {10, 20, 30, 40};
assertEquals(40, getClosestK(a5, 45));
int[] a6 = {10, 20, 30, 40};
assertEquals(40, getClosestK(a6, 40));

// case equal to
int[] a

Solution

Here's my solution which seems to work fine :

public static int getClosestK(int[] a, int x) {

    int low = 0;
    int high = a.length - 1;

    if (high < 0)
        throw new IllegalArgumentException("The array cannot be empty");

    while (low < high) {
        int mid = (low + high) / 2;
        assert(mid < high);
        int d1 = Math.abs(a[mid  ] - x);
        int d2 = Math.abs(a[mid+1] - x);
        if (d2 <= d1)
        {
            low = mid+1;
        }
        else
        {
            high = mid;
        }
    }
    return a[high];
}


Here's the principle : the interval [low, high] will contain the closest element to x at any time. At the beginning, this interval is the whole array ([low, high] = [0, length-1]). At each iteration, we'll make it strictly smaller. When the range is limited to a single element, this is the element we are looking for.

In order to make the range smaller, at each iteration, we'll consider mid the middle point of [low, high]. Because of the way mid is computed, mid+1 is also in the range. We'll check if the closest value is at mid or mid+1 and update high or low accordingly. One can check that the range actually gets smaller.

Edit to answer to comments:

As spotted by @Vick-Chijwani , this code doesn't handle perfectly the scenarios where an element appears multiple times in the input. One can add the following working tests to the code :

// case similar elements
    int[] a8 = {10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10};
    assertEquals(10, getClosestK(a8, 9));
    assertEquals(10, getClosestK(a8, 10));
    assertEquals(10, getClosestK(a8, 11));


but this one will fail :

int[] a9 = {1, 2, 100, 100, 101};
   assertEquals(3, getClosestK(a9, 2)); // actually returns 100

Code Snippets

public static int getClosestK(int[] a, int x) {

    int low = 0;
    int high = a.length - 1;

    if (high < 0)
        throw new IllegalArgumentException("The array cannot be empty");

    while (low < high) {
        int mid = (low + high) / 2;
        assert(mid < high);
        int d1 = Math.abs(a[mid  ] - x);
        int d2 = Math.abs(a[mid+1] - x);
        if (d2 <= d1)
        {
            low = mid+1;
        }
        else
        {
            high = mid;
        }
    }
    return a[high];
}
// case similar elements
    int[] a8 = {10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10};
    assertEquals(10, getClosestK(a8, 9));
    assertEquals(10, getClosestK(a8, 10));
    assertEquals(10, getClosestK(a8, 11));
int[] a9 = {1, 2, 100, 100, 101};
   assertEquals(3, getClosestK(a9, 2)); // actually returns 100

Context

StackExchange Code Review Q#47328, answer score: 12

Revisions (0)

No revisions yet.