1
votes

Mappage un-à-un efficace entre la valeur et l'index dans un tableau

J'ai besoin d'obtenir l'index (c'est-à-dire la position) d'une valeur dans un tableau, et je me demande s'il existe un moyen plus rapide que d'utiliser la commande find , en construisant une sorte de carte ou table de recherche qui contient le mappage entre les valeurs du tableau et les index.

Prenons, par exemple, ce tableau:

0 -> 1
5 -> 2
10 -> 3
15 -> 4
20 -> 5
25 -> 6

Maintenant, disons que j'ai une variable avec la valeur

idx = angle2i(angle)

et je veux savoir où cette valeur est positionnée dans le tableau (la bonne réponse est idx = 12 ). Maintenant, je pourrais bien sûr simplement utiliser find:

idx = find(th==angle)

Mais mon problème est que dans mon code, je dois faire cette recherche, pour obtenir l'index en e pour la valeur en angle , plusieurs (millions) fois, et cela semble un peu un gaspillage de ressources d'appeler constamment le find , qui, je suppose, parcourt th et fait une sorte de comparaison.

Au lieu de cela, j'espérais qu'il y avait un moyen de configurer une carte un-à-un ou une table de recherche, où je peux juste récupérer immédiatement l'index correspondant à la valeur que j'ai dans angle . (Remarque: je sais que la valeur que j'ai dans angle correspondra toujours exactement à l'une des valeurs dans th .) Donc, ayez juste une fonction

angle = 55

qui effectue ce mappage:

th = [0 5 10 15 20 25 30 35 40 45 50 55 60 65 70 75 80 85 90];

etc.

Mais je ne vois pas comment je dois implémenter un tel recherche (enfin, j'ai quelques idées très peu élégantes, j'espère et je suppose qu'il doit y avoir une approche intelligente pour cela). Ou est-ce que je perds mon temps ici et devrais-je simplement utiliser la commande find ?


3 commentaires

eh bien vous avez déjà une fonction de cartographie, que selon votre exemple e j'écrirais comme angle2i = @ (angle) (angle / 5) +1; . Je ne sais pas si c'est plus rapide que find (vous devrez profiler votre code pour estimer). Là où vous pourriez potentiellement gagner beaucoup de temps, c'est s'il pouvait être vectorisé, c'est-à-dire si vous avez déjà toutes les valeurs d'angle à rechercher dans un tableau, puis avec la fonction de mappage, calculez un tableau de tous les indices pertinents en une seule opération, puis dans votre boucle, choisissez simplement l'index de ce tableau (pas besoin d'appels répétés à votre fonction de mappage ou à find .


Salut @Hoki, merci pour la réponse. Vous avez raison pour ce cas particulier, mais j'espérais une procédure générale, quand il n'y a pas de relation apparente entre les valeurs et les index.


s'il n'y a pas de relation calculable entre les valeurs et les index, votre meilleure option est d'utiliser une carte comme indiqué dans Réponse de Chris Luengo .


5 Réponses :


3
votes

S'il existe un contexte numérique entre l'index et la valeur comme dans votre exemple, vous pouvez simplement l'utiliser comme une fonction au lieu d'une table de recherche:

function idx=angle2i(angle)
idx=angle/5+1;
end

Mais Je ne sais pas si cela résout votre problème, car je ne connais pas votre problème spécifique.


4 commentaires

Merci pour la réponse. Eh bien, vous avez raison, pour ce cas particulier, je pourrais résoudre cela avec une relation numérique. Mais j'ai aussi des cas où il n'y a pas de relation apparente entre l'indice et la valeur, donc j'aurais besoin d'une sorte de tableau qui relie les deux ... Je ne sais toujours pas comment je procéderais pour l'implémenter.


Dans ce cas, je ne pense pas qu'il existe une solution générale en cas d'optimisation de l'exécution à votre problème sans connaître la taille du tableau et la structure de valeur spécifiques. La fonction «trouver» fait exactement ce que vous essayez d'atteindre, ce qui ne veut pas dire que c'est la méthode la plus efficace pour chaque application. En ne connaissant pas le problème spécifique, la seule chance que je vois est d'essayer vos "idées très peu élégantes" et d'utiliser MATLAB Profiler pour probablement trouver le plus rapide (s'il y en a un).


mattesyo, n'hésitez pas à laisser un commentaire sur ma réponse si vous souhaitez mettre en garde contre les frais généraux. Mais ne supprimez pas la moitié et changez-la pour dire quelque chose que je n'écrirais pas. Il existe une raison de rejet pour les suggestions de modification "en conflit avec l'intention de l'auteur". La correction des fautes de frappe et la correction du formatage sont OK. Changer le contenu d'une réponse n'est pas OK. Ici , vous pouvez voir vos suggestions de modifications et les décisions qui ont été prises à leur sujet.


En fait, ce commentaire du PO suggère qu'ils recherchent effectivement ce que @CrisLuengo a écrit, une solution générale. Cas classique d'un exemple simplifié à l'extrême dans la question OMI




4
votes

Si vos clés sont des entiers, vous pourriez peut-être utiliser un tableau fragmenté! Considérez la situation suivante:

function t = q55725607
%% Define the mapping(s):
keys = (1:60).*5; % Keys must be integers!
values = 1:numel(keys); 
% Construct an array such that `map(key) == value`
map = sparse(ones(numel(keys),1), keys, values);

% Compare to containers.Map:
hashmap = containers.Map(keys, values);

%% Try this out:
queryKeys = randi(60,5000000,1).*5;
queryKeysCell = num2cell(queryKeys);

t = [timeit(@f1,1), timeit(@f2,1)];

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function queryVals = f1()
  queryVals = reshape(full(map(queryKeys)), size(queryKeys));
end

function queryVals = f2
  queryVals = hashmap.values(queryKeysCell);
end

end

Je ne sais pas si la comparaison que j'ai faite est juste, mais si c'est le cas, l'approche clairsemée est un ordre de grandeur plus rapide sur mon système ( 0.1549 vs 1.5685).

BTW, au cas où ce ne serait pas clair, la raison de l'utilisation d'un tableau sparse est parce qu'il ne prend de la place que pour les valeurs non nulles (donc même si vous avez des indices comme 1 et 10E5, vous ne stockeriez que 2 valeurs, en gros ).


2 commentaires

C'est assez intelligent! Je vais faire une comparaison pour mon cas et voir ce qui s'avère plus rapide. Merci pour votre participation!


@FinnurPind De rien! J'espère que cela s'avère utile.



3
votes

Si vous disposez de toutes les (plusieurs millions) valeurs de angle , vous pouvez utiliser ismember :

[~, idx] = ismember(angle, th);


0 commentaires

3
votes

Benchmark

En commençant par le benchmark de Dev-iL , j'ai ajouté la find d'OP et la méthode ismember de rahnema1, et regardez comment les méthodes évoluent avec la taille des données (nombre de clés). Je compare également deux cas d'utilisation différents: trouver 5000 clés à la fois et trouver 5000 clés une à la fois.

Voici les résultats:

function t = so(N)
% Define the mapping(s):
keys = (1:N).*5; % Keys must be positive integers!
values = 1:N; 

% Sparse lookup table
sparseMap = sparse(ones(numel(keys),1), keys, values);

% containers.Map lookup table
hashMap = containers.Map(keys, values);

% Try this out:
queryKeys = keys(randi(numel(keys),5000,1));
queryKeysCell = num2cell(queryKeys); % trick to read many values from the hashMap at once

t = [timeit(@f1,1), timeit(@f2,1), timeit(@f3,1), timeit(@f4,1), ...
     timeit(@f1q,1), timeit(@f2q,1), timeit(@f3q,1), timeit(@f4q,1)] * 1000;

% Functions that do the lookup one at the time:

   function queryVals = f1
      queryVals = zeros(size(queryKeys));
      for ii=1:numel(queryKeys)
         queryVals(ii) = full(sparseMap(queryKeys(ii)));
      end
   end

   function queryVals = f2
      queryVals = zeros(size(queryKeys));
      for ii=1:numel(queryKeys)
         queryVals(ii) = hashMap(queryKeys(ii));
      end
   end

   function queryVals = f3
      queryVals = zeros(size(queryKeys));
      for ii=1:numel(queryKeys)
         queryVals(ii) = find(keys==queryKeys(ii));
      end
   end

   function queryVals = f4
      queryVals = zeros(size(queryKeys));
      for ii=1:numel(queryKeys)
         [~, queryVals(ii)] = ismember(queryKeys(ii), keys);
      end
   end

% Functions that do the lookup all at once:

   function queryVals = f1q
      queryVals = reshape(full(sparseMap(queryKeys)), size(queryKeys));
   end

   function queryVals = f2q
      queryVals = hashMap.values(queryKeysCell);
   end

   function queryVals = f3q
      [queryVals,~] = find(keys.'==queryKeys);
   end

   function queryVals = f4q
      [~,queryVals] = ismember(queryKeys, keys);
   end

end

[ Horaires sur MATLAB R2017a, sur un iMac de 3 ans. Votre kilométrage variera. ]

Contrairement à mes attentes, containers.Map a beaucoup de frais généraux et ne convient pas vraiment à cette fin fort>. Même avec un tableau de 5000 clés, la méthode O (n) find est en fait plus rapide que la carte de hachage O (log n). containers.Map est une classe personnalisée, et le MATLAB JIT n'est toujours pas aussi efficace pour optimiser ce type de code. Cependant, on peut clairement voir l'effet de la mise à l'échelle ici, car la méthode find est la seule dont le temps d'exécution augmente considérablement avec l'augmentation de la taille des données.

Fait intéressant, la méthode "sparse "est environ 50 fois plus rapide lorsqu'elle est vectorisée. Ce n'est généralement plus le cas avec la vectorisation. Par exemple, la méthode find n'est qu'environ 1x-2x plus rapide lorsqu'elle est vectorisée (pour des tailles de données plus importantes, la vectorisation nécessite trop de mémoire et finira par s'avérer très lente).

La plus grande différence ici entre le code vectorisé et le code en boucle concerne la fonction ismember . Celui-ci trie les données d'entrée, et nous voyons donc ici la différence entre le faire une fois et le faire 5000 fois. Cette méthode ne convient vraiment que lorsqu'elle est appelée quelques fois. Mais dans ce cas, ismember est aussi facilement la méthode la plus rapide .

Lors de la récupération d'une clé à la fois, la méthode fragmentée est la plus rapide , sauf si la taille des données est très petite, auquel cas la méthode find l'emporte . Cependant, la méthode creuse est la seule qui nécessite que les clés soient des entiers positifs (elle ne fonctionnera pas avec 0, des valeurs négatives ou des valeurs non entières). Les autres méthodes fonctionnent toutes avec des valeurs de types arbitraires (y compris des chaînes).


Code de référence:

                One key at the time                      Many keys at once
       -------------------------------------   -------------------------------------
size    sparse     c.Map      find  ismember    sparse     c.Map      find  ismember  
----   -------   -------   -------   -------   -------   -------   -------   -------
  50    5.1681   54.3091    3.7766   28.8590    0.0956    1.2973    0.5578    0.0537
 500    5.0864   54.7872    6.9310   32.5554    0.0977    1.6847    3.6726    0.0499
5000    5.2052   56.4472   35.1449   60.6480    0.1140    2.0886   38.7444    0.0789

6 commentaires

Merci! Ceci est un résumé précieux!


@ Dev-iL C'est plus qu'un résumé!


@ rahnema1 Ai-je été un peu exagéré? :)


C'est gentil et une réponse détaillée.


@CrisLuengo J'ai deux autres solutions en tête. Pourriez-vous s'il vous plaît tester l'efficacité de griddedInterpolant et scatteredInterpolant ?


@ rahnema1: Je ne m'attends pas à ce que ces fonctions soient efficaces, car elles sont conçues pour l'interpolation, sans trouver des éléments identiques. Une solution utilisant ceux-ci aurait besoin de tester l'entrée pour s'assurer qu'aucune interpolation ne se produirait, sinon ils feraient quelque chose de fondamentalement différent (retourner une valeur ne figurant pas dans la liste, par opposition à une erreur de sortie). Mais n'hésitez pas à mettre à jour cette réponse avec les méthodes que vous proposez. J'en ai fait un wiki communautaire pour cette raison!