我需要获取数组中某个值的索引(即位置),并且我想知道是否存在通过构造某种映射或查找方法比使用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)
但是我的问题是,在我的代码中,我需要进行此查找,以获取th
中angle
中的值的索引数(百万)次,并且看起来不断调用find
函数有点浪费资源,我猜这是遍历th
并进行某种比较。
相反,我希望可以采用某种方法来建立一对一的映射或查找表,在这里我可以立即获取与我在angle
中的值对应的索引。 (请注意:我知道angle
中的值将始终与th
中的值之一完全对应。)因此,只需具有一些功能
idx = angle2i(angle)
执行此映射:
0 -> 1
5 -> 2
10 -> 3
15 -> 4
20 -> 5
25 -> 6
等
但是我没有看到应该如何实现这样的查找(嗯,我有几个非常不优雅的想法,我希望并猜测为此必须有一些聪明的方法)。还是我在这里浪费时间,我应该只使用find
命令吗?
答案 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.1549
与1.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