【问题标题】:Efficient sparse mapping from integers to integers从整数到整数的高效稀疏映射
【发布时间】:2017-09-30 09:23:48
【问题描述】:

我正在使用有限自动机实现一个专门构建的正则表达式引擎。我将不得不存储数千个状态,每个状态都有自己的从 unicode 代码点(或 UTF-16 代码单元;我还没有决定)到状态 ID 的转换表。

在许多情况下,该表会非常稀疏,但在其他情况下,它几乎是满的。在大多数情况下,大多数条目将落入具有相同值的几个连续范围内。

最简单的实现是查找表,但每个这样的表都会占用大量空间。 (范围,值)对的列表会小得多,但速度较慢。二叉搜索树会比列表更快。

有没有更好的方法,也许是利用内置功能?

【问题讨论】:

  • 因为(现代)javascript 数组本身是 sparse 这不应该是内存问题 stackoverflow.com/questions/4524067/… ;许多数组元素也可以引用同一个对象,这将在遇到范围时为您节省一些空间。
  • @StephenP 问题是关于映射到整数。您必须包装整数才能引用它们(因为它们是原始类型),因此实际上引用会更慢。
  • 点@Aurel

标签: javascript


【解决方案1】:

听起来您有两种截然不同的情况(“在许多情况下,表格会非常稀疏,但在其他情况下,它几乎会满”)。

对于稀疏情况,您可能有一个单独的稀疏索引(或几层索引),然后您的实际数据可以存储在类型化数组中。因为索引会从整数映射到整数,所以它们也可以表示为类型化数组。

查找值如下所示:

  1. 对索引进行二分搜索。索引将对存储为类型化数组中的连续条目——第一个元素是搜索值,第二个元素是数据集中的位置(或下一个索引)。
  2. 如果您有多个索引,请根据需要重复 1。
  3. 在最后一个索引给定的位置开始迭代数据集。因为索引是稀疏的,所以这个位置可能不是存储值的位置,但它是一个很好的起点,因为可以保证正确的值就在附近。
  4. 数据集本身表示为一个类型化数组,其中连续的对保存键和值。

我想不出在 JavaScript 中更好的使用方法了。类型化数组非常快,拥有索引应该会大大提高速度。话虽这么说,如果你只有几千个条目,不要打扰索引,直接在类型化数组上进行二进制搜索(在上面的 4. 中描述)。

对于密集的情况,我不确定。如果密集情况恰好是跨键范围重复值的情况,请考虑使用类似run-length encoding 的东西——相同的连续值简单地表示为它们的出现次数,然后是实际值。再一次,使用类型化数组和二分搜索,甚至可能使用索引来加快速度。

【讨论】:

    【解决方案2】:

    不幸的是,JavaScript 的内置数据类型 - 尤其是 Map - 对完成这项任务没有太大帮助,因为它们缺乏相关的方法。

    在大多数情况下,大多数条目将分为几个连续的 具有相同值的范围。

    然而,我们可以利用这一点并在排序数组上使用二进制搜索策略,假设转换表不会经常修改。

    通过将每个输入范围的最小值存储在排序数组中,对导致相同状态的连续输入范围进行编码。将相应索引处的状态保存在单独的数组中:

    let inputs = [0, 5, 10]; // Input ranges [0,4], [5,9], [10,∞)
    let states = [0, 1, 0 ]; // Inputs [0,4] lead to state 0, [5,9] to 1, [10,∞) to 0
    

    现在,给定一个输入,您需要对输入数组执行二进制搜索,类似于Java's floorEntry(k)

    // Returns the index of the greatest element less than or equal to
    // the given element, or undefined if there is no such element:
    function floorIndex(sorted, element) {
      let low = 0;
      let high = sorted.length - 1;
      while (low <= high) {
        let mid = low + high >> 1;
        if (sorted[mid] > element) {
          high = mid - 1;
        } else if (sorted[mid] < element) {
          low = mid + 1;
        } else {
          return mid
        }
      }
      return low - 1;
    }
    
    // Example: Transition to 1 for emoticons in range 1F600 - 1F64F:
    let transitions = {
      inputs: [0x00000, 0x1F600, 0x1F650],
      states: [0,       1,       0      ]
    };
    let input = 0x1F60B; // ?
    let next = transitions.states[floorIndex(transitions.inputs, input)];
    
    console.log(`transition to ${next}`);

    此搜索以 O(log n) 步完成,其中 n 是连续输入范围的数量。单个状态的转换表则需要 O(n) 的空间。只要我们最初的假设(导致相同状态的连续输入范围的数量很小)成立,这种方法同样适用于稀疏和密集转换表。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-04-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-10-17
      • 1970-01-01
      相关资源
      最近更新 更多