【问题标题】:Efficient one-to-one mapping between value and index in an array数组中值和索引之间的高效一对一映射
【发布时间】:2019-04-17 10:36:30
【问题描述】:

我需要获取数组中值的索引(即位置),我想知道是否有比使用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 命令吗?

【问题讨论】:

  • 你已经有了一个映射函数,根据你的例子th我会写成angle2i = @(angle) (angle/5)+1;。我不知道这是否比find 快(你必须分析你的代码来估计)。如果可以将其矢量化,则可能会节省大量时间,这意味着如果您已经拥有要在数组中查找的所有角度值,则使用映射函数在一次操作中计算所有相关索引的数组,然后在您的循环中,只需从此数组中选择索引(无需重复调用您的映射函数或find。
  • 嘿@Hoki,感谢您的回复。对于这种特殊情况,您是对的,但我希望有一个通用程序,当值和索引之间没有明显的关系时。
  • 如果值和索引之间没有可计算的关系,那么最好的选择是使用map,如Chris Luengo's answer 所示。

标签: arrays matlab indexing mapping


【解决方案1】:

您正在寻找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 的情况,那么@mattesyo 对computing the index 的O(1) 解是不可战胜的。

【讨论】:

  • @FinnurPind:检查the benchmark!这种方法看起来不错,听起来很棒,但实际上在速度方面很糟糕。在某些条件下,您的 find 明显更快。您可能需要重新考虑您接受哪些答案的选择。 :)
【解决方案2】:

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

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 个值,粗略地说)。

【讨论】:

  • 这很聪明!我将对我的情况进行比较,看看结果会更快。感谢您的意见!
  • @FinnurPind 不客气!我希望这被证明是有用的。
【解决方案3】:

如果索引和值之间存在数字上下文,就像您的示例中那样,您可以将其用作函数而不是查找表:

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

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

【讨论】:

  • 感谢您的回复。好吧,你是对的,对于这种特殊情况,我可以通过一些数值关系来解决这个问题。但是我也有一些情况,索引和值之间没有明显的关系,所以我需要某种将两者联系起来的表......仍然不确定我将如何实现它。
  • 在这种情况下,我认为在不知道特定数组大小和值结构的情况下对您的问题进行运行时优化时没有通用解决方案。 “查找”功能正是您想要达到的功能,这实际上并不意味着它是每个应用程序最有效的方法。由于不知道具体问题,我看到的唯一机会是,你必须尝试你的“非常不优雅的想法”并使用 MATLAB Profiler 可能找到最快的一个(如果有的话)。
  • mattesyo,如果您想警告开销,请随时对我的回答发表评论。但是不要删掉一半,改成说我不会写的东西。编辑建议“与作者的意图相冲突”有一个拒绝原因。修正错别字和修正格式是可以的。更改答案的内容是不行的。 Here您可以看到您的编辑建议以及对它们做出的决定。
  • 实际上,this comment by the OP 表明他们确实在寻找 @CrisLuengo 写的东西,一个通用的解决方案。 IMO 问题中过度简化示例的经典案例
【解决方案4】:

如果你手头有angle 的所有(数百万)值,你可以使用ismember:

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

【讨论】:

    【解决方案5】:

    基准测试

    从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
    

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

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

    有趣的是,“稀疏”方法在矢量化时快了约 50 倍。矢量化通常不再是这种情况。例如,find 方法在矢量化时仅快 1x-2x 左右(对于更大的数据量,矢量化需要太多内存,最终会变得非常慢)。

    向量化代码和循环代码之间的最大区别在于ismember 函数。这个对输入数据进行排序,所以在这里我们看到了执行一次和执行 5000 次之间的区别。这种方法真的只适合调用几次。但在这种情况下,ismember 也很容易成为最快的方法。

    同时获取一个key时,sparse方法最快,除非数据量非常小,在这种情况下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
    

    【讨论】:

    • 谢谢!这是一个有价值的总结!
    • @Dev-iL 这不仅仅是一个总结!
    • @rahnema1 我是不是有点过火了? :)
    • 答案很好,很详细。
    • @CrisLuengo 我想到了另外两个解决方案。你能测试一下griddedInterpolant和scatteredInterpolant的效率吗?
    猜你喜欢
    • 2021-09-15
    • 1970-01-01
    • 1970-01-01
    • 2021-12-21
    • 2019-09-21
    • 2011-05-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多