diff --git a/js/src/recordbatch.ts b/js/src/recordbatch.ts index b9061c8b9bb04..33dbe9e59cb14 100644 --- a/js/src/recordbatch.ts +++ b/js/src/recordbatch.ts @@ -20,6 +20,7 @@ import { Table } from './table.js'; import { Vector } from './vector.js'; import { Schema, Field } from './schema.js'; import { DataType, Struct, Null, TypeMap } from './type.js'; +import { wrapIndex } from './util/vector.js'; import { instance as getVisitor } from './visitor/get.js'; import { instance as setVisitor } from './visitor/set.js'; @@ -116,7 +117,7 @@ export class RecordBatch { } /** - * Check whether an element is null. + * Check whether an row is null. * @param index The index at which to read the validity bitmap. */ public isValid(index: number) { @@ -125,15 +126,23 @@ export class RecordBatch { /** * Get a row by position. - * @param index The index of the element to read. + * @param index The index of the row to read. */ public get(index: number) { return getVisitor.visit(this.data, index); } + /** + * Get a row value by position. + * @param index The index of the row to read. A negative index will count back from the last row. + */ + public at(index: number) { + return this.get(wrapIndex(index, this.numRows)); + } + /** * Set a row by position. - * @param index The index of the element to write. + * @param index The index of the row to write. * @param value The value to set. */ public set(index: number, value: Struct['TValue']) { @@ -175,7 +184,7 @@ export class RecordBatch { /** * Return a zero-copy sub-section of this RecordBatch. * @param start The beginning of the specified portion of the RecordBatch. - * @param end The end of the specified portion of the RecordBatch. This is exclusive of the element at the index 'end'. + * @param end The end of the specified portion of the RecordBatch. This is exclusive of the row at the index 'end'. */ public slice(begin?: number, end?: number): RecordBatch { const [slice] = new Vector([this.data]).slice(begin, end).data; diff --git a/js/src/table.ts b/js/src/table.ts index d7a6617530a8e..2aab2b7ec2e76 100644 --- a/js/src/table.ts +++ b/js/src/table.ts @@ -40,7 +40,7 @@ import { instance as indexOfVisitor } from './visitor/indexof.js'; import { instance as iteratorVisitor } from './visitor/iterator.js'; import { DataProps } from './data.js'; -import { clampRange } from './util/vector.js'; +import { clampRange, wrapIndex } from './util/vector.js'; import { ArrayDataType, BigIntArray, TypedArray, TypedArrayDataType } from './interfaces.js'; import { RecordBatch, _InternalEmptyPlaceholderRecordBatch } from './recordbatch.js'; @@ -196,6 +196,15 @@ export class Table { // @ts-ignore public get(index: number): Struct['TValue'] | null { return null; } + /** + * Get an element value by position. + * @param index The index of the element to read. A negative index will count back from the last element. + */ + // @ts-ignore + public at(index: number): Struct['TValue'] | null { + return this.get(wrapIndex(index, this.numRows)); + } + /** * Set an element value by position. * diff --git a/js/src/util/vector.ts b/js/src/util/vector.ts index 179b17a39f3f3..9414aef68ca42 100644 --- a/js/src/util/vector.ts +++ b/js/src/util/vector.ts @@ -23,19 +23,8 @@ import { compareArrayLike } from '../util/buffer.js'; /** @ignore */ type RangeLike = { length: number; stride?: number }; /** @ignore */ -type ClampThen = (source: T, index: number) => any; -/** @ignore */ type ClampRangeThen = (source: T, offset: number, length: number) => any; -export function clampIndex(source: T, index: number): number; -export function clampIndex = ClampThen>(source: T, index: number, then: N): ReturnType; -/** @ignore */ -export function clampIndex = ClampThen>(source: T, index: number, then?: N) { - const length = source.length; - const adjust = index > -1 ? index : (length + (index % length)); - return then ? then(source, adjust) : adjust; -} - /** @ignore */ let tmp: number; export function clampRange(source: T, begin: number | undefined, end: number | undefined): [number, number]; @@ -60,6 +49,9 @@ export function clampRange = Cl return then ? then(source, lhs, rhs) : [lhs, rhs]; } +/** @ignore */ +export const wrapIndex = (index: number, len: number) => index < 0 ? (len + index) : index; + const isNaNFast = (value: any) => value !== value; /** @ignore */ diff --git a/js/src/vector.ts b/js/src/vector.ts index 1b0d9a05796f0..aeaa1c134241f 100644 --- a/js/src/vector.ts +++ b/js/src/vector.ts @@ -16,7 +16,7 @@ // under the License. import { Type } from './enum.js'; -import { clampRange } from './util/vector.js'; +import { clampRange, wrapIndex } from './util/vector.js'; import { DataType, strideForType } from './type.js'; import { Data, makeData, DataProps } from './data.js'; import { BigIntArray, TypedArray, TypedArrayDataType } from './interfaces.js'; @@ -177,6 +177,14 @@ export class Vector { // @ts-ignore public get(index: number): T['TValue'] | null { return null; } + /** + * Get an element value by position. + * @param index The index of the element to read. A negative index will count back from the last element. + */ + public at(index: number): T['TValue'] | null { + return this.get(wrapIndex(index, this.length)); + } + /** * Set an element value by position. * @param index The index of the element to write. diff --git a/js/test/unit/vector/bool-vector-tests.ts b/js/test/unit/vector/bool-vector-tests.ts index afbaef01a1a45..7b6ed3e589b2d 100644 --- a/js/test/unit/vector/bool-vector-tests.ts +++ b/js/test/unit/vector/bool-vector-tests.ts @@ -25,9 +25,10 @@ describe(`BoolVector`, () => { const n = values.length; const vector = newBoolVector(n, new Uint8Array([27, 0, 0, 0, 0, 0, 0, 0])); test(`gets expected values`, () => { - let i = -1; - while (++i < n) { + for (let i = 0; i < values.length; i++) { expect(vector.get(i)).toEqual(values[i]); + expect(vector.at(i)).toEqual(values.at(i)); + expect(vector.at(-i)).toEqual(values.at(-i)); } }); test(`iterates expected values`, () => { @@ -53,7 +54,7 @@ describe(`BoolVector`, () => { const expected2 = [true, true, true, true, true, false, false, false]; const expected3 = [true, true, false, false, false, false, true, true]; function validate(expected: boolean[]) { - for (let i = -1; ++i < n;) { + for (let i = 0; i < n; i++) { expect(v.get(i)).toEqual(expected[i]); } } diff --git a/js/test/unit/vector/numeric-vector-tests.ts b/js/test/unit/vector/numeric-vector-tests.ts index 032af796de138..e5c1789068b23 100644 --- a/js/test/unit/vector/numeric-vector-tests.ts +++ b/js/test/unit/vector/numeric-vector-tests.ts @@ -337,10 +337,12 @@ function testAndValidateVector(vector: Vector, typed: function gets_expected_values(vector: Vector, typed: T['TArray'], values: any[] = [...typed]) { test(`gets expected values`, () => { expect.hasAssertions(); - let i = -1, n = vector.length; + let i = -1; try { - while (++i < n) { + while (++i < vector.length) { expect(vector.get(i)).toEqual(values[i]); + expect(vector.at(i)).toEqual(values.at(i)); + expect(vector.at(-i)).toEqual(values.at(-i)); } } catch (e) { throw new Error(`${i}: ${e}`); } }); diff --git a/js/test/unit/vector/vector-tests.ts b/js/test/unit/vector/vector-tests.ts index a10d7c757ca17..881bf987b5f1f 100644 --- a/js/test/unit/vector/vector-tests.ts +++ b/js/test/unit/vector/vector-tests.ts @@ -265,8 +265,10 @@ describe(`ListVector`, () => { }); test(`get value`, () => { - for (const [i, value] of values.entries()) { - expect(vector.get(i)!.toJSON()).toEqual(value); + for (let i = 0; i < values.length; i++) { + expect(vector.get(i)!.toJSON()).toEqual(values[i]); + expect(vector.at(i)!.toJSON()).toEqual(values.at(i)); + expect(vector.at(-i)!.toJSON()).toEqual(values.at(-i)); } }); }); @@ -308,9 +310,10 @@ function basicVectorTests(vector: Vector, values: any[], extras: any[]) { const n = values.length; test(`gets expected values`, () => { - let i = -1; - while (++i < n) { + for (let i = 0; i < values.length; i++) { expect(vector.get(i)).toEqual(values[i]); + expect(vector.at(i)).toEqual(values.at(i)); + expect(vector.at(-i)).toEqual(values.at(-i)); } }); test(`iterates expected values`, () => {