Je travaille sur un projet, écrit en Java, qui nécessite que je construise un très grand tableau 2D éparse. Très rare, si cela fait une différence. Quoi qu'il en soit: l'aspect le plus crucial pour cette application est l'efficacité en termes de temps (supposez des charges de mémoire, bien que pas presque illimitées pour me permettre d'utiliser un tableau 2D standard - la plage de clés est dans les milliards dans les deux dimensions ).
Sur les kajillions de cellules du réseau, il y aura plusieurs centaines de milliers de cellules contenant un objet. Je dois pouvoir modifier le contenu des cellules TRÈS rapidement.
Quoi qu'il en soit: quelqu'un connaît-il une bibliothèque particulièrement bonne à cet effet? Il faudrait que ce soit Berkeley, LGPL ou une licence similaire (pas de GPL, car le produit ne peut pas être entièrement open source). Ou s'il y a juste un moyen très simple de créer un objet tableau homebrew clairsemé, ce serait bien aussi.
J'envisage MTJ , mais je n'ai entendu aucune opinion sur sa qualité.
Les tableaux épars construits avec des hashmaps sont très inefficaces pour les données fréquemment lues. Les implémentations les plus efficaces utilisent un Trie qui permet d'accéder à un seul vecteur où les segments sont distribués.
Un Trie peut calculer si un élément est présent dans la table en effectuant uniquement une indexation TWO array en lecture seule pour obtenir la position effective où un élément est stocké, ou pour savoir s'il est absent du magasin sous-jacent.
Il peut également fournir une position par défaut dans le magasin de sauvegarde pour la valeur par défaut du tableau épars, de sorte que vous n'avez besoin d'aucun test sur l'index renvoyé, car le Trie garantit que tout l'index source possible correspondra au moins à la valeur par défaut position dans le magasin de sauvegarde (où vous stockerez fréquemment un zéro, une chaîne vide ou un objet nul).
Il existe des implémentations qui prennent en charge les essais à mise à jour rapide, avec une opération émotionnelle "compact ()" pour optimiser la taille du magasin de sauvegarde à la fin de plusieurs opérations. Les essais sont BEAUCOUP plus rapides que les hashmaps, car ils n'ont besoin d'aucune fonction de hachage complexe et n'ont pas besoin de gérer les collisions pour les lectures (avec les hashtags, vous avez à la fois des collisions pour la lecture et pour l'écriture, cela nécessite une boucle pour passer à la poste candidat suivant, et un test sur chacun d'eux pour comparer l'indice de source efficace ...)
De plus, Java Les Hashmaps ne peuvent indexer que sur les objets, et créer un objet Integer pour chaque index source haché (cette création d'objet sera nécessaire pour chaque lecture, pas seulement pour les écritures) est coûteux en termes de opérations de mémoire, car il met l'accent sur le ramasse-miettes.
J'espérais vraiment que le JRE incluait un IntegerTrieMap <Object> comme implémentation par défaut pour le HashMap lent <Integer, Object> ou LongTrieMap <Object> comme implémentation par défaut pour le HashMap encore plus lent <Long, Object> ... Mais c'est toujours pas le cas.
Vous vous demandez peut-être ce qu'est un Trie?
C'est juste un petit tableau d'entiers (dans une plage plus petite que la plage complète de coordonnées pour votre matrice) qui permet de mapper les coordonnées dans une position entière dans un vecteur.
Par exemple, supposons que vous vouliez une matrice 1024 * 1024 contenant uniquement quelques valeurs non nulles. Au lieu de stocker cette matrice dans un tableau contenant 1024 * 1024 éléments (plus d'un million), vous souhaiterez peut-être simplement la diviser en sous-plages de taille 16 * 16, et vous aurez juste besoin de 64 * 64 de telles sous-plages.
Dans ce cas, l'index Trie ne contiendra que 64 * 64 entiers (4096), et il y aura au moins 16 * 16 éléments de données (contenant les zéros par défaut ou la sous-gamme la plus courante trouvée dans votre matrice clairsemée).
Et le vecteur utilisé pour stocker les valeurs ne contiendra qu'une seule copie pour les sous-plages qui sont égales les unes aux autres (la plupart étant remplies de zéros, elles seront représentées par la même sous-plage).
Donc, au lieu d'utiliser une syntaxe comme matrix[i][j]
, Vous utiliseriez une syntaxe comme:
trie.values[trie.subrangePositions[(i & ~15) + (j >> 4)] +
((i & 15) << 4) + (j & 15)]
qui sera plus facilement gérée en utilisant une méthode d'accès pour l'objet trie.
Voici un exemple, intégré dans une classe commentée (j'espère qu'il compile bien, car il a été simplifié; signalez-moi s'il y a des erreurs à corriger):
/**
* 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;
}
}
}
Remarque: ce code n'est pas complet car il gère une seule taille de matrice, et son compacteur est limité pour détecter uniquement les sous-gammes communes, sans les entrelacer.
De plus, le code ne détermine pas où est la meilleure largeur ou hauteur à utiliser pour diviser la matrice en sous-plages (pour les coordonnées x ou y), en fonction de la taille de la matrice. Il utilise simplement les mêmes tailles de sous-plages statiques de 16 (pour les deux coordonnées), mais il pourrait être commodément n'importe quelle autre petite puissance de 2 (mais une non-puissance de 2 ralentirait la fonction int indexOf(int, int)
et int offsetOf(int, int)
méthodes internes), indépendamment pour les deux coordonnées, et jusqu'à la largeur ou la hauteur maximale de la matrice. Idéalement, la méthode compact()
devrait être en mesure de déterminer les meilleures tailles d'ajustement.
Si ces tailles de sous-plages de fractionnement peuvent varier, il sera alors nécessaire d'ajouter des membres d'instance pour ces tailles de sous-plages au lieu de la fonction statique SUBRANGE_POSITIONS
, Et de rendre les méthodes statiques int subrangeOf(int i, int j)
et int positionOffsetOf(int i, int j)
en non statique; et les tableaux d'initialisation DEFAULT_POSITIONS
et DEFAULT_VALUES
devront être supprimés ou redéfinis différemment.
Si vous souhaitez prendre en charge l'entrelacement, vous commencerez essentiellement par diviser les valeurs existantes en deux d'environ la même taille (les deux étant un multiple de la taille de sous-plage minimale, le premier sous-ensemble pouvant éventuellement avoir une sous-plage de plus que le second), et vous numériserez le plus grand à toutes les positions successives pour trouver un entrelacement correspondant; alors vous essaierez de faire correspondre ces valeurs. Ensuite, vous bouclerez récursivement en divisant les sous-ensembles en deux (également un multiple de la taille minimale de la sous-plage) et vous numériserez à nouveau pour correspondre à ces sous-ensembles (cela multipliera le nombre de sous-ensembles par 2: vous devez vous demander si le doublé La taille de l'index subrangePositions vaut la valeur par rapport à la taille existante des valeurs pour voir si elle offre une compression efficace (sinon, vous vous arrêtez là: vous avez trouvé la taille optimale de la sous-gamme directement à partir du processus de compression entrelacé). cas; la taille de la sous-gamme sera modifiable, lors du compactage.
Mais ce code montre comment vous affectez des valeurs non nulles et réaffectez le tableau data
pour des sous-plages supplémentaires (non nulles), puis comment vous pouvez optimiser (avec compact()
après que les affectations ont été effectuées en utilisant la méthode setAt(int i, int j, double value)
) le stockage de ces données lorsqu'il existe des sous-plages en double qui peuvent être unifiées dans les données et réindexées à la même position dans le tableau subrangePositions
.
Quoi qu'il en soit, tous les principes d'un trie y sont mis en œuvre:
Il est toujours plus rapide (et plus compact en mémoire, ce qui signifie une meilleure localité) de représenter une matrice en utilisant un seul vecteur au lieu d'un tableau à double indexation de tableaux (chacun étant alloué séparément). L'amélioration est visible dans la méthode double getAt(int, int)
!
Vous économisez beaucoup d'espace, mais lors de l'attribution de valeurs, la réaffectation de nouvelles sous-plages peut prendre du temps. Pour cette raison, les sous-plages ne doivent pas être trop petites ou des réaffectations se produiront trop fréquemment pour la configuration de votre matrice.
Il est possible de transformer automatiquement une grande matrice initiale en une matrice plus compacte en détectant des sous-plages communes. Une implémentation typique contiendra alors une méthode telle que compact()
ci-dessus. Cependant, si l'accès get () est très rapide et que set () est assez rapide, compact () peut être très lent s'il y a beaucoup de sous-plages communes à compresser (par exemple lors de la soustraction d'une grande matrice non remplie au hasard avec elle-même) , ou en le multipliant par zéro: il sera plus simple et beaucoup plus rapide dans ce cas de réinitialiser le trie en instanciant un nouveau et en supprimant l'ancien).
Les sous-plages communes utilisent un stockage commun dans les données, donc ces données partagées doivent être en lecture seule. Si vous devez modifier une seule valeur sans modifier le reste de la matrice, vous devez d'abord vous assurer qu'elle n'est référencée qu'une seule fois dans l'index subrangePositions
. Sinon, vous devrez allouer une nouvelle sous-plage n'importe où (commodément à la fin) du vecteur values
, puis stocker la position de cette nouvelle sous-plage dans l'index subrangePositions
.
Notez que la bibliothèque générique Colt, bien que très bonne, n'est pas aussi bonne lorsque vous travaillez sur une matrice clairsemée, car elle utilise des techniques de hachage (ou compressées en ligne) qui n'implémentent pas le support des essais pour l'instant, bien qu'il s'agisse d'un excellente optimisation, qui permet à la fois de gagner de la place et de gagner du temps, notamment pour les opérations getAt () les plus fréquentes.
Même l'opération setAt () décrite ici pour les essais permet de gagner beaucoup de temps (la manière est implémentée ici, c'est-à-dire sans compactage automatique après le réglage, qui pourrait toujours être implémentée en fonction de la demande et du temps estimé où le compactage économiserait encore beaucoup d'espace de stockage à le prix du temps): le gain de temps est proportionnel au nombre de cellules dans les sous-gammes, et le gain d'espace est inversement proportionnel au nombre de cellules par sous-gamme. Un bon compromis si alors d'utiliser une taille de sous-gamme telle que le nombre de cellules par sous-gamme est la racine carrée du nombre total de cellules dans une matrice 2D (ce serait une racine cubique lorsque vous travaillez avec une matrice 3D).
Les techniques de hachage utilisées dans les implémentations à matrice clairsemée Colt présentent l'inconvénient d'ajouter beaucoup de surcharge de stockage et de ralentir le temps d'accès en raison de possibles collisions. Les essais peuvent éviter toutes les collisions et peuvent alors garantir un gain de temps linéaire O(n) à O(1) temps dans les pires cas, où (n ) est le nombre de collisions possibles (qui, dans le cas d'une matrice clairsemée, peut aller jusqu'au nombre de cellules sans valeur par défaut dans la matrice, c'est-à-dire jusqu'au nombre total de taille de la matrice multiplié par un facteur proportionnel à la facteur de remplissage de hachage, pour une matrice non clairsemée, c'est-à-dire pleine).
Les techniques RC (compressées en ligne) utilisées dans Colt sont plus proches de Tries, mais c'est à un autre prix, ici les techniques de compression utilisées, qui ont un temps d'accès très lent pour les opérations get () en lecture seule les plus fréquentes, et très lent compression pour les opérations setAt (). De plus, la compression utilisée n'est pas orthogonale, contrairement à cette présentation de Tries où l'orthogonalité est préservée. Les essais conserveraient également cette orthogonalité pour les opérations de visualisation associées telles que le striding, la transposition (considérée comme une opération de striding basée sur des opérations modulaires cycliques entières), la sous-configuration (et les sous-sélections en général, y compris avec les vues de tri).
J'espère juste que Colt sera mis à jour dans un avenir proche pour implémenter une autre implémentation en utilisant Tries (c'est-à-dire TrieSparseMatrix au lieu de simplement HashSparseMatrix et RCSparseMatrix). Les idées sont dans cet article.
L'implémentation de Trove (basée sur des cartes int-> int) est également basée sur des techniques de hachage similaires à HashedSparseMatrix de Colt, c'est-à-dire qu'elles ont le même inconvénient. Les essais seront beaucoup plus rapides, avec un espace supplémentaire modéré consommé (mais cet espace peut être optimisé et devenir encore meilleur que Trove et Colt, dans un temps différé, en utilisant une opération ionique compact () finale sur la matrice/trie résultante).
Remarque: cette implémentation de Trie est liée à un type natif spécifique (ici double). Ceci est volontaire, car l'implémentation générique utilisant des types de boxe a un énorme espace (et est beaucoup plus lente en temps d'accès). Ici, il utilise simplement des tableaux unidimensionnels natifs de vecteur double plutôt que générique. Mais il est certainement possible de dériver une implémentation générique également pour Tries ... Malheureusement, Java ne permet toujours pas d'écrire des classes vraiment génériques avec tous les avantages des types natifs, sauf en écrivant plusieurs implémentations (pour un type d'objet générique ou pour chaque type natif), et desservant toutes ces opérations via une fabrique de types. Le langage devrait être capable d'instancier automatiquement les implémentations natives et de construire la fabrique automatiquement (pour l'instant ce n'est pas le cas même en = Java 7, et c'est quelque chose où .Net conserve toujours son avantage pour les types vraiment génériques qui sont aussi rapides que les types natifs).
Framework suivant pour tester Java Matrix Libraries, fournit également une bonne liste de celles-ci! https://lessthanoptimal.github.io/Java-Matrix-Benchmark/
Bibliothèques testées:
* 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)
Cela semble simple.
Vous pouvez utiliser un arbre binaire des données en utilisant la ligne * maxcolums + column comme index.
Pour trouver un élément, il vous suffit de calculer la ligne * maxcolums + colonne et de rechercher dans l'arborescence à la recherche de celui-ci, s'il n'est pas là, vous pouvez retourner null (c'est О (log n) où n est le nombre de cellules qui contiennent un objet).
Ce n'est probablement pas la solution d'exécution la plus rapide, mais la plus rapide que j'ai pu trouver semble fonctionner. Créez une classe d'index et utilisez-la comme clé pour un SortedMap, comme:
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)));
Ma classe Index ressemble à ceci (avec l'aide du générateur de code 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;
}
}
Vous devez regarder la bibliothèque la4j (Algèbre linéaire pour Java). Il prend en charge CRS (Compressed Row Storage) ainsi que CCS (Compressed Column Storage) representaions internes pour les matrices clairsemées. Ce sont donc les structures internes les plus efficaces et les plus rapides pour les données éparses.
Voici un bref exemple d'utilisation de matrices clairsemées dans 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
vous pouvez simplement utiliser une carte imbriquée bien que si vous avez besoin de faire un calcul matriciel dessus, ce ne soit pas la meilleure option
Map<Integer, Map<integer, Object>> matrix;
peut-être qu'au lieu d'objet, utilisez un tuple pour les données réelles afin que vous puissiez travailler plus facilement après l'extraction, quelque chose comme:
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
vérification nulle, etc. omise par souci de concision