6

次の特性を持つ事前構築済みの Java データ構造を探しています。

  1. ArrayList のように見える必要がありますが、整数ではなく倍精度によるインデックスを許可する必要があります。これは、元のデータ ポイントと一致しない指標が表示される可能性が高いことを意味することに注意してください (つまり、キー "1.5" に対応する値を求めます)。 編集: 明確にするために、コメントに基づいて、ArrayList 実装を変更するつもりはありません。同様のインターフェースと開発者エクスペリエンスを探しています。

  2. 結果として、返される値は補間される可能性があります。たとえば、キーが 1.5 の場合、返される値は、キー 1.0 の値とキー 2.0 の値の平均になります。

  3. キーはソートされますが、値が単調に増加することは保証されません。実際、値の一次導関数が連続であるという保証はありません (特定のタイプのスプラインには適合しません)。

  4. 自由に利用できるコードのみでお願いします。

明確にするために、私はそのようなことを書く方法を知っています。実際、これといくつかの関連するデータ構造の実装が既にレガシー コードにあり、パフォーマンスとコーディングの問題のために置き換えたいと考えています。

私が避けようとしているのは、JDKApache Commons、または別の標準ライブラリに既にそのようなものが存在する可能性があるときに、独自のソリューションを展開するのに多くの時間を費やすことです。率直に言って、それはまさに、このレガシー コードを現在の状況に陥らせたアプローチです....

無料で利用できるライブラリにそのようなものはありますか?

4

5 に答える 5

4

double値をインデックスとして許可することは、実際に行うこととはかなり大きな変更ArrayListです。

この理由は、doubleインデックスとしての配列またはリストは、定義上、ほぼsparse arrayになるためです。つまり、ほとんどすべての可能なインデックスに対して値がなく (または定義によっては、固定された既知の値)、有限の値しかありません。インデックスの数には明示的な値が設定されています。

Java SE には、これらすべてをサポートするビルド済みのクラスはありません。

個人的には、適切な補間を使用して、タプルのスキップ リスト(または同様の高速検索データ構造)などのデータ構造を実装します。(index, value)

編集:実際には、バックエンド ストレージ (つまり、補間を除くすべて) にかなりよく一致します: 単純に aNavigableMapなどを使用しTreeMapて、インデックスから値へのマッピングを格納します。

ceilingEntry()これにより、 and (必要な場合)を簡単に使用して、必要なhigherEntry()インデックスに最も近い値を取得し、それらから補間することができます。

于 2010-04-20T14:35:39.093 に答える
3

あなたの現在の実装が値を補間するための複雑さ 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、単体テストではそれらをキャッチできませんでした。ここで、テストを拡張し、それに応じてコードを修正しました。

于 2010-10-17T09:04:55.030 に答える
1

Apache commons-math libraryでは、 UnivariateRealInterpolatorと、型がUnivariateRealFunctionであるその interpolate メソッドの戻り値を実装すると、ほとんどの場合、そこに到達します。

補間インターフェイスは、x[] と y[] の 2 つの配列を取ります。返される関数には、x' を取り、補間された y' を返す value() メソッドがあります。

ArrayList のようなエクスペリエンスを提供できないのは、Listが成長しているかのように範囲とドメインに値を追加する機能です。

さらに、いくつかの追加の補間機能が必要なようです。安定版リリースのライブラリには 4 つの実装しかありません。コメンターが指摘したように、「線形」または最近傍のようなさらに単純なものが欠落しているようです。多分それは本当に補間ではありません...

于 2010-09-29T20:13:16.863 に答える
0

「ArrayListのように」あるべきであるというあなたの説明は誤解を招きます。なぜなら、あなたが説明したのは1次元補間器であり、本質的にArrayListと共通点がないからです。これが、IMO が間違ったパスを送信している他のデータ構造の提案を取得している理由です。

私はJavaで利用できるものを知りません(そして、Googleで簡単に見つけることができませんでした)が、スプライン補間器を含むGSL - GNU Scientific Libraryを見てみるべきだと思います。2 次元インターポレーターであるため、探しているものには少し重いかもしれませんが、ArrayList のようなものではなく、このようなものを探す必要があるようです。

「ArrayList のように見える」ようにしたい場合は、List インターフェイスに似たアクセス メソッドを持つ Java クラスでいつでもラップできます。ただし、メソッドは整数インデックスを取るように宣言されているため、実際にインターフェイスを実装することはできません。

于 2010-04-20T14:59:01.060 に答える
0

これは ArrayList からの大きな変更点です。

上記のヨアヒムの回答と同じですが、おそらくこれを二分木として実装し、探しているものが見つからない場合は、次に小さい値と最大値の値を平均します。これはすばやくトラバースできるはずです。

于 2010-04-20T14:37:46.873 に答える