Разреженные матрицы / массивы в Java

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

из kajillion ячеек в массиве будет несколько сотен тысяч ячеек которые содержат объект. Мне нужно очень быстро изменить содержимое ячейки.

в любом случае: кто-нибудь знает особенно хорошую библиотеку для этой цели? Это должна быть лицензия Berkeley, LGPL или аналогичная лицензия (без GPL, поскольку продукт не может быть полностью открытым). Или, если есть очень простой способ сделать объект разреженного массива homebrew, это тоже было бы хорошо.

Я считаю MTJ, но не слышал никаких мнений о его качестве.

11 ответов


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

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

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

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

кроме того, Java Hashmaps может индексировать только объекты и создавать целочисленный объект для каждого хэшированного исходного индекса (это создание объекта будет необходимо для каждого чтения, а не только записи) является дорогостоящим с точки зрения операций памяти, так как он подчеркивает сборщик мусора.

я действительно надеялся, что JRE включил IntegerTrieMap в качестве реализации по умолчанию для медленного HashMap или LongTrieMap в качестве реализации по умолчанию для еще более медленного HashMap... Но это все еще не так.



вы можете задаться вопросом, Что такое Триэ?

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

например, предположим, что вам нужна матрица 1024*1024, содержащая только несколько ненулевых значений. Вместо того, чтобы хранить эту матрицу в массиве, содержащем 1024*1024 элемента (более 1 миллиона), вы можете просто разделить ее на поддиапазоны размером 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)]

который будет более удобно обрабатываться с помощью метода доступа для объекта trie.

вот пример, встроенный в комментируемый класс (надеюсь, он компилируется нормально, так как он был упрощен; сообщите мне, если есть ошибки для исправления):

/**
 * 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: вам нужно задаться вопросом, стоит ли удвоенный размер индекса подрангепозиций значения по сравнению с существующим размером значений, чтобы увидеть, предлагает ли он эффективное сжатие (если нет, вы остановитесь на этом: вы нашли оптимальный размер подранга непосредственно из процесса сжатия чередования). В этом случае размер поддиапазона будет изменяться во время уплотнения.

но этот код показывает, как вы назначаете ненулевые значения и перераспределяете the data массив для дополнительных (ненулевых) поддиапазонов, а затем как вы можете оптимизировать (с помощью compact() после выполнения заданий с помощью setAt(int i, int j, double value) метод) хранение этих данных, когда есть дубликаты поддиапазонов, которые могут быть объединены в данных и переиндексированы в той же позиции в subrangePositions массив.

во всяком случае, все принципы trie реализованы там:

  1. он всегда быстрее (и компактнее в памяти, что означает лучше местности) представляют собой матрицу, используя единый вектор, а не дважды индексированный массив массивов (каждый выделен отдельно). Улучшение видно в double getAt(int, int) способ !

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

  3. это возможно автоматическое преобразование исходной большой матрицы в более компактную матрицу путем обнаружения общих поддиапазонов. Типичная реализация будет содержать такой метод, как compact() выше. Однако, если get() access очень быстрый, а set() довольно быстрый, compact () может быть очень медленным, если есть много общих поддиапазонов для сжатия (например, при вычитании большой не разреженной случайно заполненной матрицы с собой или умножении ее на ноль: в этом случае будет проще и намного быстрее сбросьте trie, создав новый экземпляр и отбросив старый).

  4. общие поддиапазоны используют общее хранилище данных, поэтому эти общие данные должны быть доступны только для чтения. Если вы должны изменить одно значение без изменения остальной части матрицы, вы должны сначала убедиться, что на него ссылаются только один раз в . В противном случае вам нужно будет выделить новый поддиапазон в любом месте (удобно в конце)values вектор, а затем сохранить позиции этого нового поддиапазон в .



обратите внимание, что общая библиотека Colt, хотя и очень хорошая, не так хороша при работе с разреженной матрицей, потому что она использует хеширование (или сжатие строк), которые пока не реализуют поддержку попыток, несмотря на отличную оптимизацию, которая экономит пространство и экономия времени, особенно для наиболее частых операций getAt ().

даже операция setAt (), описанная здесь для попыток, экономит много времени (способ реализован здесь, т. е. без автоматического уплотнения после установки, которое все еще может быть реализовано на основе спроса и расчетного времени, когда уплотнение все равно сэкономит много места для хранения по цене времени): экономия времени пропорциональна количеству ячеек в поддиапазонах, а экономия пространства обратно пропорциональна количеству ячеек в поддиапазоне. Хороший компромисс, если затем использовать размер поддиапазона такой число ячеек на поддиапазон-это квадратный корень из общего числа ячеек в 2D-матрице (это будет кубический корень при работе с 3D-матрицей).

методы хэширования, используемые в реализациях разреженной матрицы Colt, имеют неудобство, что они добавляют много накладных расходов на хранение и замедляют время доступа из-за возможных столкновений. Попытки могут избежать всех столкновений, а затем могут гарантировать сохранение линейного времени O(n) до O (1) в худших случаях, где (n) - количество возможных коллизии (которые, в случае разреженной матрицы, могут быть до числа ячеек с нестандартным значением в матрице, т. е. до общего числа размеров матрицы, умноженного на коэффициент, пропорциональный коэффициенту заполнения хэширования, для не разреженной, т. е. полной матрицы).

методы RC (row-compressed), используемые в Colt, ближе от попыток, но это по другой цене, здесь используются методы сжатия, которые имеют очень медленное время доступа для наиболее частых операций get () только для чтения, и очень медленное сжатие для операций setAt (). Кроме того, используемое сжатие не является ортогональным, в отличие от этого представления попыток, где ортогональность сохраняется. Попытки также будут сохранять эту ортогональность для связанных операций просмотра, таких как шаг, транспозиция (рассматриваемая как операция шага на основе целочисленных циклических модульных операций), подзадача (и подзадачи в целом, в том числе с сортировкой представлений).

я просто надеюсь, что Colt будет обновлен в некоторых будущее для реализации другой реализации с использованием попыток (т. е. TrieSparseMatrix вместо просто HashSparseMatrix и RCSparseMatrix). Идеи в этой статье.

реализация Trove (основанная на int->int maps) также основана на технике хэширования, подобной HashedSparseMatrix Colt, т. е. они имеют те же неудобства. Попытки будут намного быстрее, с умеренным дополнительным пространством (но это пространство может быть оптимизировано и стать еще лучше, чем Trove и Colt, в отложенное время, используя окончательную компактную () ионную операцию на результирующей матрице / trie).

Примечание: эта реализация Trie привязана к определенному собственному типу (здесь double). Это является добровольным, потому что универсальная реализация с использованием типов бокса имеет огромные накладные расходы (и намного медленнее во времени). Здесь он просто использует собственные одномерные массивы двойного, а не общего вектора. Но, безусловно, можно получить общую реализацию, а также для попыток... К сожалению, Java по-прежнему не позволяет писать действительно универсальные классы со всеми преимуществами собственных типов, за исключением написания нескольких реализаций (для универсального типа объекта или для каждого собственного типа) и обслуживания всех этих операций через фабрику типов. Язык должен иметь возможность автоматически создавать экземпляры собственных реализаций и автоматически создавать фабрику (на данный момент это не так даже в Java 7, и это то, где .Net по-прежнему сохраняет свое преимущество для действительно универсальные типы, которые так же быстры, как и собственные типы).


следующая структура для тестирования матричных библиотек Java также предоставляет хороший список из них! 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) 

вот статья, которая может вас заинтересовать, в которой говорится о структурах данных для матричных вычислений, включая разреженные массивы:

http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.13.7544

вы можете скачать документ в формате PDF или PS. Он также включает исходный код.


может быть Кольт поможет. Он обеспечивает разреженную реализацию матрицы.


Это, кажется, простой.

вы можете использовать двоичное дерево данных, используя строку * maxcolums + столбец в качестве индекса.

чтобы найти элемент, вы просто вычисляете строку * maxcolums + столбец и двоичный поиск дерева, ищущего его, если его нет, вы можете вернуть null (это О(log n), где n-количество ячеек, которые содержат объект).


не самое быстрое решение для выполнения, вероятно, но самое быстрое, что я мог придумать, похоже, работает. Создать класс и использовать его в качестве ключа для 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)));

мой класс индекса выглядит так (с некоторой помощью генератора кода 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;
    }
}

Я только что использовал Trove что обеспечило гораздо лучшую производительность, чем Colt при использовании карты int->int (используется для реализации разреженной матрицы).


вы migth смотреть на la4j (линейная алгебра для Java) библиотека. Он поддерживает CRS (сжатое хранилище строк) а также CCS (сжатый столбец хранения) внутренние representaions для разреженных матриц. Таким образом, это наиболее эффективные и быстрые внутренние структуры для разреженных данных.

вот краткий пример использования разреженных матриц в 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

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

 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

нулевая проверка и т. д. опущена для краткости



скалы HashMap. Просто объедините индексы (как строки) с разделителем, скажем '/', используя StringBuilder (Не + или String.format), и используйте это как ключ. Вы не можете получить быстрее и эффективнее памяти, чем это. Разреженные матрицы-это soo 20th century. :-)