从整数到整数的高效稀疏映射

时间:2017-05-02 18:33:33

标签: javascript

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

在许多情况下,表格将非常稀疏,但在其他情况下,它几乎已满。在大多数情况下,大多数条目将分为几个具有相同值的连续范围。

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

是否有更好的方法,可能利用内置功能?

2 个答案:

答案 0 :(得分:1)

听起来你有两种非常不同的情况(“在很多情况下,表格会非常稀疏,但在其他情况下它会快满了”。)

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

查找值看起来像这样:

  1. 二进制搜索索引。索引将对存储为类型化数组中的连续条目 - 第一个元素是搜索值,第二个元素是数据集中的位置(或下一个索引)。
  2. 如果您有多个索引,请根据需要重复1。
  3. 开始在最后一个索引给定的位置迭代数据集。由于索引是稀疏的,因此该位置可能不是存储该值的位置,但它是一个很好的起点,因为保证正确的值在附近。
  4. 数据集本身表示为一个类型化数组,其中连续的对包含键和值。
  5. 我无法想到在JavaScript中使用更好的东西。类型化数组非常快,索引应该会大大提高速度。话虽这么说,如果你只有几千个条目,不要打扰索引,直接在类型数组上进行二进制搜索(在上面的4.中描述)。

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

答案 1 :(得分:1)

不幸的是,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)。只要我们的初始假设 - 导致相同状态的连续输入范围的数量很小 - 这种方法对于稀疏和密集转换表同样有效。