// Licensed to the Apache Software Foundation (ASF) under one // or more contributor license agreements. See the NOTICE file // distributed with this work for additional information // regarding copyright ownership. The ASF licenses this file // to you under the Apache License, Version 2.0 (the // "License"); you may not use this file except in compliance // with the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. import { Data } from '../data.js'; import { DataType } from '../type.js'; /** @ignore */ export class ChunkedIterator implements IterableIterator { private chunkIndex = 0; private chunkIterator: IterableIterator; constructor( private numChunks: number = 0, private getChunkIterator: (chunkIndex: number) => IterableIterator ) { this.chunkIterator = this.getChunkIterator(0); } next(): IteratorResult { while (this.chunkIndex < this.numChunks) { const next = this.chunkIterator.next(); if (!next.done) { return next; } if (++this.chunkIndex < this.numChunks) { this.chunkIterator = this.getChunkIterator(this.chunkIndex); } } return { done: true, value: null }; } [Symbol.iterator]() { return this; } } /** @ignore */ export function computeChunkNullCounts(chunks: ReadonlyArray>) { return chunks.reduce((nullCount, chunk) => nullCount + chunk.nullCount, 0); } /** @ignore */ export function computeChunkOffsets(chunks: ReadonlyArray>) { return chunks.reduce((offsets, chunk, index) => { offsets[index + 1] = offsets[index] + chunk.length; return offsets; }, new Uint32Array(chunks.length + 1)); } /** @ignore */ export function sliceChunks(chunks: ReadonlyArray>, offsets: Uint32Array | Array, begin: number, end: number) { const slices: Data[] = []; for (let i = -1, n = chunks.length; ++i < n;) { const chunk = chunks[i]; const offset = offsets[i]; const { length } = chunk; // Stop if the child is to the right of the slice boundary if (offset >= end) { break; } // Exclude children to the left of of the slice boundary if (begin >= offset + length) { continue; } // Include entire child if between both left and right boundaries if (offset >= begin && (offset + length) <= end) { slices.push(chunk); continue; } // Include the child slice that overlaps one of the slice boundaries const from = Math.max(0, begin - offset); const to = Math.min(end - offset, length); slices.push(chunk.slice(from, to - from)); } if (slices.length === 0) { slices.push(chunks[0].slice(0, 0)); } return slices; } /** @ignore */ export function binarySearch< T extends DataType, F extends (chunks: ReadonlyArray>, _1: number, _2: number) => any >(chunks: ReadonlyArray>, offsets: Uint32Array | number[], idx: number, fn: F) { let lhs = 0, mid = 0, rhs = offsets.length - 1; do { if (lhs >= rhs - 1) { return (idx < offsets[rhs]) ? fn(chunks, lhs, idx - offsets[lhs]) : null; } mid = lhs + (Math.trunc((rhs - lhs) * .5)); idx < offsets[mid] ? (rhs = mid) : (lhs = mid); } while (lhs < rhs); } /** @ignore */ export function isChunkedValid(data: Data, index: number): boolean { return data.getValid(index); } /** @ignore */ export function wrapChunkedCall1(fn: (c: Data, _1: number) => any) { function chunkedFn(chunks: ReadonlyArray>, i: number, j: number) { return fn(chunks[i], j); } return function (this: any, index: number) { const data = this.data as ReadonlyArray>; return binarySearch(data, this._offsets, index, chunkedFn); }; } /** @ignore */ export function wrapChunkedCall2(fn: (c: Data, _1: number, _2: any) => any) { let _2: any; function chunkedFn(chunks: ReadonlyArray>, i: number, j: number) { return fn(chunks[i], j, _2); } return function (this: any, index: number, value: any) { const data = this.data as ReadonlyArray>; _2 = value; const result = binarySearch(data, this._offsets, index, chunkedFn); _2 = undefined; return result; }; } /** @ignore */ export function wrapChunkedIndexOf(indexOf: (c: Data, e: T['TValue'], o?: number) => any) { let _1: any; function chunkedIndexOf(data: ReadonlyArray>, chunkIndex: number, fromIndex: number) { let begin = fromIndex, index = 0, total = 0; for (let i = chunkIndex - 1, n = data.length; ++i < n;) { const chunk = data[i]; if (~(index = indexOf(chunk, _1, begin))) { return total + index; } begin = 0; total += chunk.length; } return -1; } return function (this: any, element: T['TValue'], offset?: number) { _1 = element; const data = this.data as ReadonlyArray>; const result = typeof offset !== 'number' ? chunkedIndexOf(data, 0, 0) : binarySearch(data, this._offsets, offset, chunkedIndexOf); _1 = undefined; return result; }; }