diff --git a/range2/range2.ts b/range2/range2.ts index 74a4345c6c9..0ec565e9e16 100644 --- a/range2/range2.ts +++ b/range2/range2.ts @@ -1,15 +1,16 @@ namespace $ { + const options_key = Symbol('$mol_range2_options') /** Lazy computed lists with native Array interface. $mol_range2_array is mutable but all derived ranges are immutable. */ export function $mol_range2< Item = number >( item : ( index : number )=> Item = index => index as any , size = ()=> Number.POSITIVE_INFINITY , + sample = new $mol_range2_array ) : Item[] { - return new Proxy( new $mol_range2_array< Item >() , { + return new Proxy( sample , { get( target , field ) { - if( typeof field === 'string' ) { if( field === 'length' ) return size() @@ -21,7 +22,7 @@ namespace $ { } , set( target , field ) { - return $mol_fail( new TypeError( 'Lazy range is read only' ) ) + return $mol_fail( new TypeError( `Lazy range is read only, ${String(field)}` ) ) } , ownKeys( target ) { @@ -128,6 +129,10 @@ namespace $ { ) } + at(index: number): Item | undefined { + return this[index >= 0 ? index : this.length + index] + } + some< Context > ( check : ( this : Context , val : Item , index : number , list : Item[] )=> boolean , context? : Context , diff --git a/range3/range3.test.ts b/range3/range3.test.ts new file mode 100644 index 00000000000..2dfa4fc50a5 --- /dev/null +++ b/range3/range3.test.ts @@ -0,0 +1,38 @@ +namespace $ { + function stub_ids(max = 10): string[] { + const ids: string[] = [] + for (let i = 0; i < max; i++) { + ids.push($mol_stub_code()) + } + return ids + } + + function make_range() { + const range = new $mol_range3() + const chunk_size = 10 + const chunks = [ + stub_ids(chunk_size), + stub_ids(chunk_size), + stub_ids(chunk_size / 2), + ] + const count = chunks.reduce((acc, row) => acc + row.length, 0) + range.count = () => count + range.chunk_size = () => chunk_size + range.chunk = offset => chunks[offset] + + return { range, chunks } + } + + $mol_test({ + + 'chunk addressing'() { + const { range, chunks } = make_range() + const arr = range.range() + + $mol_assert_equal(arr[0], chunks[0][0]) + $mol_assert_equal(arr.at(-1), chunks.at(-1)?.at(-1)) + } + + }) +} + diff --git a/range3/range3.ts b/range3/range3.ts new file mode 100644 index 00000000000..0cd89a3a94b --- /dev/null +++ b/range3/range3.ts @@ -0,0 +1,80 @@ +namespace $ { + export class $mol_range3 extends $mol_range2_array { + count(): number { + throw new Error('implement') + } + + chunk(offset: number): readonly Item[] { + throw new Error('implement') + } + + chunk_size() { + return 100 + } + + page(page: number) { + return this.chunk(page * this.chunk_size()) + } + + item(index: number) { + const limit = this.chunk_size() + + const chunk_index = index % limit + const page = Math.floor(index / limit) + + const chunk = this.page(page) + const id = chunk[chunk_index] + + return id + } + + @ $mol_mem + temp(next?: readonly Item[]) { + return next ?? [] + } + + push(...ids: readonly Item[]) { + this.temp([ ...this.temp(), ...ids ]) + this.temp_cut() + + return this.length + } + + temp_chunk_min() { + return 1 + } + + temp_cut() { + const temp = this.temp() + if (! temp) return + + const limit = this.chunk_size() + const delete_chunks = Math.ceil(temp.length / limit) - this.temp_chunk_min() + if (delete_chunks <= 0) return + + const delete_count = delete_chunks * limit + this.removed_count = this.removed_count + delete_count + this.temp(temp.slice(delete_count)) + } + + at(index: number) { + const count = this.count() + this.removed_count + if (index < 0) index = Math.max(0, count + this.temp().length + index) + + if (index < count) return this.item(index) + return this.temp()[index - count] + } + + protected removed_count = 0 + + get length() { + return this.count() + this.removed_count + this.temp().length + } + + @ $mol_mem + range() { + return $mol_range2(index => this.at(index), () => this.length, this) + } + + } +}