Spaces:
Sleeping
Sleeping
| // @flow | |
| import murmur3 from 'murmurhash-js'; | |
| import {register} from '../util/web_worker_transfer'; | |
| import assert from 'assert'; | |
| type SerializedFeaturePositionMap = { | |
| ids: Float64Array; | |
| positions: Uint32Array; | |
| }; | |
| type FeaturePosition = { | |
| index: number; | |
| start: number; | |
| end: number; | |
| }; | |
| // A transferable data structure that maps feature ids to their indices and buffer offsets | |
| export default class FeaturePositionMap { | |
| ids: Array<number>; | |
| positions: Array<number>; | |
| indexed: boolean; | |
| constructor() { | |
| this.ids = []; | |
| this.positions = []; | |
| this.indexed = false; | |
| } | |
| add(id: mixed, index: number, start: number, end: number) { | |
| this.ids.push(getNumericId(id)); | |
| this.positions.push(index, start, end); | |
| } | |
| getPositions(id: mixed): Array<FeaturePosition> { | |
| assert(this.indexed); | |
| const intId = getNumericId(id); | |
| // binary search for the first occurrence of id in this.ids; | |
| // relies on ids/positions being sorted by id, which happens in serialization | |
| let i = 0; | |
| let j = this.ids.length - 1; | |
| while (i < j) { | |
| const m = (i + j) >> 1; | |
| if (this.ids[m] >= intId) { | |
| j = m; | |
| } else { | |
| i = m + 1; | |
| } | |
| } | |
| const positions = []; | |
| while (this.ids[i] === intId) { | |
| const index = this.positions[3 * i]; | |
| const start = this.positions[3 * i + 1]; | |
| const end = this.positions[3 * i + 2]; | |
| positions.push({index, start, end}); | |
| i++; | |
| } | |
| return positions; | |
| } | |
| static serialize(map: FeaturePositionMap, transferables: Array<ArrayBuffer>): SerializedFeaturePositionMap { | |
| const ids = new Float64Array(map.ids); | |
| const positions = new Uint32Array(map.positions); | |
| sort(ids, positions, 0, ids.length - 1); | |
| if (transferables) { | |
| transferables.push(ids.buffer, positions.buffer); | |
| } | |
| return {ids, positions}; | |
| } | |
| static deserialize(obj: SerializedFeaturePositionMap): FeaturePositionMap { | |
| const map = new FeaturePositionMap(); | |
| // after transferring, we only use these arrays statically (no pushes), | |
| // so TypedArray vs Array distinction that flow points out doesn't matter | |
| map.ids = (obj.ids: any); | |
| map.positions = (obj.positions: any); | |
| map.indexed = true; | |
| return map; | |
| } | |
| } | |
| const MAX_SAFE_INTEGER = Math.pow(2, 53) - 1; | |
| function getNumericId(value: mixed) { | |
| const numValue = +value; | |
| if (!isNaN(numValue) && numValue <= MAX_SAFE_INTEGER) { | |
| return numValue; | |
| } | |
| return murmur3(String(value)); | |
| } | |
| // custom quicksort that sorts ids, indices and offsets together (by ids) | |
| // uses Hoare partitioning & manual tail call optimization to avoid worst case scenarios | |
| function sort(ids, positions, left, right) { | |
| while (left < right) { | |
| const pivot = ids[(left + right) >> 1]; | |
| let i = left - 1; | |
| let j = right + 1; | |
| while (true) { | |
| do i++; while (ids[i] < pivot); | |
| do j--; while (ids[j] > pivot); | |
| if (i >= j) break; | |
| swap(ids, i, j); | |
| swap(positions, 3 * i, 3 * j); | |
| swap(positions, 3 * i + 1, 3 * j + 1); | |
| swap(positions, 3 * i + 2, 3 * j + 2); | |
| } | |
| if (j - left < right - j) { | |
| sort(ids, positions, left, j); | |
| left = j + 1; | |
| } else { | |
| sort(ids, positions, j + 1, right); | |
| right = j; | |
| } | |
| } | |
| } | |
| function swap(arr, i, j) { | |
| const tmp = arr[i]; | |
| arr[i] = arr[j]; | |
| arr[j] = tmp; | |
| } | |
| register('FeaturePositionMap', FeaturePositionMap); | |