web-dev-qa-db-ja.com

Javaのスパース行列/配列

私はJavaで書かれたプロジェクトに取り組んでいますが、これには非常に大きな2Dスパース配列を構築する必要があります。それが違いを生む場合、非常にまばらです。とにかく、このアプリケーションの最も重要な側面は、時間の面での効率です(標準の2次元配列を使用できるほど無制限ではありませんが、メモリの負荷を想定します-キー範囲は両次元で数十億です) )。

配列内の無数のセルのうち、オブジェクトを含む数十万個のセルがあります。セルの内容を非常に迅速に変更できる必要があります。

とにかく:この目的のために特に良いライブラリを知っている人はいますか?バークレー、LGPL、または同様のライセンスでなければなりません(製品は完全にオープンソース化できないため、GPLはありません)。または、自作のスパース配列オブジェクトを作成する非常に簡単な方法がある場合でも、それは問題ありません。

[〜#〜] mtj [〜#〜] を検討していますが、その品質については意見がありません。

63
DanM

ハッシュマップを使用して構築されたスパース配列は、頻繁に読み取られるデータに対して非常に非効率的です。最も効率的な実装では、セグメントが分散されている単一のベクトルにアクセスできるTrieを使用します。

Trieは、読み取り専用TWO配列インデックスのみを実行して要素が格納されている有効な位置を取得するか、基礎となるストアに要素が存在しないかどうかを知ることで、テーブルに要素が存在するかどうかを計算できます。

また、スパース配列のデフォルト値のバッキングストア内のデフォルト位置を提供することもできます。これにより、Trieはすべての可能なソースインデックスが少なくともデフォルトにマッピングされることを保証するため、返されたインデックスに対してテストを行う必要はありませんバッキングストア内の位置(ゼロ、空の文字列、またはnullオブジェクトを頻繁に格納する場所)。

複数の操作の終了時にバッキングストアのサイズを最適化するための、otionalの「compact()」操作で、高速更新可能なTriesをサポートする実装が存在します。試行は、複雑なハッシュ関数を必要とせず、読み取りの衝突を処理する必要がないため、ハッシュマップよりもはるかに高速です(ハッシュマップを使用すると、読み取りと書き込みの両方に衝突が発生します。次の候補者の位置、および有効なソースインデックスを比較するためのそれぞれのテスト...)

さらに、Javaハッシュマップはオブジェクトのみにインデックスを付けることができ、ハッシュされたソースインデックスごとに整数オブジェクトを作成します(このオブジェクトの作成は、書き込みだけでなく読み取りごとに必要になります)ガベージコレクターに負荷をかけるメモリ操作。

JREが、遅いHashMap <Integer、Object>のデフォルト実装としてIntegerTrieMap <Object>を含むこと、またはさらに遅いHashMap <Long、Object>のデフォルト実装としてLongTrieMap <Object>を含むことを本当に望みました...しかし、これはまだそうではありません。



あなたはトライとは何でしょうか?

これは、ベクトルの整数位置に座標をマッピングできる整数の小さな配列(マトリックスの座標の全範囲よりも狭い範囲)です。

たとえば、ゼロ以外のいくつかの値のみを含む1024 * 1024マトリックスが必要だとします。そのマトリックスを1024 * 1024要素(100万以上)を含む配列に格納する代わりに、サイズ16 * 16のサブ範囲に分割するだけで、64 * 64のサブ範囲が必要になります。

その場合、Trieインデックスには64 * 64整数(4096)だけが含まれ、少なくとも16 * 16データ要素(デフォルトのゼロまたはスパース行列で見つかった最も一般的な部分範囲を含む)があります。

そして、値を保存するために使用されるベクトルには、互いに等しい部分範囲のコピーが1つだけ含まれます(それらのほとんどはゼロでいっぱいで、同じ部分範囲で表されます)。

したがって、_matrix[i][j]_のような構文を使用する代わりに、次のような構文を使用します。

_trie.values[trie.subrangePositions[(i & ~15) + (j >> 4)] +
            ((i & 15) << 4) + (j & 15)]
_

これは、トライオブジェクトのアクセスメソッドを使用してより便利に処理されます。

コメント付きクラスに組み込まれた例を次に示します(単純化されたため、正常にコンパイルされることを望みます。訂正するエラーがある場合は、私に合図してください)

_/**
 * Implement a sparse matrix. Currently limited to a static size
 * (<code>SIZE_I</code>, <code>SIZE_I</code>).
 */
public class DoubleTrie {

    /* Matrix logical options */        
    public static final int SIZE_I = 1024;
    public static final int SIZE_J = 1024;
    public static final double DEFAULT_VALUE = 0.0;

    /* Internal splitting options */
    private static final int SUBRANGEBITS_I = 4;
    private static final int SUBRANGEBITS_J = 4;

    /* Internal derived splitting constants */
    private static final int SUBRANGE_I =
        1 << SUBRANGEBITS_I;
    private static final int SUBRANGE_J =
        1 << SUBRANGEBITS_J;
    private static final int SUBRANGEMASK_I =
        SUBRANGE_I - 1;
    private static final int SUBRANGEMASK_J =
        SUBRANGE_J - 1;
    private static final int SUBRANGE_POSITIONS =
        SUBRANGE_I * SUBRANGE_J;

    /* Internal derived default values for constructors */
    private static final int SUBRANGES_I =
        (SIZE_I + SUBRANGE_I - 1) / SUBRANGE_I;
    private static final int SUBRANGES_J =
        (SIZE_J + SUBRANGE_J - 1) / SUBRANGE_J;
    private static final int SUBRANGES =
        SUBRANGES_I * SUBRANGES_J;
    private static final int DEFAULT_POSITIONS[] =
        new int[SUBRANGES](0);
    private static final double DEFAULT_VALUES[] =
        new double[SUBRANGE_POSITIONS](DEFAULT_VALUE);

    /* Internal fast computations of the splitting subrange and offset. */
    private static final int subrangeOf(
            final int i, final int j) {
        return (i >> SUBRANGEBITS_I) * SUBRANGE_J +
               (j >> SUBRANGEBITS_J);
    }
    private static final int positionOffsetOf(
            final int i, final int j) {
        return (i & SUBRANGEMASK_I) * MAX_J +
               (j & SUBRANGEMASK_J);
    }

    /**
     * Utility missing in Java.lang.System for arrays of comparable
     * component types, including all native types like double here.
     */
    public static final int arraycompare(
            final double[] values1, final int position1,
            final double[] values2, final int position2,
            final int length) {
        if (position1 >= 0 && position2 >= 0 && length >= 0) {
            while (length-- > 0) {
                double value1, value2;
                if ((value1 = values1[position1 + length]) !=
                    (value2 = values2[position2 + length])) {
                    /* Note: NaN values are different from everything including
                     * all Nan values; they are are also neigher lower than nor
                     * greater than everything including NaN. Note that the two
                     * infinite values, as well as denormal values, are exactly
                     * ordered and comparable with <, <=, ==, >=, >=, !=. Note
                     * that in comments below, infinite is considered "defined".
                     */
                    if (value1 < value2)
                        return -1;        /* defined < defined. */
                    if (value1 > value2)
                        return 1;         /* defined > defined. */
                    if (value1 == value2)
                        return 0;         /* defined == defined. */
                    /* One or both are NaN. */
                    if (value1 == value1) /* Is not a NaN? */
                        return -1;        /* defined < NaN. */
                    if (value2 == value2) /* Is not a NaN? */
                        return 1;         /* NaN > defined. */
                    /* Otherwise, both are NaN: check their precise bits in
                     * range 0x7FF0000000000001L..0x7FFFFFFFFFFFFFFFL
                     * including the canonical 0x7FF8000000000000L, or in
                     * range 0xFFF0000000000001L..0xFFFFFFFFFFFFFFFFL.
                     * Needed for sort stability only (NaNs are otherwise
                     * unordered).
                     */
                    long raw1, raw2;
                    if ((raw1 = Double.doubleToRawLongBits(value1)) !=
                        (raw2 = Double.doubleToRawLongBits(value2)))
                        return raw1 < raw2 ? -1 : 1;
                    /* Otherwise the NaN are strictly equal, continue. */
                }
            }
            return 0;
        }
        throw new ArrayIndexOutOfBoundsException(
                "The positions and length can't be negative");
    }

    /**
     * Utility shortcut for comparing ranges in the same array.
     */
    public static final int arraycompare(
            final double[] values,
            final int position1, final int position2,
            final int length) {
        return arraycompare(values, position1, values, position2, length);
    }

    /**
     * Utility missing in Java.lang.System for arrays of equalizable
     * component types, including all native types like double here.
     */ 
    public static final boolean arrayequals(
            final double[] values1, final int position1,
            final double[] values2, final int position2,
            final int length) {
        return arraycompare(values1, position1, values2, position2, length) ==
            0;
    }

    /**
     * Utility shortcut for identifying ranges in the same array.
     */
    public static final boolean arrayequals(
            final double[] values,
            final int position1, final int position2,
            final int length) {
        return arrayequals(values, position1, values, position2, length);
    }

    /**
     * Utility shortcut for copying ranges in the same array.
     */
    public static final void arraycopy(
            final double[] values,
            final int srcPosition, final int dstPosition,
            final int length) {
        arraycopy(values, srcPosition, values, dstPosition, length);
    }

    /**
     * Utility shortcut for resizing an array, preserving values at start.
     */
    public static final double[] arraysetlength(
            double[] values,
            final int newLength) {
        final int oldLength =
            values.length < newLength ? values.length : newLength;
        System.arraycopy(values, 0, values = new double[newLength], 0,
            oldLength);
        return values;
    }

    /* Internal instance members. */
    private double values[];
    private int subrangePositions[];
    private bool isSharedValues;
    private bool isSharedSubrangePositions;

    /* Internal method. */
    private final reset(
            final double[] values,
            final int[] subrangePositions) {
        this.isSharedValues =
            (this.values = values) == DEFAULT_VALUES;
        this.isSharedsubrangePositions =
            (this.subrangePositions = subrangePositions) ==
                DEFAULT_POSITIONS;
    }

    /**
     * Reset the matrix to fill it with the same initial value.
     *
     * @param initialValue  The value to set in all cell positions.
     */
    public reset(final double initialValue = DEFAULT_VALUE) {
        reset(
            (initialValue == DEFAULT_VALUE) ? DEFAULT_VALUES :
                new double[SUBRANGE_POSITIONS](initialValue),
            DEFAULT_POSITIONS);
    }

    /**
     * Default constructor, using single default value.
     *
     * @param initialValue  Alternate default value to initialize all
     *                      positions in the matrix.
     */
    public DoubleTrie(final double initialValue = DEFAULT_VALUE) {
        this.reset(initialValue);
    }

    /**
     * This is a useful preinitialized instance containing the
     * DEFAULT_VALUE in all cells.
     */
    public static DoubleTrie DEFAULT_INSTANCE = new DoubleTrie();

    /**
     * Copy constructor. Note that the source trie may be immutable
     * or not; but this constructor will create a new mutable trie
     * even if the new trie initially shares some storage with its
     * source when that source also uses shared storage.
     */
    public DoubleTrie(final DoubleTrie source) {
        this.values = (this.isSharedValues =
            source.isSharedValues) ?
            source.values :
            source.values.clone();
        this.subrangePositions = (this.isSharedSubrangePositions =
            source.isSharedSubrangePositions) ?
            source.subrangePositions :
            source.subrangePositions.clone());
    }

    /**
     * Fast indexed getter.
     *
     * @param i  Row of position to set in the matrix.
     * @param j  Column of position to set in the matrix.
     * @return   The value stored in matrix at that position.
     */
    public double getAt(final int i, final int j) {
        return values[subrangePositions[subrangeOf(i, j)] +
                      positionOffsetOf(i, j)];
    }

    /**
     * Fast indexed setter.
     *
     * @param i      Row of position to set in the sparsed matrix.
     * @param j      Column of position to set in the sparsed matrix.
     * @param value  The value to set at this position.
     * @return       The passed value.
     * Note: this does not compact the sparsed matric after setting.
     * @see compact(void)
     */
    public double setAt(final int i, final int i, final double value) {
       final int subrange       = subrangeOf(i, j);
       final int positionOffset = positionOffsetOf(i, j);
       // Fast check to see if the assignment will change something.
       int subrangePosition, valuePosition;
       if (Double.compare(
               values[valuePosition =
                   (subrangePosition = subrangePositions[subrange]) +
                   positionOffset],
               value) != 0) {
               /* So we'll need to perform an effective assignment in values.
                * Check if the current subrange to assign is shared of not.
                * Note that we also include the DEFAULT_VALUES which may be
                * shared by several other (not tested) trie instances,
                * including those instanciated by the copy contructor. */
               if (isSharedValues) {
                   values = values.clone();
                   isSharedValues = false;
               }
               /* Scan all other subranges to check if the position in values
                * to assign is shared by another subrange. */
               for (int otherSubrange = subrangePositions.length;
                       --otherSubrange >= 0; ) {
                   if (otherSubrange != subrange)
                       continue; /* Ignore the target subrange. */
                   /* Note: the following test of range is safe with future
                    * interleaving of common subranges (TODO in compact()),
                    * even though, for now, subranges are sharing positions
                    * only between their common start and end position, so we
                    * could as well only perform the simpler test <code>
                    * (otherSubrangePosition == subrangePosition)</code>,
                    * instead of testing the two bounds of the positions
                    * interval of the other subrange. */
                   int otherSubrangePosition;
                   if ((otherSubrangePosition =
                           subrangePositions[otherSubrange]) >=
                           valuePosition &&
                           otherSubrangePosition + SUBRANGE_POSITIONS <
                           valuePosition) {
                       /* The target position is shared by some other
                        * subrange, we need to make it unique by cloning the
                        * subrange to a larger values vector, copying all the
                        * current subrange values at end of the new vector,
                        * before assigning the new value. This will require
                        * changing the position of the current subrange, but
                        * before doing that, we first need to check if the
                        * subrangePositions array itself is also shared
                        * between instances (including the DEFAULT_POSITIONS
                        * that should be preserved, and possible arrays
                        * shared by an external factory contructor whose
                        * source trie was declared immutable in a derived
                        * class). */
                       if (isSharedSubrangePositions) {
                           subrangePositions = subrangePositions.clone();
                           isSharedSubrangePositions = false;
                       }
                       /* TODO: no attempt is made to allocate less than a
                        * fully independant subrange, using possible
                        * interleaving: this would require scanning all
                        * other existing values to find a match for the
                        * modified subrange of values; but this could
                        * potentially leave positions (in the current subrange
                        * of values) unreferenced by any subrange, after the
                        * change of position for the current subrange. This
                        * scanning could be prohibitively long for each
                        * assignement, and for now it's assumed that compact()
                        * will be used later, after those assignements. */
                       values = setlengh(
                           values,
                           (subrangePositions[subrange] =
                            subrangePositions = values.length) +
                           SUBRANGE_POSITIONS);
                       valuePosition = subrangePositions + positionOffset;
                       break;
                   }
               }
               /* Now perform the effective assignment of the value. */
               values[valuePosition] = value;
           }
       }
       return value;
    }

    /**
     * Compact the storage of common subranges.
     * TODO: This is a simple implementation without interleaving, which
     * would offer a better data compression. However, interleaving with its
     * O(N²) complexity where N is the total length of values, should
     * be attempted only after this basic compression whose complexity is
     * O(n²) with n being SUBRANGE_POSITIIONS times smaller than N.
     */
    public void compact() {
        final int oldValuesLength = values.length;
        int newValuesLength = 0;
        for (int oldPosition = 0;
                 oldPosition < oldValuesLength;
                 oldPosition += SUBRANGE_POSITIONS) {
            int oldPosition = positions[subrange];
            bool commonSubrange = false;
            /* Scan values for possible common subranges. */
            for (int newPosition = newValuesLength;
                    (newPosition -= SUBRANGE_POSITIONS) >= 0; )
                if (arrayequals(values, newPosition, oldPosition,
                        SUBRANGE_POSITIONS)) {
                    commonSubrange = true;
                    /* Update the subrangePositions|] with all matching
                     * positions from oldPosition to newPosition. There may
                     * be several index to change, if the trie has already
                     * been compacted() before, and later reassigned. */
                    for (subrange = subrangePositions.length;
                         --subrange >= 0; )
                        if (subrangePositions[subrange] == oldPosition)
                            subrangePositions[subrange] = newPosition;
                    break;
                }
            if (!commonSubrange) {
                /* Move down the non-common values, if some previous
                 * subranges have been compressed when they were common.
                 */
                if (!commonSubrange && oldPosition != newValuesLength) {
                    arraycopy(values, oldPosition, newValuesLength,
                        SUBRANGE_POSITIONS);
                    /* Advance compressed values to preserve these new ones. */
                    newValuesLength += SUBRANGE_POSITIONS;
                }
            }
        }
        /* Check the number of compressed values. */
        if (newValuesLength < oldValuesLength) {
            values = values.arraysetlength(newValuesLength);
            isSharedValues = false;
        }
    }

}
_

注:このコードは単一のマトリックスサイズを処理するため完全ではなく、コンパクターはインターリーブせずに共通のサブレンジのみを検出するように制限されています。

また、コードは、マトリックスのサイズに従って、マトリックスをサブレンジに分割するために使用する最適な幅または高さを決定しません(xまたはy座標の場合)。同じ静的サブ範囲サイズの16(両方の座標)を使用しますが、2のべき乗以外の任意の小さな累乗にすることができます(ただし、2の累乗ではint indexOf(int, int)およびint offsetOf(int, int)内部メソッド)、両方の座標に独立して、最大でマトリックスの幅または高さまで。理想的にはcompact()メソッドは最適なサイズを決定できるはずです。

これらの分割サブ範囲のサイズが異なる場合、静的_SUBRANGE_POSITIONS_の代わりにこれらのサブ範囲サイズのインスタンスメンバーを追加し、静的メソッドint subrangeOf(int i, int j)およびint positionOffsetOf(int i, int j)非静的に;また、初期化配列_DEFAULT_POSITIONS_および_DEFAULT_VALUES_は、異なる方法でドロップまたは再定義する必要があります。

インターリーブをサポートする場合、基本的には、既存の値をほぼ同じサイズの2つに分割することから始めます(両方とも最小サブレンジサイズの倍数であり、最初のサブセットは2番目のサブレンジよりも1つ多くのサブレンジを持つ可能性があります)。連続するすべての位置で大きい方をスキャンして、一致するインターリーブを見つけます。次に、これらの値を一致させようとします。次に、サブセットを半分に分割して再帰的にループし(最小サブレンジサイズの倍数)、これらのサブセットに一致するように再度スキャンします(これにより、サブセットの数が2倍になります。 subrangePositionsインデックスのサイズは、有効な圧縮を提供するかどうかを確認するために、既存の値のサイズと比較する価値があります(そうでない場合は、そこで停止します:インターリーブ圧縮プロセスから直接最適なサブレンジサイズを見つけました)。ケース;圧縮中、サブレンジサイズは変更可能です。

ただし、このコードは、ゼロ以外の値を割り当て、追加の(ゼロ以外の)サブ範囲にdata配列を再割り当てする方法と、割り当ての実行後に(compact()で)最適化する方法を示していますsetAt(int i, int j, double value)メソッドを使用して)データ内で統合され、subrangePositions配列内の同じ位置で再インデックスされる重複サブ範囲がある場合、このデータのストレージ。

とにかく、トライのすべての原則がそこに実装されています:

  1. 配列のダブルインデックス配列(各配列は個別に割り当てられる)の代わりに単一のベクトルを使用して行列を表す方が、常に高速(およびメモリ内のコンパクト化、局所性の向上)です。改善はdouble getAt(int, int)メソッドで見ることができます!

  2. 多くのスペースを節約できますが、値を割り当てるときに新しいサブ範囲を再割り当てするのに時間がかかる場合があります。このため、サブレンジは小さすぎてはなりません。さもないと、マトリックスをセットアップするために再割り当てが頻繁に発生します。

  3. 共通の部分範囲を検出することにより、初期の大きな行列をよりコンパクトな行列に自動的に変換することができます。通常の実装には、上記のcompact()などのメソッドが含まれます。ただし、get()アクセスが非常に高速でset()が非常に高速である場合、圧縮する共通のサブ範囲が多数あると、compact()は非常に遅くなる可能性があります(たとえば、大きな非スパースでランダムに満たされた行列をそれ自体で減算する場合) 、またはゼロを乗算します。その場合、新しいトライをインスタンス化して古いトライを削除することでトライをリセットする方が簡単ではるかに高速になります)。

  4. 共通のサブ範囲はデータの共通ストレージを使用するため、この共有データは読み取り専用である必要があります。マトリックスの残りを変更せずに単一の値を変更する必要がある場合、最初にsubrangePositionsインデックスで1回だけ参照されることを確認する必要があります。それ以外の場合は、valuesベクトルの任意の場所(都合の良いことに末尾)に新しいサブ範囲を割り当ててから、この新しいサブ範囲の位置をsubrangePositionsインデックスに保存する必要があります。



汎用のColtライブラリは、非常に優れているものの、まばらな行列での作業には適していないことに注意してください。なぜなら、それは、優れた最適化。スペース節約および時間節約の両方であり、特に最も頻繁なgetAt()操作に最適です。

ここで説明したsetAt()操作でも時間を節約できます(方法はここで実装されています。つまり、設定後の自動圧縮なしで、要求と推定時間に基づいて実装できます。時間の価格):時間の節約はサブ範囲内のセルの数に比例し、スペースの節約はサブ範囲ごとのセルの数に反比例します。サブレンジあたりのセル数が2Dマトリックス内のセルの総数の平方根であるようなサブレンジサイズを使用する場合は、妥協することをお勧めします(3Dマトリックスで作業する場合は立方根になります)。

Coltのスパースマトリックス実装で使用されるハッシュテクニックには、大量のストレージオーバーヘッドが追加され、衝突の可能性があるためアクセス時間が遅くなるという不便さがあります。試行はすべての衝突を回避でき、その後、線形O(n)時間をO(1)時間を節約することを保証できます。ここで、(n )は、起こり得る衝突の数です(スパースマトリックスの場合、マトリックス内のデフォルト値以外のセルの数まで、つまり、マトリックスのサイズの合計数×非スパース、つまりフルマトリックスの場合のハッシュ充填係数)。

Coltで使用されるRC(行圧縮)技術はTriesに近いですが、これは別の価格です。ここでは、使用される圧縮技術であり、最も頻繁な読み取り専用get()操作のアクセス時間が非常に遅く、 setAt()操作の圧縮。さらに、使用される圧縮は、直交性が保持されているこのTriesのプレゼンテーションとは異なり、直交していません。トライは、ストライド、転置(整数巡回モジュラー操作に基づくストライド操作として表示)、サブレンジング(およびビューの並べ替えを含む一般的なサブ選択)などの関連する表示操作に対してもこの直交性を維持します。

将来的にはColtが更新され、Triesを使用した別の実装(HashSparseMatrixとRCSparseMatrixだけでなくTrieSparseMatrix)を実装することを願っています。アイデアはこの記事にあります。

Troveの実装(int-> intマップに基づく)も、ColtのHashedSparseMatrixと同様のハッシュテクニックに基づいています。つまり、それらは同じ不便さを持っています。試行ははるかに高速になり、適度な追加のスペースが消費されます(ただし、このスペースは最適化され、結果のマトリックス/トライで最終的なcompact()ion操作を使用して、遅延時間でTroveとColtよりもさらに良くなります)。

注:このTrie実装は、特定のネイティブタイプ(ここではdouble)にバインドされています。ボクシングタイプを使用した一般的な実装には大きなスペースオーバーヘッドがあるため、これは任意です。ここでは、汎用Vectorではなく、doubleのネイティブな1次元配列を使用しています。しかし、確かにTriesの一般的な実装を導出することも可能です...残念ながら、Javaは、複数の実装を書くことを除いて、ネイティブ型のすべての利点を備えた本当に一般的なクラスを書くことをまだ許可していません(汎用オブジェクトタイプまたは各ネイティブタイプ)、およびこれらすべての操作をタイプファクトリを介して提供します。言語は、ネイティブ実装を自動的にインスタンス化し、ファクトリを自動的に構築できる必要があります(現時点では= Java7。これは、.Netがネイティブ型と同じくらい高速な実際のジェネリック型の利点を依然として維持している場所です)。

72
verdy_p

テストするフレームワークに従って、Java Matrix Libraries、これらの適切なリストも提供します! https://lessthanoptimal.github.io/Java-Matrix-Benchmark/

テスト済みのライブラリ:

* Colt
* Commons Math
* Efficient Java Matrix Library (EJML)
* Jama
* jblas
* JScience (Older benchmarks only)
* Matrix Toolkit Java (MTJ)
* OjAlgo
* Parallel Colt
* Universal Java Matrix Package (UJMP) 
10
Laurent Ho

これは簡単なようです。

Row * maxcolums + columnをインデックスとして使用して、データのバイナリツリーを使用できます。

アイテムを見つけるには、単にrow * maxcolums + columnを計算し、それを探してツリーをバイナリ検索します。ない場合は、nullを返すことができます(nはオブジェクトを含むセルの数ですО(log n)です)。

3

おそらく最速のランタイムソリューションではありませんが、私が思いついた最速のソリューションはうまくいくようです。 Indexクラスを作成し、次のようにSortedMapのキーとして使用します。

    SortedMap<Index, Object> entries = new TreeMap<Index, Object>();
    entries.put(new Index(1, 4), "1-4");
    entries.put(new Index(5555555555l, 767777777777l), "5555555555l-767777777777l");
    System.out.println(entries.size());
    System.out.println(entries.get(new Index(1, 4)));
    System.out.println(entries.get(new Index(5555555555l, 767777777777l)));

私のIndexクラスはこのように見えます(Eclipseコードジェネレーターの助けを借りて)。

public static class Index implements Comparable<Index>
{
    private long x;
    private long y;

    public Index(long x, long y)
    {
        super();
        this.x = x;
        this.y = y;
    }

    public int compareTo(Index index)
    {
        long ix = index.x;
        if (ix == x)
        {
            long iy = index.y;
            if (iy == y)
            {
                return 0;
            }
            else if (iy < y)
            {
                return -1;
            }
            else
            {
                return 1;
            }
        }
        else if (ix < x)
        {
            return -1;
        }
        else
        {
            return 1;
        }
    }

    public int hashCode()
    {
        final int PRIME = 31;
        int result = 1;
        result = PRIME * result + (int) (x ^ (x >>> 32));
        result = PRIME * result + (int) (y ^ (y >>> 32));
        return result;
    }

    public boolean equals(Object obj)
    {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        final Index other = (Index) obj;
        if (x != other.x)
            return false;
        if (y != other.y)
            return false;
        return true;
    }

    public long getX()
    {
        return x;
    }

    public long getY()
    {
        return y;
    }
}
2
eljenso

la4j (Java用の線形代数)ライブラリをご覧ください。 CRS(圧縮行ストレージ) および CCS(圧縮列ストレージ) スパース行列の内部表現をサポートします。したがって、これらはスパースデータの最も効率的で高速な内部構造です。

la4j でスパース行列を使用する簡単な例を次に示します。

Matrix a = new CRSMatrix(new double[][]{ // 'a' - CRS sparse matrix
   { 1.0, 0.0, 3.0 },
   { 0.0, 5.0, 0.0 },
   { 7.0, 0.0. 9.0 }
});

Matrix b = a.transpose(); // 'b' - CRS sparse matrix

Matrix c = b.multiply(a, Matrices.CCS_FACTORY); // 'c' = 'b' * 'a'; 
                                                // 'c' - CCS sparse matrix
2

ネストされたマップを使用できますが、その上でマトリックス計算を行う必要がある場合、それは最良のオプションではない可能性があります

 Map<Integer, Map<integer, Object>> matrix;

オブジェクトの代わりに、実際のデータにタプルを使用して、抽出後に簡単に作業できるようにすることができます。

class Tuple<T extends yourDataObject> {
  public final int x;
  public final int y;
  public final T object;
}

class Matrix {
  private final Map<Integer, Map<interger, Tupple>> data = new...;

 void add(int x, int y, Object object) {
     data.get(x).put(new Tupple(x,y,object);
 }
}


//etc

簡潔にするためにnullチェックなどを省略

0
pvgoddijn