Существует ли структура данных Java, которая по сути представляет собой ArrayList с двойной индикацией и встроенной интерполяцией? - PullRequest
6 голосов
/ 20 апреля 2010

Я ищу предварительно построенную структуру данных Java со следующими характеристиками:

  1. Он должен выглядеть примерно как ArrayList, но должен позволять индексирование с двойной точностью, а не целыми числами. Обратите внимание, что это означает, что вполне вероятно, что вы увидите индикаторы, которые не совпадают с исходными точками данных (т.е. запрашивают значение, соответствующее ключу "1.5"). EDIT : Для ясности, исходя из комментариев, я не собираюсь изменять реализацию ArrayList. Я ищу похожий интерфейс и опыт разработчика.

  2. Как следствие, возвращаемое значение, вероятно, будет интерполировано. Например, если ключ равен 1,5, возвращаемое значение может быть средним значением значения для ключа 1,0 и значения для ключа 2,0.

  3. Ключи будут отсортированы, но значения не будут увеличиваться монотонно. На самом деле нет уверенности в том, что первая производная значений будет непрерывной (что делает ее плохо подходящей для определенных типов сплайнов).

  4. Свободно доступен только код, пожалуйста.

Для ясности я знаю, как написать такую ​​вещь. Фактически, у нас уже есть реализация этой и некоторых связанных структур данных в унаследованном коде, которую я хочу заменить из-за некоторых проблем с производительностью и кодированием.

Чего я пытаюсь избежать, так это тратить много времени на развертывание своего собственного решения, когда такое может быть уже в JDK , Apache Commons или другой стандартной библиотеке. , Честно говоря, именно такой подход привел этот унаследованный код в ситуацию, в которой он сейчас находится ...

Есть ли такая вещь в свободно доступной библиотеке?

Ответы [ 5 ]

3 голосов
/ 20 апреля 2010

Допустимые значения double в качестве индексов - это довольно большое изменение по сравнению с тем, что делает ArrayList.

Причина этого заключается в том, что массив или список с double в качестве индексов почти по определению будет разреженным массивом , что означает, что он не имеет значения (или в зависимости от вашего определения: фиксированный, известное значение) для почти всех возможных индексов, и только конечное число индексов имеет явный набор значений.

В Java SE нет встроенного класса, который бы поддерживал все это.

Лично я бы реализовал такую ​​структуру данных, как список пропусков (или аналогичная структура данных с быстрым поиском) из (index, value) кортежей с соответствующей интерполяцией.

Редактировать: На самом деле есть довольно хорошее совпадение для внутреннего хранилища (т.е. все, кроме интерполяции): просто используйте NavigableMap, например TreeMap для сохранения сопоставления от индекса к значению.

С этим вы можете легко использовать ceilingEntry() и (при необходимости) higherEntry(), чтобы получить наиболее близкие значения к нужному индексу, а затем интерполировать из них .

2 голосов
/ 17 октября 2010

Если ваша текущая реализация имеет сложность O (log N) для интерполяции значения, то реализация, которую я только что составил, может быть для вас:

package so2675929;

import java.util.Arrays;

public abstract class AbstractInterpolator {
  private double[] keys;
  private double[] values;
  private int size;

  public AbstractInterpolator(int initialCapacity) {
    keys = new double[initialCapacity];
    values = new double[initialCapacity];
  }

  public final void put(double key, double value) {
    int index = indexOf(key);
    if (index >= 0) {
      values[index] = value;
    } else {
      if (size == keys.length) {
        keys = Arrays.copyOf(keys, size + 32);
        values = Arrays.copyOf(values, size + 32);
      }
      int insertionPoint = insertionPointFromIndex(index);
      System.arraycopy(keys, insertionPoint, keys, insertionPoint + 1, size - insertionPoint);
      System.arraycopy(values, insertionPoint, values, insertionPoint + 1, size - insertionPoint);
      keys[insertionPoint] = key;
      values[insertionPoint] = value;
      size++;
    }
  }

  public final boolean containsKey(double key) {
    int index = indexOf(key);
    return index >= 0;
  }

  protected final int indexOf(double key) {
    return Arrays.binarySearch(keys, 0, size, key);
  }

  public final int size() {
    return size;
  }

  protected void ensureValidIndex(int index) {
    if (!(0 <= index && index < size))
      throw new IndexOutOfBoundsException("index=" + index + ", size=" + size);
  }

  protected final double getKeyAt(int index) {
    ensureValidIndex(index);
    return keys[index];
  }

  protected final double getValueAt(int index) {
    ensureValidIndex(index);
    return values[index];
  }

  public abstract double get(double key);

  protected static int insertionPointFromIndex(int index) {
    return -(1 + index);
  }
}

Бетонные интерполяторы должны будут реализовывать только функцию get(double).

Например:

package so2675929;

public class LinearInterpolator extends AbstractInterpolator {

  public LinearInterpolator(int initialCapacity) {
    super(initialCapacity);
  }

  @Override
  public double get(double key) {
    final double minKey = getKeyAt(0);
    final double maxKey = getKeyAt(size() - 1);
    if (!(minKey <= key && key <= maxKey))
      throw new IndexOutOfBoundsException("key=" + key + ", min=" + minKey + ", max=" + maxKey);

    int index = indexOf(key);
    if (index >= 0)
      return getValueAt(index);

    index = insertionPointFromIndex(index);
    double lowerKey = getKeyAt(index - 1);
    double lowerValue = getValueAt(index - 1);
    double higherKey = getKeyAt(index);
    double higherValue = getValueAt(index);

    double rate = (higherValue - lowerValue) / (higherKey - lowerKey);
    return lowerValue + (key - lowerKey) * rate;
  }

}

И, наконец, юнит-тест:

package so2675929;

import static org.junit.Assert.*;

import org.junit.Test;

public class LinearInterpolatorTest {

  @Test
  public void simple() {
    LinearInterpolator interp = new LinearInterpolator(2);
    interp.put(0.0, 0.0);
    interp.put(1.0, 1.0);

    assertEquals(0.0, interp.getValueAt(0), 0.0);
    assertEquals(1.0, interp.getValueAt(1), 0.0);
    assertEquals(0.0, interp.get(0.0), 0.0);
    assertEquals(0.1, interp.get(0.1), 0.0);
    assertEquals(0.5, interp.get(0.5), 0.0);
    assertEquals(0.9, interp.get(0.9), 0.0);
    assertEquals(1.0, interp.get(1.0), 0.0);

    interp.put(0.5, 0.0);

    assertEquals(0.0, interp.getValueAt(0), 0.0);
    assertEquals(0.0, interp.getValueAt(1), 0.0);
    assertEquals(1.0, interp.getValueAt(2), 0.0);
    assertEquals(0.0, interp.get(0.0), 0.0);
    assertEquals(0.0, interp.get(0.1), 0.0);
    assertEquals(0.0, interp.get(0.5), 0.0);
    assertEquals(0.75, interp.get(0.875), 0.0);
    assertEquals(1.0, interp.get(1.0), 0.0);
  }

  @Test
  public void largeKeys() {
    LinearInterpolator interp = new LinearInterpolator(10);
    interp.put(100.0, 30.0);
    interp.put(200.0, 40.0);

    assertEquals(30.0, interp.get(100.0), 0.0);
    assertEquals(35.0, interp.get(150.0), 0.0);
    assertEquals(40.0, interp.get(200.0), 0.0);

    try {
      interp.get(99.0);
      fail();
    } catch (IndexOutOfBoundsException e) {
      assertEquals("key=99.0, min=100.0, max=200.0", e.getMessage());
    }
    try {
      interp.get(201.0);
      fail();
    } catch (IndexOutOfBoundsException e) {
      assertEquals("key=201.0, min=100.0, max=200.0", e.getMessage());
    }
  }

  private static final int N = 10 * 1000 * 1000;

  private double measure(int size) {
    LinearInterpolator interp = new LinearInterpolator(size);
    for (int i = 0; i < size; i++)
      interp.put(i, i);
    double max = interp.size() - 1;
    double sum = 0.0;
    for (int i = 0; i < N; i++)
      sum += interp.get(max * i / N);
    return sum;
  }

  @Test
  public void speed10() {
    assertTrue(measure(10) > 0.0);
  }

  @Test
  public void speed10000() {
    assertTrue(measure(10000) > 0.0);
  }

  @Test
  public void speed1000000() {
    assertTrue(measure(1000000) > 0.0);
  }
}

Таким образом, функциональность, кажется, работает. Я измерял скорость только в некоторых простых случаях, и это говорит о том, что масштабирование будет лучше, чем линейное.

Обновление (2010-10-17T23: 45 + 0200): Я допустил несколько глупых ошибок при проверке аргумента key в LinearInterpolator, и мои модульные тесты не уловили их. Теперь я расширил тесты и исправил код соответствующим образом.

1 голос
/ 30 сентября 2010

В библиотеке Apache commons-math , если вы реализуете UnivariateRealInterpolator и возвращаемое значение его метода интерполяции, который напечатан UnivariateRealFunction , вы будете большая часть пути туда.

Интерфейс интерполятора принимает два массива: x [] и y []. Возвращаемая функция имеет метод value (), который принимает x 'и возвращает интерполированный y'.

Там, где он не обеспечивает ArrayList-подобный опыт, находится в способности добавлять больше значений в диапазон и домен, как если бы Список увеличивался.

Кроме того, они, похоже, нуждаются в некоторых дополнительных функциях интерполяции. В стабильной версии в библиотеке всего 4 реализации. Как отметил комментатор, кажется, что отсутствует «линейный» или что-то еще проще, как ближайший сосед. Может быть, это не совсем интерполяция ...

0 голосов
/ 20 апреля 2010

Ваше описание того, что оно должно быть "как ArrayList", вводит в заблуждение, поскольку то, что вы описали, является одномерным интерполятором и по сути не имеет ничего общего с ArrayList. Вот почему вы получаете предложения для других структур данных, которые IMO отправляет вам по неправильному пути.

Я не знаю ни одного доступного в Java (и не мог легко найти один из них в Google), но я думаю, что вы должны взглянуть на GSL - GNU Scientific Library , которая включает сплайн-интерполятор . Это может быть немного тяжело для того, что вы ищете, поскольку это двумерный интерполятор, но кажется, что вы должны искать что-то вроде этого, а не что-то вроде ArrayList.

Если вы хотите, чтобы он "выглядел как ArrayList", вы всегда можете заключить его в класс Java, который имеет методы доступа, аналогичные интерфейсу List. Однако вы не сможете реализовать интерфейс, так как объявлены методы, принимающие целочисленные индексы.

0 голосов
/ 20 апреля 2010

Это огромное отличие от ArrayList.

То же, что и ответ Иоахима выше, но я, вероятно, реализовал бы это как двоичное дерево, и когда я не нашел то, что искал, усреднил значение следующих наименьших и наибольших значений, которые должны быть быстры ход к.

...