【问题标题】:Find closest value in an ordered list在有序列表中查找最接近的值
【发布时间】:2021-02-24 07:41:47
【问题描述】:

我想知道如何编写一个简单的 java 方法,在已排序的 Integer 列表中找到与给定值最接近的 Integer。

这是我的第一次尝试:

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);
    }
}

您如何看待这个解决方案?我确信有一种更清洁的方式来完成这项工作。

也许这种方法存在于 Java 库中的某个地方,我错过了它?

【问题讨论】:

    标签: java


    【解决方案1】:

    试试这个小方法:

    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;
    }
    

    一些测试用例:

    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));
    }
    

    【讨论】:

    • 此算法的一个主要优点是它不需要对List&lt;Integer&gt; 进行排序(如果使用Collections.binarySearch(..) 则必须如此)。
    • 这种方法性能不好。如果我的数组的值高达 10 000 000,而我想要的是 5 000 000,它将执行 10 000 000 次迭代来找到它。最佳算法是二分法(在这种情况下,它将需要 1 或 2 次迭代。
    • 为什么不使用 if (diff
    • @AdelBoutros 您多久拥有一个包含 10,000,000 个项目的列表?如果经常——我为你感到难过,你的评论是有效的。否则——它只是在挑剔一个很好的答案
    • 使用 java 8 可以简化为一行流in.stream().reduce((p1, p2) -&gt; Math.abs(p1 - of) &lt; Math.abs(p2 - of) ? p1 : p2)
    【解决方案2】:

    Kotlin 很有帮助

    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
    
    

    列表不需要排序

    编辑:从 kotlin 1.4 开始,minBy 已被弃用。首选minByOrNull

    @Deprecated("Use minByOrNull instead.", ReplaceWith("this.minByOrNull(selector)"))
    @DeprecatedSinceKotlin(warningSince = "1.4")
    

    【讨论】:

    • 不错的答案!谢谢
    【解决方案3】:

    没有二分查找的解决方案(利用列表排序的优势):

    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];
    }
    

    【讨论】:

      【解决方案4】:

      为了解决这个问题,我会通过 distanceTo 方法扩展 Comparable 接口。 distanceTo 的实现返回一个 double 值,表示预期的距离,并且与 compareTo 实现的结果兼容。

      以下示例仅用苹果说明了这个想法。您可以按重量、体积或甜度来交换直径。袋子总是返回“最接近”的苹果(大小、重量或味道最相似)

      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;
         }
      }
      

      【讨论】:

        【解决方案5】:

        当然,您可以简单地使用 for 循环来遍历并跟踪您所在的值与该值之间的差异。它看起来更干净,但速度要慢得多。

        见:Finding closest match in collection of numbers

        【讨论】:

          【解决方案6】:

          我认为您所拥有的是最简单和最有效的方法。在排序列表中查找“最接近”的项目并不是编程中经常遇到的事情(您通常会寻找较大的项目或较小的项目)。这个问题只对数字类型有意义,所以不是很通用,因此为它提供一个库函数是不寻常的。

          【讨论】:

          • 通常但不仅仅是数字:它适用于可以测量到项目之间“距离”的所有事物。换句话说:在任何地方都可以问“大/小多少”。
          【解决方案7】:

          未测试

          int[] randomArray; // your array you want to find the closest
                  int theValue; // value the closest should be near to
          
                  for (int i = 0; i < randomArray.length; i++) {
                      int compareValue = randomArray[i];
          
                      randomArray[i] -= theValue;
                  }
          
                  int indexOfClosest = 0;
                  for (int i = 1; i < randomArray.length; i++) {
                      int compareValue = randomArray[i];
          
                      if(Math.abs(randomArray[indexOfClosest] > Math.abs(randomArray[i]){
                          indexOfClosest = i;
                      }
                  }
          

          【讨论】:

            【解决方案8】:

            我认为您的回答可能是返回单个结果的最有效方式。

            但是,您的方法的问题是有 0(如果没有列表)、1 或 2 个可能的解决方案。当您对某个函数有两种可能的解决方案时,您的问题才真正开始:如果这不是最终答案,而只是确定最佳行动方案的一系列步骤中的第一步,以及您没有得到的答案怎么办? return 会提供更好的解决方案吗?唯一正确的做法是考虑两个答案并仅在最后比较进一步处理的结果。

            将平方根函数想象成与此有些类似的问题。

            【讨论】:

              【解决方案9】:

              如果您不太关心性能(假设该集合被搜索了两次),我认为使用 Navigable 集合会导致代码更清晰:

              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);
                }
              }
              

              【讨论】:

                【解决方案10】:

                您的解决方案似乎是渐近最优的。如果它使用 Math.min/max,它可能会稍微快一些(尽管可能不太容易维护)。一个好的 JIT 可能具有使这些速度更快的内在函数。

                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);
                }
                

                【讨论】:

                  【解决方案11】:

                  可能有点晚了,但这会起作用,这是一个数据结构二分搜索:

                  科特林:

                  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
                      }
                  }
                  

                  我希望这会有所帮助,祝您编码愉快。

                  【讨论】:

                    猜你喜欢
                    • 2015-07-26
                    • 2021-11-20
                    • 2020-02-13
                    • 2017-06-25
                    • 2015-08-03
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    相关资源
                    最近更新 更多