30

I am wondering how you would write a simple java method finding the closest Integer to a given value in a sorted Integer list.

Here is my first attempt:

public class Closest {

    private static List<Integer> integers = new ArrayList<Integer>();

    static {
        for (int i = 0; i <= 10; i++) {
            integers.add(Integer.valueOf(i * 10));
        }
    }

    public static void main(String[] args) {

        Integer closest = null;
        Integer arg = Integer.valueOf(args[0]);

        int index = Collections.binarySearch(
                integers, arg);

        if (index < 0) /*arg doesn't exist in integers*/ {
            index = -index - 1;
            if (index == integers.size()) {
                closest = integers.get(index - 1);
            } else if (index == 0) {
                closest = integers.get(0);
            } else {
                int previousDate = integers.get(index - 1);
                int nextDate =  integers.get(index);
                if (arg - previousDate < nextDate - arg) {
                    closest = previousDate;
                } else {
                    closest = nextDate;
                }
            }
        } else /*arg exists in integers*/ {
            closest = integers.get(index);
        }
        System.out.println("The closest Integer to " + arg + " in " + integers
                + " is " + closest);
    }
}

What do you think about this solution ? I am sure there is a cleaner way to do this job.

Maybe such method exists somewhere in the Java libraries and I missed it ?

Vadim Kotov
  • 8,084
  • 8
  • 48
  • 62
Manuel Selva
  • 18,554
  • 22
  • 89
  • 134

10 Answers10

35

try this little method:

public int closest(int of, List<Integer> in) {
    int min = Integer.MAX_VALUE;
    int closest = of;

    for (int v : in) {
        final int diff = Math.abs(v - of);

        if (diff < min) {
            min = diff;
            closest = v;
        }
    }

    return closest;
}

some testcases:

private final static List<Integer> list = Arrays.asList(10, 20, 30, 40, 50);

@Test
public void closestOf21() {
    assertThat(closest(21, list), is(20));
}

@Test
public void closestOf19() {
    assertThat(closest(19, list), is(20));
}

@Test
public void closestOf20() {
    assertThat(closest(20, list), is(20));
}
dfa
  • 114,442
  • 31
  • 189
  • 228
  • A major advantage of this algorithm is that it doesn't require `List` to be sorted (as it would have to be if `Collections.binarySearch(..)` is used). – Boris Brudnoy Jun 22 '11 at 20:06
  • 5
    This approach is bad performance wise. If my array has values up to 10 000 000 and what I want is 5 000 000, it will do 10 000 000 iterations to find it. The optimal alogrithm si the Dichotomy one (In that case it will take 1 or 2 iterations. – Adel Boutros Mar 16 '14 at 11:36
  • why not use if (diff <= min) ? – hesham.shabana Nov 27 '15 at 14:11
  • @AdelBoutros how often do you have a list containing 10,000,000 items? If often – I feel sorry for you and your comment is valid. Otherwise – it's just nitpicking over a great answer – jujka Aug 13 '18 at 12:57
  • With java 8 it can be simplified to one line with stream `in.stream().reduce((p1, p2) -> Math.abs(p1 - of) < Math.abs(p2 - of) ? p1 : p2)` – cmenti Mar 19 '20 at 10:06
  • This solution is `O(n)` and does not take advantage of the data being sorted. Sorted data allows for a `O(log n)` solution using slightly modified binary search. – David Soroko Jul 04 '21 at 18:09
22

Kotlin is so helpful

fun List<Int>.closestValue(value: Int) = minBy { abs(value - it) }

val values = listOf(1, 8, 4, -6)

println(values.closestValue(-7)) // -6
println(values.closestValue(2)) // 1
println(values.closestValue(7)) // 8

List doesn't need to be sorted BTW

Edit: since kotlin 1.4, minBy is deprecated. Prefer minByOrNull

@Deprecated("Use minByOrNull instead.", ReplaceWith("this.minByOrNull(selector)"))
@DeprecatedSinceKotlin(warningSince = "1.4")
Nicolas Duponchel
  • 1,219
  • 10
  • 17
3

A solution without binary search (takes advantage of list being sorted):

public int closest(int value, int[] sorted) {
  if(value < sorted[0])
    return sorted[0];

  int i = 1;
  for( ; i < sorted.length && value > sorted[i] ; i++);

  if(i >= sorted.length)
    return sorted[sorted.length - 1];

  return Math.abs(value - sorted[i]) < Math.abs(value - sorted[i-1]) ?
         sorted[i] : sorted[i-1];
}
user3367701
  • 823
  • 1
  • 8
  • 17
1

To solve the problem, I'd extend the Comparable Interface by a distanceTo method. The implementation of distanceTo returns a double value that represents the intended distance and which is compatible with the result of the compareTo implementation.

The following example illustrates the idea with just apples. You can exchange diameter by weight, volume or sweetness. The bag will always return the 'closest' apple (most similiar in size, wight or taste)

public interface ExtComparable<T> extends Comparable<T> {
   public double distanceTo(T other);
}

public class Apple implements Comparable<Apple> {
   private Double diameter;

   public Apple(double diameter) {
      this.diameter = diameter;
   }

   public double distanceTo(Apple o) {
      return diameter - o.diameter;
   }

   public int compareTo(Apple o) {
      return (int) Math.signum(distanceTo(o));
   }
}

public class AppleBag {
   private List<Apple> bag = new ArrayList<Apple>();

   public addApples(Apple...apples){
      bag.addAll(Arrays.asList(apples));
      Collections.sort(bag);
   }

   public removeApples(Apple...apples){
      bag.removeAll(Arrays.asList(apples));
   }

   public Apple getClosest(Apple apple) {
      Apple closest = null;
      boolean appleIsInBag = bag.contains(apple);
      if (!appleIsInBag) {
         bag.addApples(apple);
      }

      int appleIndex = bag.indexOf(apple);
      if (appleIndex = 0) {
         closest = bag.get(1);
      } else if(appleIndex = bag.size()-1) {
         closest = bag.get(bag.size()-2);
      } else {
         double absDistToPrev = Math.abs(apple.distanceTo(bag.get(appleIndex-1));
         double absDistToNext = Math.abs(apple.distanceTo(bag.get(appleIndex+1));
         closest = bag.get(absDistToNext < absDistToPrev ? next : previous);
      }

      if (!appleIsInBag) {
         bag.removeApples(apple);
      }

      return closest;
   }
}
Andreas Dolk
  • 113,398
  • 19
  • 180
  • 268
1

Probably a bit late, but this WILL work, this is a data structure binary search:

Kotlin:

fun binarySearch(list: List<Int>, valueToCompare: Int): Int {
    var central: Int
    var initialPosition = 0
    var lastPosition: Int
    var centralValue: Int
    lastPosition = list.size - 1
    while (initialPosition <= lastPosition) {
        central = (initialPosition + lastPosition) / 2 //Central index
        centralValue = list[central]                   //Central index value
        when {
            valueToCompare == centralValue -> {
                return centralValue          //found; returns position
            }
            valueToCompare < centralValue -> {
                lastPosition = central - 1  //position changes to the previous index
            }
                else -> {
                initialPosition = central + 1 //position changes to next index
                }
            }
    }
    return -1 //element not found
}

Java:

public int binarySearch(int list[], int valueToCompare) {
    int central;
    int centralValue;
    int initialPosition = 0;
    int lastPosition = list . length -1;
    while (initialPosition <= lastPosition) {
        central = (initialPosition + lastPosition) / 2; //central index
        centralValue = list[central]; //central index value
        if (valueToCompare == centralValue) {
            return centralValue; //element found; returns position
        } else if (valueToCompare < centralValue) {
            lastPosition = central - 1; //Position changes to the previous index
        } else {
            initialPosition = central + 1; //Position changes to the next index
        }
        return -1; //element not found
    }
}

I hope this helps, happy coding.

RickJo
  • 86
  • 1
  • 8
0

Certainly you can simply use a for loop to go through the and keep track of the difference between the value you are on and the value. It would look cleaner, but be much slower.

See: Finding closest match in collection of numbers

Community
  • 1
  • 1
AlbertoPL
  • 11,479
  • 5
  • 49
  • 73
0

I think what you have is about the simplest and most efficient way to do it. Finding the "closest" item in a sorted list isn't something that is commonly encountered in programming (you typically look for the one that is bigger, or the one that is smaller). The problem only makes sense for numeric types, so is not very generalizable, and thus it would be unusual to have a library function for it.

newacct
  • 119,665
  • 29
  • 163
  • 224
  • Typically but not only numbers: it's applicable to everything where one can measure the 'distance' between to items. In other words: everywhere where one can ask 'how much bigger/smaller'. – Andreas Dolk Jul 27 '09 at 11:37
0

I think your answer is probably the most efficient way to return a single result.

However, the problem with your approach is that there are 0 (if there is no list), 1, or 2 possible solutions. It's when you have two possible solutions to a function that your problems really start: What if this is not the final answer, but only the first in a series of steps to determine an optimal course of action, and the answer that you didn't return would have provided a better solution? The only correct thing to do would be to consider both answers and compare the results of further processing only at the end.

Think of the square root function as a somewhat analogous problem to this.

Galghamon
  • 2,012
  • 18
  • 27
0

If you're not massively concerned on performance (given that the set is searched twice), I think using a Navigable set leads to clearer code:

public class Closest
{
  private static NavigableSet<Integer> integers = new TreeSet<Integer>();

  static
  {
    for (int i = 0; i <= 10; i++)
    {
      integers.add(Integer.valueOf(i * 10));
    }
  }

  public static void main(String[] args)
  {
    final Integer arg = Integer.valueOf(args[0]);
    final Integer lower = integers.lower(arg);
    final Integer higher = integers.higher(arg);

    final Integer closest;
    if (lower != null)
    {
      if (higher != null)
        closest = (higher - arg > arg - lower) ? lower : higher;
      else
        closest = lower;
    }
    else
      closest = higher;

    System.out.println("The closest Integer to " + arg + " in " + integers + " is " + closest);
  }
}
SimonC
  • 6,590
  • 1
  • 23
  • 40
0

Your solution appears to be asymptotically optimal. It might be slightly faster (though probably less maintainable) if it used Math.min/max. A good JIT likely has intrinsics that make these fast.

int index = Collections.binarySearch(integers, arg);
if (index < 0) {
    int previousDate = integers.get(Math.max(0, -index - 2));
    int nextDate = integers.get(Math.min(integers.size() - 1, -index - 1));
    closest = arg - previousDate < nextDate - arg ? previousDate : nextDate;
} else {
    closest = integers.get(index);
}
Brett Kail
  • 33,593
  • 2
  • 85
  • 90