数组中值和索引之间的有效一对一映射

时间:2019-04-17 10:36:31

标签: arrays matlab indexing mapping

我需要获取数组中某个值的索引(即位置),并且我想知道是否存在通过构造某种映射或查找方法比使用find命令更快的方法该表包含数组值和索引之间的映射。

例如,使用以下数组:

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

现在,假设我有一个值为

的变量
angle = 55

,我想知道此值在数组中的位置(正确答案是idx = 12)。现在,我当然可以只使用find

idx = find(th==angle)

但是我的问题是,在我的代码中,我需要进行此查找,以获取thangle中的值的索引数(百万)次,并且看起来不断调用find函数有点浪费资源,我猜这是遍历th并进行某种比较。

相反,我希望可以采用某种方法来建立一对一的映射或查找表,在这里我可以立即获取与我在angle中的值对应的索引。 (请注意:我知道angle中的值将始终与th中的值之一完全对应。)因此,只需具有一些功能

idx = angle2i(angle)

执行此映射:

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

但是我没有看到应该如何实现这样的查找(嗯,我有几个非常不优雅的想法,我希望并猜测为此必须有一些聪明的方法)。还是我在这里浪费时间,我应该只使用find命令吗?

5 个答案:

答案 0 :(得分:5)

您正在寻找containers.Map

您可以这样做:

th = [0 5 10 15 20 25 30 35 40 45 50 55 60 65 70 75 80 85 90];
angle2i = containers.Map(th,1:numel(th));
index = angle2i(55)

这是一个通用解决方案,仅要求th包含唯一元素。它们不需要排序,也不需要是整数(尽管在比较浮点值时必须小心!)。对于大型阵列,该解决方案应该比find快得多,因为该解决方案为O(log n),而find解决方案为O(n)。但是对于非常小的阵列,将显示使用containers.Map的开销。

如果保证对th进行排序,那么solutions to this other question也可能有用。

当然,如果存在简单的数学关系(如示例th的情况,那么{@ 3}}的@mattesyo的O(1)解决方案就无法被击败。

答案 1 :(得分:4)

如果键是整数,也许可以使用稀疏数组!请考虑以下情况:

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

我不知道我所做的比较是否合理,但如果是这样,则稀疏方法在我的系统上的速度要快一个数量级(0.15491.5685)。

顺便说一句,如果不清楚,使用sparse数组的原因是因为它仅占用非零值的空间(因此,即使您有1和10E5之类的索引,只会存储2个值,大致来说)。

答案 2 :(得分:3)

如果在索引和值之间存在数值上下文(如您的示例中所示),则可以将其用作函数而不是查找表:

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

但是我不知道这是否可以解决您的问题,因为我不知道您的具体问题。

答案 3 :(得分:3)

如果手头有angle的所有(几百万个)值,则可以使用ismember

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

答案 4 :(得分:3)

基准

Dev-iL's benchmark开始,我添加了OP的find方法和rahnema1's ismember method,并研究了这些方法如何随数据大小(键数)扩展。我还比较了两种不同的用例:一次找到5000个键,一次一次找到5000个键。

这些是结果:

                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

[在3岁的iMac上的MATLAB R2017a上的时间。您的里程会有所不同。]

与我的期望相反, containers.Map有很多开销,因此并不真正适合此目的。即使有5000个键的数组,O(n)find方法实际上也比O(log n)哈希映射要快。 containers.Map是一个自定义类,并且在优化该类型的代码方面,MATLAB JIT仍然不如以前。但是,在那里可以清楚地看到缩放的效果,因为find方法是唯一一种运行时间随着数据大小的增加而显着增加的方法。

有趣的是,向量化后,“稀疏”方法的速度提高了约50倍。向量化通常不再是这种情况。例如,find方法在向量化时仅快大约1到2倍(对于更大的数据大小,向量化需要太多内存,最终会证明非常慢)。

矢量化代码和循环代码之间的最大区别在于ismember函数。这是对输入数据进行排序的方法,因此在这里我们看到一次执行和执行5000次之间的区别。此方法仅在几次调用时才真正适用。但是在这种情况下, ismember也是最快捷的方法

一次获取一个密钥时,稀疏方法最快,除非数据量很小,在这种情况下, find方法会获胜 。但是,稀疏方法是唯一要求键为正整数的方法(不适用于0,负值或非整数值)。其他方法都适用于任意类型的值(包括字符串)。


基准代码:

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