Setup eslint and enforce rules
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
2021-06-02 22:36:25 -05:00
parent 82e7a1f299
commit 1d5056b753
149 changed files with 6104 additions and 3114 deletions

View File

@@ -1,5 +1,5 @@
import { Iterable } from './Iterable'
import { collect } from './Collection'
import {collect, Collection} from './Collection'
/**
* A basic Iterable implementation that uses an array as a backend.
@@ -15,19 +15,19 @@ export class ArrayIterable<T> extends Iterable<T> {
super()
}
async at(i: number) {
async at(i: number): Promise<T | undefined> {
return this.items[i]
}
async range(start: number, end: number) {
async range(start: number, end: number): Promise<Collection<T>> {
return collect(this.items.slice(start, end + 1))
}
async count() {
async count(): Promise<number> {
return this.items.length
}
clone() {
clone(): ArrayIterable<T> {
return new ArrayIterable([...this.items])
}
}

View File

@@ -3,7 +3,7 @@ import {
Collection, CollectionIndex,
CollectionItem, ComparisonFunction,
DeterminesEquality, KeyFunction,
KeyOperator, KeyReducerFunction, MaybeCollectionIndex, MaybeCollectionItem
KeyOperator, KeyReducerFunction, MaybeCollectionIndex, MaybeCollectionItem,
} from './Collection'
import {Iterable, StopIteration} from './Iterable'
import {applyWhere, WhereOperator} from './where'
@@ -20,53 +20,56 @@ export class AsyncCollection<T> {
* Iterable of items to base this collction on.
* @type Iterable
*/
private _items: Iterable<T>,
private storedItems: Iterable<T>,
/**
* Size to use when chunking results for memory-optimization.
* @type number
*/
private _chunk_size: number = 1000, // TODO fix this. It's just for testing
private iteratorChunkSize: number = 1000, // TODO fix this. It's just for testing
) {}
private async _chunk(callback: (items: Collection<T>) => any): Promise<void> {
await this._items.chunk(this._chunk_size, async items => {
private async inChunks(callback: (items: Collection<T>) => any): Promise<void> {
await this.storedItems.chunk(this.iteratorChunkSize, async items => {
await callback(items)
})
await this._items.reset()
await this.storedItems.reset()
}
private async _chunk_all<T2>(key: KeyOperator<T, T2>, callback: (items: Collection<T2>) => any): Promise<void> {
await this._items.chunk(this._chunk_size, async items => {
private async inChunksAll<T2>(key: KeyOperator<T, T2>, callback: (items: Collection<T2>) => any): Promise<void> {
await this.storedItems.chunk(this.iteratorChunkSize, async items => {
await callback(items.pluck(key))
})
await this._items.reset()
await this.storedItems.reset()
}
private async _chunk_all_numbers<T2>(key: KeyOperator<T, T2>, callback: (items: number[]) => any): Promise<void> {
await this._items.chunk(this._chunk_size, async items => {
await callback(items.pluck(key).map(x => Number(x)).all())
private async inChunksAllNumbers<T2>(key: KeyOperator<T, T2>, callback: (items: number[]) => any): Promise<void> {
await this.storedItems.chunk(this.iteratorChunkSize, async items => {
await callback(items.pluck(key).map(x => Number(x))
.all())
})
await this._items.reset()
await this.storedItems.reset()
}
private async _chunk_all_associate<T2>(key: KeyOperator<T, T2>, callback: (items: AssociatedCollectionItem<T2, T>[]) => any): Promise<void> {
await this._items.chunk(this._chunk_size, async items => {
const assoc_items: AssociatedCollectionItem<T2, T>[] = []
private async inChunksAllAssociated<T2>(key: KeyOperator<T, T2>, callback: (items: AssociatedCollectionItem<T2, T>[]) => any): Promise<void> {
await this.storedItems.chunk(this.iteratorChunkSize, async items => {
const assocItems: AssociatedCollectionItem<T2, T>[] = []
if ( typeof key === 'function' ) {
items.map((item, index) => {
const key_item = key(item, index)
assoc_items.push({ key: key_item, item })
const keyItem = key(item, index)
assocItems.push({ key: keyItem,
item })
})
} else {
items.map((item, index) => {
assoc_items.push({key: (<any>item)[key], item})
items.map(item => {
assocItems.push({key: (<any>item)[key],
item})
})
}
await callback(assoc_items)
await callback(assocItems)
})
await this._items.reset()
await this.storedItems.reset()
}
/**
@@ -74,7 +77,7 @@ export class AsyncCollection<T> {
* @return Promise<array>
*/
async all(): Promise<CollectionItem<T>[]> {
return (await this._items.all()).toArray()
return (await this.storedItems.all()).toArray()
}
/**
@@ -82,7 +85,7 @@ export class AsyncCollection<T> {
* @return Promise<Collection>
*/
async collect(): Promise<Collection<T>> {
return this._items.all()
return this.storedItems.all()
}
/**
@@ -91,20 +94,23 @@ export class AsyncCollection<T> {
* @return Promise<number>
*/
async average<T2>(key?: KeyOperator<T, T2>): Promise<number> {
let running_total = 0
let running_items = 0
let runningTotal = 0
let runningItems = 0
const chunk_helper = (items: number[]) => {
running_items += items.length
running_total += items.reduce((prev, curr) => prev + curr)
const chunkHelper = (items: number[]) => {
runningItems += items.length
runningTotal += items.reduce((prev, curr) => prev + curr)
}
if ( key ) await this._chunk_all_numbers(key, chunk_helper)
else await this._chunk((items) => {
chunk_helper(items.map(x => Number(x)).all())
})
if ( key ) {
await this.inChunksAllNumbers(key, chunkHelper)
} else {
await this.inChunks((items) => {
chunkHelper(items.map(x => Number(x)).all())
})
}
return running_total / running_items
return runningTotal / runningItems
}
/**
@@ -115,19 +121,25 @@ export class AsyncCollection<T> {
async median<T2>(key?: KeyOperator<T, T2>): Promise<number> {
let items: number[] = []
const chunk_helper = (next_items: number[]) => {
items = items.concat(next_items)
const chunkHelper = (nextItems: number[]) => {
items = items.concat(nextItems)
}
if ( key ) await this._chunk_all_numbers(key, chunk_helper)
else await this._chunk(items => {
chunk_helper(items.map(x => Number(x)).all())
})
if ( key ) {
await this.inChunksAllNumbers(key, chunkHelper)
} else {
await this.inChunks(chunkItems => {
chunkHelper(chunkItems.map(x => Number(x)).all())
})
}
items = items.sort((a, b) => a - b)
const middle = Math.floor((items.length - 1) / 2)
if ( items.length % 2 ) return items[middle]
else return (items[middle] + items[middle + 1]) / 2
if ( items.length % 2 ) {
return items[middle]
} else {
return (items[middle] + items[middle + 1]) / 2
}
}
/**
@@ -136,19 +148,25 @@ export class AsyncCollection<T> {
* @return Promise<number>
*/
async mode<T2>(key?: KeyOperator<T, T2>): Promise<number> {
let counts: any = {}
const counts: any = {}
const chunk_helper = (items: number[]) => {
const chunkHelper = (items: number[]) => {
for ( const item of items ) {
if ( !counts[item] ) counts[item] = 1
else counts[item] += 1
if ( !counts[item] ) {
counts[item] = 1
} else {
counts[item] += 1
}
}
}
if ( key ) await this._chunk_all_numbers(key, chunk_helper)
else await this._chunk(items => {
chunk_helper(items.map(x => Number(x)).all())
})
if ( key ) {
await this.inChunksAllNumbers(key, chunkHelper)
} else {
await this.inChunks(items => {
chunkHelper(items.map(x => Number(x)).all())
})
}
return Number(Object.keys(counts).reduce((a, b) => counts[a] > counts[b] ? a : b)[0])
}
@@ -171,10 +189,10 @@ export class AsyncCollection<T> {
* @param [operand]
* @return Promise<boolean>
*/
async contains<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: any): Promise<boolean> {
async contains<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: unknown): Promise<boolean> {
let contains = false
await this._chunk_all_associate(key, (items: AssociatedCollectionItem<T2, T>[]) => {
await this.inChunksAllAssociated(key, (items: AssociatedCollectionItem<T2, T>[]) => {
const matches = applyWhere(items, operator, operand)
if ( matches.length > 0 ) {
contains = true
@@ -190,7 +208,7 @@ export class AsyncCollection<T> {
* @return Promise<AsyncCollection>
*/
async clone(): Promise<AsyncCollection<T>> {
return new AsyncCollection<T>(await this._items.clone())
return new AsyncCollection<T>(await this.storedItems.clone())
}
/**
@@ -201,7 +219,7 @@ export class AsyncCollection<T> {
async diff(items: AsyncCollectionComparable<T>): Promise<Collection<T>> {
const matches: T[] = []
await this._chunk(async chunk => {
await this.inChunks(async chunk => {
for ( const item of chunk.all() ) {
if ( !(await items.includes(item)) ) {
matches.push(item)
@@ -222,10 +240,11 @@ export class AsyncCollection<T> {
async diffUsing(items: AsyncCollectionComparable<T>, compare: DeterminesEquality<T>): Promise<Collection<T>> {
const matches: T[] = []
await this._chunk(async chunk => {
await this.inChunks(async chunk => {
for ( const item of chunk.all() ) {
if ( !(await items.some(exc => compare(item, exc))) )
if ( !(await items.some(exc => compare(item, exc))) ) {
matches.push(item)
}
}
})
@@ -240,7 +259,7 @@ export class AsyncCollection<T> {
async includes(item: CollectionItem<T>): Promise<boolean> {
let contains = false
await this._chunk(items => {
await this.inChunks(items => {
if ( items.includes(item) ) {
contains = true
throw new StopIteration()
@@ -258,7 +277,7 @@ export class AsyncCollection<T> {
async some(operator: (item: T) => boolean): Promise<boolean> {
let contains = false
await this._chunk(items => {
await this.inChunks(items => {
for ( const item of items.all() ) {
if ( operator(item) ) {
contains = true
@@ -278,7 +297,7 @@ export class AsyncCollection<T> {
async each<T2>(func: AsyncKeyFunction<T, T2>): Promise<void> {
let index = 0
await this._chunk(async items => {
await this.inChunks(async items => {
for ( const item of items.all() ) {
await func(item, index)
index += 1
@@ -292,11 +311,11 @@ export class AsyncCollection<T> {
* @return Promise<Collection>
*/
async map<T2>(func: AsyncKeyFunction<T, T2>): Promise<Collection<T2>> {
const new_items: CollectionItem<T2>[] = []
const newItems: CollectionItem<T2>[] = []
await this.each(async (item, index) => {
new_items.push(await func(item, index))
newItems.push(await func(item, index))
})
return new Collection<T2>(new_items)
return new Collection<T2>(newItems)
}
/**
@@ -308,7 +327,7 @@ export class AsyncCollection<T> {
let pass = true
let index = 0
await this._chunk(async items => {
await this.inChunks(async items => {
for ( const item of items.all() ) {
if ( !(await func(item, index)) ) {
pass = false
@@ -328,10 +347,10 @@ export class AsyncCollection<T> {
* @param {WhereOperator} operator
* @param [operand]
*/
async everyWhere<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: any): Promise<boolean> {
async everyWhere<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: unknown): Promise<boolean> {
let pass = true
await this._chunk(async items => {
await this.inChunks(async items => {
pass = pass && items.everyWhere(key, operator, operand)
if ( !pass ) {
throw new StopIteration()
@@ -347,13 +366,13 @@ export class AsyncCollection<T> {
* @return Promise<Collection>
*/
async filter<T2>(func: KeyFunction<T, T2>): Promise<Collection<T>> {
let new_items: CollectionItem<T>[] = []
let newItems: CollectionItem<T>[] = []
await this._chunk(async items => {
new_items = new_items.concat(items.filter(func).all())
await this.inChunks(async items => {
newItems = newItems.concat(items.filter(func).all())
})
return new Collection<T>(new_items)
return new Collection<T>(newItems)
}
/**
@@ -363,7 +382,9 @@ export class AsyncCollection<T> {
* @return AsyncCollection
*/
when<T2>(bool: boolean, then: AsyncCollectionFunction<T, T2>): AsyncCollection<T> {
if ( bool ) then(this)
if ( bool ) {
then(this)
}
return this
}
@@ -374,7 +395,9 @@ export class AsyncCollection<T> {
* @return AsyncCollection
*/
unless<T2>(bool: boolean, then: AsyncCollectionFunction<T, T2>): AsyncCollection<T> {
if ( !bool ) then(this)
if ( !bool ) {
then(this)
}
return this
}
@@ -385,12 +408,12 @@ export class AsyncCollection<T> {
* @param [operand]
* @return Promise<Collection>
*/
async where<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: any): Promise<Collection<T>> {
let new_items: CollectionItem<T>[] = []
await this._chunk(async items => {
new_items = new_items.concat(items.where(key, operator, operand).all())
async where<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: unknown): Promise<Collection<T>> {
let newItems: CollectionItem<T>[] = []
await this.inChunks(async items => {
newItems = newItems.concat(items.where(key, operator, operand).all())
})
return new Collection<T>(new_items)
return new Collection<T>(newItems)
}
/**
@@ -401,12 +424,12 @@ export class AsyncCollection<T> {
* @param [operand]
* @return Promise<Collection>
*/
async whereNot<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: any): Promise<Collection<T>> {
let new_items: CollectionItem<T>[] = []
await this._chunk(async items => {
new_items = new_items.concat(items.whereNot(key, operator, operand).all())
async whereNot<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: unknown): Promise<Collection<T>> {
let newItems: CollectionItem<T>[] = []
await this.inChunks(async items => {
newItems = newItems.concat(items.whereNot(key, operator, operand).all())
})
return new Collection<T>(new_items)
return new Collection<T>(newItems)
}
/**
@@ -416,15 +439,15 @@ export class AsyncCollection<T> {
* @return Promise<Collection>
*/
async whereIn<T2>(key: KeyOperator<T, T2>, items: AsyncCollectionComparable<T2>): Promise<Collection<T>> {
let new_items: CollectionItem<T>[] = []
await this._chunk_all_associate(key,async chunk => {
const newItems: CollectionItem<T>[] = []
await this.inChunksAllAssociated(key, async chunk => {
for ( const item of chunk ) {
if ( await items.includes(item.key) ) {
new_items.push(item.item)
newItems.push(item.item)
}
}
})
return new Collection<T>(new_items)
return new Collection<T>(newItems)
}
/**
@@ -435,15 +458,15 @@ export class AsyncCollection<T> {
* @return Promise<Collection>
*/
async whereNotIn<T2>(key: KeyOperator<T, T2>, items: AsyncCollectionComparable<T2>): Promise<Collection<T>> {
let new_items: CollectionItem<T>[] = []
await this._chunk_all_associate(key,async chunk => {
const newItems: CollectionItem<T>[] = []
await this.inChunksAllAssociated(key, async chunk => {
for ( const item of chunk ) {
if ( !(await items.includes(item.key)) ) {
new_items.push(item.item)
newItems.push(item.item)
}
}
})
return new Collection<T>(new_items)
return new Collection<T>(newItems)
}
/**
@@ -451,7 +474,7 @@ export class AsyncCollection<T> {
* @return Promise<MaybeCollectionItem>
*/
async first(): Promise<MaybeCollectionItem<T>> {
return this._items.at(0)
return this.storedItems.at(0)
}
/**
@@ -463,7 +486,7 @@ export class AsyncCollection<T> {
*/
async firstWhere<T2>(key: KeyOperator<T, T2>, operator: WhereOperator = '=', operand: any = true): Promise<MaybeCollectionItem<T>> {
let item = undefined
await this._chunk_all_associate(key, async items => {
await this.inChunksAllAssociated(key, async items => {
const matches = applyWhere(items, operator, operand)
if ( matches.length > 0 ) {
item = matches[0]
@@ -481,7 +504,7 @@ export class AsyncCollection<T> {
*/
async firstWhereNot<T2>(key: KeyOperator<T, T2>, operator: WhereOperator = '=', operand: any = true): Promise<MaybeCollectionItem<T>> {
let item: MaybeCollectionItem<T> = undefined
await this._chunk(async items => {
await this.inChunks(async items => {
const matches = items.whereNot(key, operator, operand)
if ( matches.length > 0 ) {
item = matches.first()
@@ -495,16 +518,16 @@ export class AsyncCollection<T> {
* Returns the number of elements in this collection.
* @return Promise<number>
*/
async count() {
return this._items.count()
async count(): Promise<number> {
return this.storedItems.count()
}
/**
* Returns the number of elements in this collection.
* @return Promise<number>
*/
async length() {
return this._items.count()
async length(): Promise<number> {
return this.storedItems.count()
}
/**
@@ -514,9 +537,12 @@ export class AsyncCollection<T> {
* @param [fallback]
* @return Promise<any>
*/
async get(index: number, fallback?: any) {
if ( (await this.count()) > index ) return this._items.at(index)
else return fallback
async get(index: number, fallback?: T): Promise<T | undefined> {
if ( (await this.count()) > index ) {
return this.storedItems.at(index)
} else {
return fallback
}
}
/**
@@ -553,13 +579,13 @@ export class AsyncCollection<T> {
* @return Promise<string>
*/
async join(delimiter: string): Promise<string> {
let running_strings: string[] = []
const runningStrings: string[] = []
await this._chunk(async items => {
running_strings.push(items.join(delimiter))
await this.inChunks(async items => {
runningStrings.push(items.join(delimiter))
})
return running_strings.join(delimiter)
return runningStrings.join(delimiter)
}
/**
@@ -580,7 +606,7 @@ export class AsyncCollection<T> {
* @return Promise<boolean>
*/
async isEmpty(): Promise<boolean> {
return (await this._items.count()) < 1
return (await this.storedItems.count()) < 1
}
/**
@@ -588,7 +614,7 @@ export class AsyncCollection<T> {
* @return Promise<boolean>
*/
async isNotEmpty(): Promise<boolean> {
return (await this._items.count()) > 0
return (await this.storedItems.count()) > 0
}
/**
@@ -596,8 +622,10 @@ export class AsyncCollection<T> {
* @return Promise<MaybeCollectionItem>
*/
async last(): Promise<MaybeCollectionItem<T>> {
const length = await this._items.count()
if ( length > 0 ) return this._items.at(length - 1)
const length = await this.storedItems.count()
if ( length > 0 ) {
return this.storedItems.at(length - 1)
}
}
/**
@@ -607,7 +635,7 @@ export class AsyncCollection<T> {
* @param [operand]
* @return Promise<MaybeCollectionItem>
*/
async lastWhere<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: any): Promise<MaybeCollectionItem<T>> {
async lastWhere<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: unknown): Promise<MaybeCollectionItem<T>> {
return (await this.where(key, operator, operand)).last()
}
@@ -618,7 +646,7 @@ export class AsyncCollection<T> {
* @param [operand]
* @return Promise<MaybeCollectionItem>
*/
async lastWhereNot<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: any): Promise<MaybeCollectionItem<T>> {
async lastWhereNot<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: unknown): Promise<MaybeCollectionItem<T>> {
return (await this.whereNot(key, operator, operand)).last()
}
@@ -631,13 +659,13 @@ export class AsyncCollection<T> {
* @return Promise<Collection>
*/
async pluck<T2>(key: KeyOperator<T, T2>): Promise<Collection<T2>> {
let new_items: CollectionItem<T2>[] = []
let newItems: CollectionItem<T2>[] = []
await this._chunk_all(key, async items => {
new_items = new_items.concat(items.all())
await this.inChunksAll(key, async items => {
newItems = newItems.concat(items.all())
})
return new Collection<T2>(new_items)
return new Collection<T2>(newItems)
}
/**
@@ -645,17 +673,19 @@ export class AsyncCollection<T> {
* @param {KeyOperator} key
* @return Promise<number>
*/
async max<T2>(key: KeyOperator<T, T2>): Promise<number> {
let running_max: number
async max<T2>(key: KeyOperator<T, T2>): Promise<number | undefined> {
let runningMax: number | undefined = undefined
await this._chunk_all_numbers(key, async items => {
const local_max = Math.max(...items)
if ( typeof running_max === 'undefined' ) running_max = local_max
else running_max = Math.max(running_max, local_max)
await this.inChunksAllNumbers(key, async items => {
const localMax = Math.max(...items)
if ( typeof runningMax === 'undefined' ) {
runningMax = localMax
} else {
runningMax = Math.max(runningMax, localMax)
}
})
// @ts-ignore
return running_max
return runningMax
}
/**
@@ -672,17 +702,19 @@ export class AsyncCollection<T> {
* @param {KeyOperator} key
* @return Promise<number>
*/
async min<T2>(key: KeyOperator<T, T2>): Promise<number> {
let running_min: number
async min<T2>(key: KeyOperator<T, T2>): Promise<number | undefined> {
let runningMin: number | undefined = undefined
await this._chunk_all_numbers(key, async items => {
const local_min = Math.min(...items)
if ( typeof running_min === 'undefined' ) running_min = local_min
else running_min = Math.min(running_min, local_min)
await this.inChunksAllNumbers(key, async items => {
const localMin = Math.min(...items)
if ( typeof runningMin === 'undefined' ) {
runningMin = localMin
} else {
runningMin = Math.min(runningMin, localMin)
}
})
// @ts-ignore
return running_min
return runningMin
}
/**
@@ -696,16 +728,19 @@ export class AsyncCollection<T> {
/**
* Merge the two collections.
* @param {AsyncCollectionComparable} merge_with
* @param {AsyncCollectionComparable} mergeWith
* @return Promise<Collection>
*/
async merge<T2>(merge_with: AsyncCollectionComparable<T2>): Promise<Collection<T|T2>> {
let items: T2[]
if ( merge_with instanceof Collection ) items = await merge_with.all()
else if ( merge_with instanceof AsyncCollection ) items = await merge_with.all()
else if ( Array.isArray(merge_with) ) items = merge_with
async merge<T2>(mergeWith: AsyncCollectionComparable<T2>): Promise<Collection<T|T2>> {
let items: T2[] = []
if ( mergeWith instanceof Collection ) {
items = await mergeWith.all()
} else if ( mergeWith instanceof AsyncCollection ) {
items = await mergeWith.all()
} else if ( Array.isArray(mergeWith) ) {
items = mergeWith
}
// @ts-ignore
return new Collection<T|T2>([...items, ...await this.all()])
}
@@ -718,11 +753,15 @@ export class AsyncCollection<T> {
const matches: CollectionItem<T>[] = []
let current = 1
await this._chunk(async chunk => {
await this.inChunks(async chunk => {
for ( const item of chunk.all() ) {
if ( current === 1 ) matches.push(item)
if ( current === 1 ) {
matches.push(item)
}
current += 1
if ( current > n ) current = 1
if ( current > n ) {
current = 1
}
}
})
@@ -732,12 +771,12 @@ export class AsyncCollection<T> {
/**
* Return a collection containing the items that would be on the given page, with the given number of items per page.
* @param {number} page
* @param {number} per_page
* @param {number} perPage
*/
async forPage(page: number, per_page: number): Promise<Collection<T>> {
const start = page * per_page - per_page
const end = page * per_page - 1
return this._items.range(start, end)
async forPage(page: number, perPage: number): Promise<Collection<T>> {
const start = page * perPage - perPage
const end = page * perPage - 1
return this.storedItems.range(start, end)
}
/**
@@ -748,10 +787,10 @@ export class AsyncCollection<T> {
return func(this)
}
/*async pop(): Promise<MaybeCollectionItem<T>> {
const next_item = await this._items.next()
if ( !next_item.done ) {
return next_item.value
/* async pop(): Promise<MaybeCollectionItem<T>> {
const nextItem = await this.storedItems.next()
if ( !nextItem.done ) {
return nextItem.value
}
}*/ // TODO Fix this
@@ -762,57 +801,60 @@ export class AsyncCollection<T> {
* @return Promise<Collection>
*/
async random(n: number): Promise<Collection<T>> {
const random_items: CollectionItem<T>[] = []
const fetched_indices: number[] = []
const randomItems: CollectionItem<T>[] = []
const fetchedIndices: number[] = []
const max_n = await this._items.count()
if ( n > max_n ) n = max_n
const maxN = await this.storedItems.count()
if ( n > maxN ) {
n = maxN
}
while ( random_items.length < n ) {
const index = Math.floor(Math.random() * max_n)
while ( randomItems.length < n ) {
const index = Math.floor(Math.random() * maxN)
if ( !fetched_indices.includes(index) ) {
fetched_indices.push(index)
const item = await this._items.at(index)
if ( typeof item !== 'undefined' )
random_items.push(item)
if ( !fetchedIndices.includes(index) ) {
fetchedIndices.push(index)
const item = await this.storedItems.at(index)
if ( typeof item !== 'undefined' ) {
randomItems.push(item)
}
}
}
return new Collection<T>(random_items)
return new Collection<T>(randomItems)
}
/**
* Collapse the collection into a single value using a reducer function.
* @param {KeyReducerFunction} reducer
* @param [initial_value]
* @param [initialValue]
* @return Promise<any>
*/
async reduce<T2>(reducer: KeyReducerFunction<T, T2>, initial_value?: T2): Promise<T2 | undefined> {
let current_value = initial_value
async reduce<T2>(reducer: KeyReducerFunction<T, T2>, initialValue?: T2): Promise<T2 | undefined> {
let currentValue = initialValue
let index = 0
await this._chunk(async items => {
await this.inChunks(async items => {
for ( const item of items.all() ) {
current_value = reducer(current_value, item, index)
currentValue = reducer(currentValue, item, index)
index += 1
}
})
return current_value
return currentValue
}
/**
* Returns a collection of items that fail the truth test.
* @param {AsyncKeyFunction} truth_test
* @param {AsyncKeyFunction} truthTestFunction
* @return Promise<Collection>
*/
async reject<T2>(truth_test: AsyncKeyFunction<T, T2>): Promise<Collection<T>> {
async reject<T2>(truthTestFunction: AsyncKeyFunction<T, T2>): Promise<Collection<T>> {
let rejected: CollectionItem<T>[] = []
await this._chunk(async items => {
await this.inChunks(async items => {
rejected = rejected.concat(items.all().filter((item, index) => {
return !truth_test(item, index)
return !truthTestFunction(item, index)
}))
})
@@ -833,13 +875,13 @@ export class AsyncCollection<T> {
* @return Promise<MaybeCollectionIndex>
*/
async search(item: CollectionItem<T>): Promise<MaybeCollectionIndex> {
let found_index
let foundIndex
let index = 0
await this._chunk(async items => {
items.some(possible_item => {
if ( possible_item === item ) {
found_index = index
await this.inChunks(async items => {
items.some(possibleItem => {
if ( possibleItem === item ) {
foundIndex = index
throw new StopIteration()
}
@@ -848,7 +890,7 @@ export class AsyncCollection<T> {
})
})
return found_index
return foundIndex
}
/**
@@ -856,9 +898,9 @@ export class AsyncCollection<T> {
* @return Promise<MaybeCollectionItem>
*/
async shift(): Promise<MaybeCollectionItem<T>> {
const next_item = await this._items.next()
if ( !next_item.done ) {
return next_item.value
const nextItem = await this.storedItems.next()
if ( !nextItem.done ) {
return nextItem.value
}
}
@@ -877,16 +919,16 @@ export class AsyncCollection<T> {
* @return Promise<Collection>
*/
async slice(start: number, end: number): Promise<Collection<T>> {
return this._items.range(start, end - 1)
return this.storedItems.range(start, end - 1)
}
/**
* Sort the collection, optionally with the given comparison function.
* @param {ComparisonFunction} compare_func
* @param {ComparisonFunction} comparisonFunction
* @return Promise<Collection>
*/
async sort(compare_func?: ComparisonFunction<T>): Promise<Collection<T>> {
return (await this.collect()).sort(compare_func)
async sort(comparisonFunction?: ComparisonFunction<T>): Promise<Collection<T>> {
return (await this.collect()).sort(comparisonFunction)
}
/**
@@ -900,11 +942,11 @@ export class AsyncCollection<T> {
/**
* Reverse sort the collection, optionally with the given comparison function.
* @param {ComparisonFunction} compare_func
* @param {ComparisonFunction} comparisonFunction
* @return Promise<Collection>
*/
async sortDesc(compare_func?: ComparisonFunction<T>): Promise<Collection<T>> {
return (await this.collect()).sortDesc(compare_func)
async sortDesc(comparisonFunction?: ComparisonFunction<T>): Promise<Collection<T>> {
return (await this.collect()).sortDesc(comparisonFunction)
}
/**
@@ -932,20 +974,23 @@ export class AsyncCollection<T> {
* @return Promise<number>
*/
async sum<T2>(key?: KeyOperator<T, T2>): Promise<number> {
let running_sum: number = 0
let runningSum = 0
const chunk_handler = (items: number[]) => {
const chunkHandler = (items: number[]) => {
for ( const item of items ) {
running_sum += item
runningSum += item
}
}
if ( key ) await this._chunk_all_numbers(key, chunk_handler)
else await this._chunk(async chunk => {
chunk_handler(chunk.map(x => Number(x)).all())
})
if ( key ) {
await this.inChunksAllNumbers(key, chunkHandler)
} else {
await this.inChunks(async chunk => {
chunkHandler(chunk.map(x => Number(x)).all())
})
}
return running_sum
return runningSum
}
/**
@@ -954,12 +999,13 @@ export class AsyncCollection<T> {
* @return Promise<Collection>
*/
async take(limit: number): Promise<Collection<T>> {
if ( limit === 0 ) return new Collection<T>()
else if ( limit > 0 ) {
if ( limit === 0 ) {
return new Collection<T>()
} else if ( limit > 0 ) {
return this.slice(0, limit)
} else {
const cnt = await this._items.count()
return this._items.range(cnt - (-1 * limit), cnt - 1)
const cnt = await this.storedItems.count()
return this.storedItems.range(cnt - (-1 * limit), cnt - 1)
}
}
@@ -982,15 +1028,19 @@ export class AsyncCollection<T> {
const has: CollectionItem<T|T2>[] = []
if ( !key ) {
await this._chunk(async items => {
await this.inChunks(async items => {
for ( const item of items.all() ) {
if ( !has.includes(item) ) has.push(item)
if ( !has.includes(item) ) {
has.push(item)
}
}
})
} else {
await this._chunk_all(key, async items => {
await this.inChunksAll(key, async items => {
for ( const item of items.all() ) {
if ( !has.includes(item) ) has.push(item)
if ( !has.includes(item) ) {
has.push(item)
}
}
})
}
@@ -1005,9 +1055,13 @@ export class AsyncCollection<T> {
async toArray(): Promise<any[]> {
const returns: any = []
for ( const item of (await this.all()) ) {
if ( item instanceof Collection ) returns.push(item.toArray())
else if ( item instanceof AsyncCollection ) returns.push(await item.toArray())
else returns.push(item)
if ( item instanceof Collection ) {
returns.push(item.toArray())
} else if ( item instanceof AsyncCollection ) {
returns.push(await item.toArray())
} else {
returns.push(item)
}
}
return returns
}
@@ -1025,7 +1079,7 @@ export class AsyncCollection<T> {
* Get a clone of the underlying iterator of this collection.
* @return Iterable
*/
iterator() {
return this._items.clone()
iterator(): Iterable<T> {
return this.storedItems.clone()
}
}

View File

@@ -1,4 +1,4 @@
import {Pipe} from "../support/Pipe";
import {Pipe} from '../support/Pipe'
type CollectionItem<T> = T
type MaybeCollectionItem<T> = CollectionItem<T> | undefined
@@ -11,9 +11,9 @@ type CollectionComparable<T> = CollectionItem<T>[] | Collection<T>
type DeterminesEquality<T> = (item: CollectionItem<T>, other: any) => boolean
type CollectionIndex = number
type MaybeCollectionIndex = CollectionIndex | undefined
type ComparisonFunction<T> = (item: CollectionItem<T>, other_item: CollectionItem<T>) => number
type ComparisonFunction<T> = (item: CollectionItem<T>, otherItem: CollectionItem<T>) => number
import { WhereOperator, applyWhere, whereMatch } from "./where";
import { WhereOperator, applyWhere, whereMatch } from './where'
const collect = <T>(items: CollectionItem<T>[]): Collection<T> => Collection.collect(items)
export {
@@ -40,13 +40,13 @@ export {
* Provides helpers for accessing sub-keys, filtering, piping, and aggregate functions.
*/
class Collection<T> {
private _items: CollectionItem<T>[] = []
private storedItems: CollectionItem<T>[] = []
/**
* Create a new collection from an array of items.
* @param items
*/
public static collect<T>(items: CollectionItem<T>[]): Collection<T> {
public static collect<T2>(items: CollectionItem<T2>[]): Collection<T2> {
return new Collection(items)
}
@@ -64,56 +64,63 @@ class Collection<T> {
* @param size
* @param item
*/
public static fill<T>(size: number, item: T): Collection<T> {
public static fill<T2>(size: number, item: T2): Collection<T2> {
const arr = Array(size).fill(item)
return new Collection<T>(arr)
return new Collection<T2>(arr)
}
constructor(
/**
* The items to base the collection on.
*/
items?: CollectionItem<T>[]
items?: CollectionItem<T>[],
) {
if ( items )
this._items = items
if ( items ) {
this.storedItems = items
}
}
private _all<T2>(key: KeyOperator<T, T2>): CollectionItem<T2>[] {
private allOperator<T2>(key: KeyOperator<T, T2>): CollectionItem<T2>[] {
let items: CollectionItem<T2>[] = []
if ( typeof key === 'function' ) {
items = this._items.map(key)
items = this.storedItems.map(key)
} else {
items = this._items.map((item: CollectionItem<T>) => (<any>item)[key])
items = this.storedItems.map((item: CollectionItem<T>) => (<any>item)[key])
}
return items
}
private _all_numbers<T2>(key: KeyOperator<T, T2>): number[] {
return this._all(key).map(value => Number(value))
private allAsNumbers<T2>(key: KeyOperator<T, T2>): number[] {
return this.allOperator(key).map(value => Number(value))
}
private _all_associate<T2>(key: KeyOperator<T, T2>): AssociatedCollectionItem<T2, T>[] {
const assoc_items: AssociatedCollectionItem<T2, T>[] = []
let items = [...this._items]
private allAssociated<T2>(key: KeyOperator<T, T2>): AssociatedCollectionItem<T2, T>[] {
const associatedItems: AssociatedCollectionItem<T2, T>[] = []
const items = [...this.storedItems]
if ( typeof key === 'function' ) {
items.map((item, index) => {
const key_item = key(item, index)
assoc_items.push({ key: key_item, item })
const keyItem = key(item, index)
associatedItems.push({
key: keyItem,
item,
})
})
} else {
items.map((item, index) => {
assoc_items.push({key: (<any>item)[key], item})
items.map(item => {
associatedItems.push({
key: (<any>item)[key],
item,
})
})
}
return assoc_items
return associatedItems
}
/**
* Cast the collection to an array.
*/
all(): CollectionItem<T>[] {
return [...this._items]
return [...this.storedItems]
}
/**
@@ -122,11 +129,16 @@ class Collection<T> {
*/
average<T2>(key?: KeyOperator<T, T2>): number {
let items
if ( key ) items = this._all_numbers(key)
else items = this._items.map(x => Number(x))
if ( items.length === 0 ) return 0
if ( key ) {
items = this.allAsNumbers(key)
} else {
items = this.storedItems.map(x => Number(x))
}
if ( items.length === 0 ) {
return 0
}
let sum = items.reduce((prev, curr) => prev + curr)
const sum = items.reduce((prev, curr) => prev + curr)
return sum / items.length
}
@@ -136,12 +148,18 @@ class Collection<T> {
*/
median<T2>(key?: KeyOperator<T, T2>): number {
let items
if ( key ) items = this._all_numbers(key).sort((a, b) => a - b)
else items = this._items.map(x => Number(x)).sort((a, b) => a - b)
if ( key ) {
items = this.allAsNumbers(key).sort((a, b) => a - b)
} else {
items = this.storedItems.map(x => Number(x)).sort((a, b) => a - b)
}
const middle = Math.floor((items.length - 1) / 2)
if ( items.length % 2 ) return items[middle]
else return (items[middle] + items[middle + 1]) / 2
if ( items.length % 2 ) {
return items[middle]
} else {
return (items[middle] + items[middle + 1]) / 2
}
}
/**
@@ -150,10 +168,13 @@ class Collection<T> {
*/
mode<T2>(key?: KeyOperator<T, T2>): number {
let items
if ( key ) items = this._all_numbers(key).sort((a, b) => a - b)
else items = this._items.map(x => Number(x)).sort((a, b) => a - b)
if ( key ) {
items = this.allAsNumbers(key).sort((a, b) => a - b)
} else {
items = this.storedItems.map(x => Number(x)).sort((a, b) => a - b)
}
let counts: any = {}
const counts: any = {}
for ( const item of items ) {
counts[item] = (counts[item] ?? -1) + 1
}
@@ -165,24 +186,22 @@ class Collection<T> {
* Collapse a (potentially nested) collection of items down to a single dimension.
*/
collapse(): Collection<any> {
const new_items: CollectionItem<T>[] = []
const items = [...this._items]
const get_layer = (current: CollectionItem<T>|CollectionItem<T>[]) => {
const newItems: CollectionItem<T>[] = []
const items = [...this.storedItems]
const getLayer = (current: CollectionItem<T>|CollectionItem<T>[]) => {
if ( typeof (<any>current)[Symbol.iterator] === 'function' ) {
// @ts-ignore
// TODO fix this
for (const item of current) {
for (const item of (current as any)) {
if (Array.isArray(item)) {
get_layer(item)
getLayer(item)
} else {
new_items.push(item)
newItems.push(item)
}
}
}
}
get_layer(items)
return new Collection(new_items)
getLayer(items)
return new Collection(newItems)
}
/**
@@ -197,8 +216,8 @@ class Collection<T> {
* @param operator
* @param operand
*/
contains<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: any): boolean {
const associate = this._all_associate(key)
contains<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: unknown): boolean {
const associate = this.allAssociated(key)
const matches = applyWhere(associate, operator, operand)
return matches.length > 0
}
@@ -210,7 +229,7 @@ class Collection<T> {
* Does NOT deep copy the underlying items.
*/
clone(): Collection<T> {
return new Collection<T>(this._items)
return new Collection<T>(this.storedItems)
}
/**
@@ -220,8 +239,10 @@ class Collection<T> {
diff<T2>(items: CollectionComparable<T|T2>): Collection<T> {
const exclude = items instanceof Collection ? items.all() : items
const matches = []
for ( const item of [...this._items] ) {
if ( !exclude.includes(item) ) matches.push(item)
for ( const item of [...this.storedItems] ) {
if ( !exclude.includes(item) ) {
matches.push(item)
}
}
return new Collection(matches)
}
@@ -241,9 +262,10 @@ class Collection<T> {
diffUsing<T2>(items: CollectionComparable<T|T2>, compare: DeterminesEquality<T>): Collection<T> {
const exclude = items instanceof Collection ? items.all() : items
const matches = []
for ( const item of [...this._items] ) {
if ( !exclude.some(exc => compare(item, exc)) )
for ( const item of [...this.storedItems] ) {
if ( !exclude.some(exc => compare(item, exc)) ) {
matches.push(item)
}
}
return new Collection(matches)
}
@@ -254,7 +276,7 @@ class Collection<T> {
* @param func
*/
some(func: (item: T) => boolean): boolean {
return this._items.some(func)
return this.storedItems.some(func)
}
/**
@@ -262,7 +284,7 @@ class Collection<T> {
* @param func
*/
each<T2>(func: KeyFunction<T, T2>): void {
this._items.map(func)
this.storedItems.map(func)
}
/**
@@ -270,11 +292,11 @@ class Collection<T> {
* @param func
*/
map<T2>(func: KeyFunction<T, T2>): Collection<T2> {
const new_items: CollectionItem<T2>[] = []
const newItems: CollectionItem<T2>[] = []
this.each(((item, index) => {
new_items.push(func(item, index))
newItems.push(func(item, index))
}))
return new Collection<T2>(new_items)
return new Collection<T2>(newItems)
}
/**
@@ -291,7 +313,7 @@ class Collection<T> {
keyBy(key: KeyOperator<T, string>): {[key: string]: T} {
const obj: {[key: string]: T} = {}
this._all_associate(key).forEach(assoc => {
this.allAssociated(key).forEach(assoc => {
obj[assoc.key] = assoc.item
})
@@ -314,13 +336,12 @@ class Collection<T> {
keyMap<T2>(key: KeyOperator<T, string>, value: KeyOperator<T, T2>): {[key: string]: T2} {
const obj: {[key: string]: T2} = {}
let i = -1;
this._all_associate(key).forEach(assoc => {
i += 1;
let i = -1
this.allAssociated(key).forEach(assoc => {
i += 1
if ( typeof value === 'string' ) {
// @ts-ignore
obj[assoc.key] = assoc.item[value]
obj[assoc.key] = (assoc.item as any)[value]
} else {
obj[assoc.key] = value(assoc.item, i)
}
@@ -334,11 +355,11 @@ class Collection<T> {
* @param func
*/
every<T2>(func: KeyFunction<T, T2>): boolean {
return this._items.every(func)
return this.storedItems.every(func)
}
everyWhere<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: any): boolean {
const items = this._all_associate(key)
everyWhere<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: unknown): boolean {
const items = this.allAssociated(key)
return items.every(item => whereMatch(item, operator, operand))
}
@@ -347,7 +368,7 @@ class Collection<T> {
* @param func
*/
filter<T2>(func: KeyFunction<T, T2>): Collection<T> {
return new Collection(this._items.filter(func))
return new Collection(this.storedItems.filter(func))
}
/**
@@ -355,14 +376,14 @@ class Collection<T> {
* @param func
*/
find<T2>(func: KeyFunction<T, T2>): number | undefined {
let found_index: number | undefined = undefined
this._items.some((item, index) => {
let foundIndex: number | undefined = undefined
this.storedItems.some((item, index) => {
if ( func(item, index) ) {
found_index = index
foundIndex = index
return true
}
})
return found_index
return foundIndex
}
/**
@@ -372,7 +393,9 @@ class Collection<T> {
* @param then
*/
when<T2>(bool: boolean, then: CollectionFunction<T, T2>): Collection<T> {
if ( bool ) then(this)
if ( bool ) {
then(this)
}
return this
}
@@ -383,7 +406,9 @@ class Collection<T> {
* @param then
*/
unless<T2>(bool: boolean, then: CollectionFunction<T, T2>): Collection<T> {
if ( !bool ) then(this)
if ( !bool ) {
then(this)
}
return this
}
@@ -405,8 +430,8 @@ class Collection<T> {
* @param operator
* @param operand
*/
where<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: any): Collection<T> {
const items = this._all_associate(key)
where<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: unknown): Collection<T> {
const items = this.allAssociated(key)
return new Collection(applyWhere(items, operator, operand))
}
@@ -416,7 +441,7 @@ class Collection<T> {
* @param operator
* @param operand
*/
whereNot<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: any): Collection<T> {
whereNot<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: unknown): Collection<T> {
return this.diff(this.where(key, operator, operand))
}
@@ -428,8 +453,10 @@ class Collection<T> {
whereIn<T2>(key: KeyOperator<T, T2>, items: CollectionComparable<T2>): Collection<T> {
const allowed = items instanceof Collection ? items.all() : items
const matches = []
for ( const { key: search, item } of this._all_associate(key) ) {
if ( allowed.includes(search) ) matches.push(item)
for ( const { key: search, item } of this.allAssociated(key) ) {
if ( allowed.includes(search) ) {
matches.push(item)
}
}
return new Collection(matches)
}
@@ -447,7 +474,9 @@ class Collection<T> {
* Return the first item in the collection, if it exists.
*/
first(): MaybeCollectionItem<T> {
if ( this.length > 0 ) return this._items[0]
if ( this.length > 0 ) {
return this.storedItems[0]
}
}
/**
@@ -458,7 +487,9 @@ class Collection<T> {
*/
firstWhere<T2>(key: KeyOperator<T, T2>, operator: WhereOperator = '=', operand: any = true): MaybeCollectionItem<T> {
const items = this.where(key, operator, operand).all()
if ( items.length > 0 ) return items[0]
if ( items.length > 0 ) {
return items[0]
}
}
/**
@@ -467,23 +498,25 @@ class Collection<T> {
* @param operator
* @param operand
*/
firstWhereNot<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: any): MaybeCollectionItem<T> {
firstWhereNot<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: unknown): MaybeCollectionItem<T> {
const items = this.whereNot(key, operator, operand).all()
if ( items.length > 0 ) return items[0]
if ( items.length > 0 ) {
return items[0]
}
}
/**
* Get the number of items in the collection.
*/
get length() {
return this._items.length
get length(): number {
return this.storedItems.length
}
/**
* Get the number of items in the collection.
*/
count() {
return this._items.length
count(): number {
return this.storedItems.length
}
// TODO flatten - depth
@@ -494,9 +527,12 @@ class Collection<T> {
* @param index
* @param fallback
*/
get(index: number, fallback?: any) {
if ( this.length > index ) return this._items[index]
else return fallback
get(index: number, fallback?: T): MaybeCollectionItem<T> {
if ( this.length > index ) {
return this.storedItems[index]
} else {
return fallback
}
}
/**
@@ -524,13 +560,17 @@ class Collection<T> {
* @param key
*/
groupBy<T2>(key: KeyOperator<T, T2>): any {
const items = this._all_associate(key)
const items = this.allAssociated(key)
const groups: any = {}
for ( const item of items ) {
const key = String(item.key)
if ( !groups[key] ) groups[key] = []
groups[key].push(item.item)
const itemKey = String(item.key)
if ( !groups[itemKey] ) {
groups[itemKey] = []
}
groups[itemKey].push(item.item)
}
return groups
}
@@ -540,7 +580,7 @@ class Collection<T> {
* @param key
*/
associate<T2>(key: KeyOperator<T, T2>): any {
const items = this._all_associate(key)
const items = this.allAssociated(key)
const values: any = {}
for ( const item of items ) {
values[String(item.key)] = item.item
@@ -553,7 +593,7 @@ class Collection<T> {
* @param delimiter
*/
join(delimiter: string): string {
return this._items.join(delimiter)
return this.storedItems.join(delimiter)
}
/**
@@ -573,15 +613,22 @@ class Collection<T> {
intersect(items: CollectionComparable<T>, key?: KeyOperator<T, T>): Collection<T> {
const compare = items instanceof Collection ? items.all() : items
const intersect = []
let all_items
if ( key ) all_items = this._all_associate(key)
else all_items = this._items.map(item => {
return { key: item, item }
})
let allItems
if ( key ) {
allItems = this.allAssociated(key)
} else {
allItems = this.storedItems.map(item => {
return {
key: item,
item,
}
})
}
for ( const item of all_items ) {
if ( compare.includes(item.key) )
for ( const item of allItems ) {
if ( compare.includes(item.key) ) {
intersect.push(item.item)
}
}
return new Collection(intersect)
}
@@ -604,7 +651,9 @@ class Collection<T> {
* Return the last item in the collection.
*/
last(): MaybeCollectionItem<T> {
if ( this.length > 0 ) return this._items.reverse()[0]
if ( this.length > 0 ) {
return this.storedItems.reverse()[0]
}
}
/**
@@ -613,9 +662,11 @@ class Collection<T> {
* @param operator
* @param operand
*/
lastWhere<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: any): MaybeCollectionItem<T> {
lastWhere<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: unknown): MaybeCollectionItem<T> {
const items = this.where(key, operator, operand).all()
if ( items.length > 0 ) return items.reverse()[0]
if ( items.length > 0 ) {
return items.reverse()[0]
}
}
/**
@@ -624,9 +675,11 @@ class Collection<T> {
* @param operator
* @param operand
*/
lastWhereNot<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: any): MaybeCollectionItem<T> {
lastWhereNot<T2>(key: KeyOperator<T, T2>, operator: WhereOperator, operand?: unknown): MaybeCollectionItem<T> {
const items = this.whereNot(key, operator, operand).all()
if ( items.length > 0 ) return items.reverse()[0]
if ( items.length > 0 ) {
return items.reverse()[0]
}
}
/**
@@ -646,7 +699,7 @@ class Collection<T> {
* @param key
*/
pluck<T2>(key: KeyOperator<T, T2>): Collection<T2> {
return new Collection<T2>(this._all(key))
return new Collection<T2>(this.allOperator(key))
}
/**
@@ -654,7 +707,7 @@ class Collection<T> {
* @param key
*/
max<T2>(key: KeyOperator<T, T2>): number {
const values = this._all_numbers(key)
const values = this.allAsNumbers(key)
return Math.max(...values)
}
@@ -671,7 +724,7 @@ class Collection<T> {
* @param key
*/
min<T2>(key: KeyOperator<T, T2>): number {
const values = this._all_numbers(key)
const values = this.allAsNumbers(key)
return Math.min(...values)
}
@@ -689,7 +742,7 @@ class Collection<T> {
*/
merge<T2>(items: CollectionComparable<T2>): Collection<T|T2> {
const merge = items instanceof Collection ? items.all() : items
return new Collection([...this._items, ...merge])
return new Collection([...this.storedItems, ...merge])
}
/**
@@ -707,10 +760,15 @@ class Collection<T> {
nth(n: number): Collection<T> {
const matches: CollectionItem<T>[] = []
let current = 1
this._items.forEach((item, index) => {
if ( current === 1 ) matches.push(item)
this.storedItems.forEach(item => {
if ( current === 1 ) {
matches.push(item)
}
current += 1
if ( current > n ) current = 1
if ( current > n ) {
current = 1
}
})
return new Collection(matches)
}
@@ -720,10 +778,10 @@ class Collection<T> {
* @param page
* @param perPage
*/
forPage(page: number, perPage: number) {
forPage(page: number, perPage: number): Collection<T> {
const start = page * perPage - perPage
const end = page * perPage
return new Collection(this._items.slice(start, end))
return new Collection(this.storedItems.slice(start, end))
}
/**
@@ -738,7 +796,7 @@ class Collection<T> {
*/
pop(): MaybeCollectionItem<T> {
if ( this.length > 0 ) {
return this._items.pop()
return this.storedItems.pop()
}
}
@@ -747,7 +805,7 @@ class Collection<T> {
* @param item
*/
prepend(item: CollectionItem<T>): Collection<T> {
this._items = [item, ...this._items]
this.storedItems = [item, ...this.storedItems]
return this
}
@@ -756,7 +814,7 @@ class Collection<T> {
* @param item
*/
push(item: CollectionItem<T>): Collection<T> {
this._items.push(item)
this.storedItems.push(item)
return this
}
@@ -768,7 +826,7 @@ class Collection<T> {
concat(items: CollectionComparable<T>): Collection<T> {
const concats = items instanceof Collection ? items.all() : items
for ( const item of concats ) {
this._items.push(item)
this.storedItems.push(item)
}
return this
}
@@ -779,19 +837,21 @@ class Collection<T> {
* @param item
*/
put(index: number, item: CollectionItem<T>): Collection<T> {
const new_items = []
const newItems = []
let inserted = false
this._items.forEach((existing, existing_index) => {
if ( existing_index === index ) {
new_items.push(item)
this.storedItems.forEach((existing, existingIndex) => {
if ( existingIndex === index ) {
newItems.push(item)
inserted = true
}
new_items.push(existing)
newItems.push(existing)
})
if ( !inserted ) new_items.push(item)
return new Collection(new_items)
if ( !inserted ) {
newItems.push(item)
}
return new Collection(newItems)
}
/**
@@ -799,14 +859,18 @@ class Collection<T> {
* @param n
*/
random(n: number): Collection<T> {
const random_items: CollectionItem<T>[] = []
const all = this._items
if ( n > this.length ) n = this.length
while ( random_items.length < n ) {
const item = all[Math.floor(Math.random() * all.length)]
if ( !random_items.includes(item) ) random_items.push(item)
const randomItems: CollectionItem<T>[] = []
const all = this.storedItems
if ( n > this.length ) {
n = this.length
}
return new Collection(random_items)
while ( randomItems.length < n ) {
const item = all[Math.floor(Math.random() * all.length)]
if ( !randomItems.includes(item) ) {
randomItems.push(item)
}
}
return new Collection(randomItems)
}
/**
@@ -820,23 +884,24 @@ class Collection<T> {
* ```
*
* @param reducer
* @param initial_value
* @param initialValue
*/
reduce<T2>(reducer: KeyReducerFunction<T, T2>, initial_value?: T2): T2 | undefined {
let current_value = initial_value
this._items.forEach((item, index) => {
current_value = reducer(current_value, item, index)
reduce<T2>(reducer: KeyReducerFunction<T, T2>, initialValue?: T2): T2 | undefined {
let currentValue = initialValue
this.storedItems.forEach((item, index) => {
currentValue = reducer(currentValue, item, index)
})
return current_value
return currentValue
}
/**
* Return a new collection of items that fail the given truth-test function.
* @param truth_test
* @param truthTestFunction
*/
reject<T2>(truth_test: KeyFunction<T, T2>): Collection<T> {
const rejected = this._items.filter((item, index) => {
return !truth_test(item, index)
reject<T2>(truthTestFunction: KeyFunction<T, T2>): Collection<T> {
const rejected = this.storedItems.filter((item, index) => {
return !truthTestFunction(item, index)
})
return new Collection(rejected)
}
@@ -845,7 +910,7 @@ class Collection<T> {
* Return a new collection whose items are in the reverse order of the current one.
*/
reverse(): Collection<T> {
return new Collection([...this._items.reverse()])
return new Collection([...this.storedItems.reverse()])
}
/**
@@ -853,14 +918,14 @@ class Collection<T> {
* @param item
*/
search(item: CollectionItem<T>): MaybeCollectionIndex {
let found_index
this._items.some((possible_item, index) => {
if ( possible_item === item ) {
found_index = index
let foundIndex
this.storedItems.some((possibleItem, index) => {
if ( possibleItem === item ) {
foundIndex = index
return true
}
})
return found_index
return foundIndex
}
/**
@@ -868,7 +933,7 @@ class Collection<T> {
*/
shift(): MaybeCollectionItem<T> {
if ( this.length > 0 ) {
return this._items.shift()
return this.storedItems.shift()
}
}
@@ -876,7 +941,7 @@ class Collection<T> {
* Shuffle the items in this collection to a random order.
*/
shuffle(): Collection<T> {
const items = [...this._items]
const items = [...this.storedItems]
for ( let i = items.length - 1; i > 0; i-- ) {
const j = Math.floor(Math.random() * (i + 1))
;[items[i], items[j]] = [items[j], items[i]]
@@ -889,8 +954,8 @@ class Collection<T> {
* @param start
* @param end
*/
slice(start: number, end: number) {
return new Collection(this._items.slice(start, end))
slice(start: number, end: number): Collection<T> {
return new Collection(this.storedItems.slice(start, end))
}
// TODO split
@@ -898,12 +963,16 @@ class Collection<T> {
/**
* Sort the collection (optionally) using the given comparison function.
* @param compare_func
* @param comparisonFunction
*/
sort(compare_func?: ComparisonFunction<T>): Collection<T> {
const items = this._items
if ( compare_func ) items.sort(compare_func)
else items.sort()
sort(comparisonFunction?: ComparisonFunction<T>): Collection<T> {
const items = this.storedItems
if ( comparisonFunction ) {
items.sort(comparisonFunction)
} else {
items.sort()
}
return new Collection(items)
}
@@ -913,25 +982,33 @@ class Collection<T> {
*/
sortBy<T2>(key?: KeyOperator<T, T2>): Collection<T> {
let items: any[]
if ( key ) items = this._all_associate(key)
else items = this._items.map(item => {
return { key: item, item }
})
if ( key ) {
items = this.allAssociated(key)
} else {
items = this.storedItems.map(item => {
return { key: item,
item }
})
}
items.sort((a: any, b: any) => {
if ( a.key > b.key ) return 1
else if ( a.key < b.key ) return -1
else return 0
if ( a.key > b.key ) {
return 1
} else if ( a.key < b.key ) {
return -1
} else {
return 0
}
})
return new Collection(items.map((item: AssociatedCollectionItem<T2, T>) => item.item))
}
/**
* Identical to `sort()`, but in reverse order.
* @param compare_func
* @param comparisonFunction
*/
sortDesc(compare_func?: ComparisonFunction<T>): Collection<T> {
return this.sort(compare_func).reverse()
sortDesc(comparisonFunction?: ComparisonFunction<T>): Collection<T> {
return this.sort(comparisonFunction).reverse()
}
/**
@@ -948,7 +1025,7 @@ class Collection<T> {
* @param deleteCount
*/
splice(start: CollectionIndex, deleteCount?: number): Collection<T> {
return new Collection([...this._items].splice(start, deleteCount))
return new Collection([...this.storedItems].splice(start, deleteCount))
}
/**
@@ -965,8 +1042,11 @@ class Collection<T> {
*/
sum<T2>(key?: KeyOperator<T, T2>): number {
let items
if ( key ) items = this._all_numbers(key)
else items = this._items.map(x => Number(x))
if ( key ) {
items = this.allAsNumbers(key)
} else {
items = this.storedItems.map(x => Number(x))
}
return items.reduce((prev, curr) => prev + curr)
}
@@ -976,11 +1056,13 @@ class Collection<T> {
* @param limit
*/
take(limit: number): Collection<T> {
if ( limit === 0 ) return new Collection()
else if ( limit > 0 ) {
return new Collection(this._items.slice(0, limit))
if ( limit === 0 ) {
return new Collection()
} else if ( limit > 0 ) {
return new Collection(this.storedItems.slice(0, limit))
} else {
return new Collection(this._items.reverse().slice(0, -1 * limit).reverse())
return new Collection(this.storedItems.reverse().slice(0, -1 * limit)
.reverse())
}
}
@@ -1024,10 +1106,15 @@ class Collection<T> {
unique<T2>(key?: KeyOperator<T, T2>): Collection<T|T2> {
const has: CollectionItem<T|T2>[] = []
let items
if ( key ) items = this._all<T2>(key)
else items = [...this._items]
if ( key ) {
items = this.allOperator<T2>(key)
} else {
items = [...this.storedItems]
}
for ( const item of items ) {
if ( !has.includes(item) ) has.push(item)
if ( !has.includes(item) ) {
has.push(item)
}
}
return new Collection(has)
}
@@ -1037,7 +1124,7 @@ class Collection<T> {
* @param item
*/
includes(item: CollectionItem<T>): boolean {
return this._items.includes(item)
return this.storedItems.includes(item)
}
/**
@@ -1046,8 +1133,10 @@ class Collection<T> {
* @param value
*/
pad(length: number, value: CollectionItem<T>): Collection<T> {
const items = [...this._items]
while ( items.length < length ) items.push(value)
const items = [...this.storedItems]
while ( items.length < length ) {
items.push(value)
}
return new Collection(items)
}
@@ -1056,9 +1145,12 @@ class Collection<T> {
*/
toArray(): any[] {
const returns: any = []
for ( const item of this._items ) {
if ( item instanceof Collection ) returns.push(item.toArray())
else returns.push(item)
for ( const item of this.storedItems ) {
if ( item instanceof Collection ) {
returns.push(item.toArray())
} else {
returns.push(item)
}
}
return returns
}
@@ -1075,18 +1167,18 @@ class Collection<T> {
// TODO getIterator
// TODO getCachingIterator
[Symbol.iterator]() {
const items = this._items
[Symbol.iterator](): Iterator<T> {
const items = this.storedItems
let currentIndex = 0
return {
current_index: 0,
next() {
if ( items.length < 1 || this.current_index + 1 > items.length ) {
return { done: true }
}
const item = items[currentIndex]
currentIndex += 1
const item = items[this.current_index]
this.current_index += 1
return { done: false, value: item }
return {
done: currentIndex > items.length,
value: item,
}
},
}
}
@@ -1109,7 +1201,7 @@ class Collection<T> {
*/
async promiseMap<T2>(func: KeyFunction<T, T2 | Promise<T2>>): Promise<Collection<T2>> {
return new Collection<T2>(await Promise.all(
this.map(func).toArray()
this.map(func).toArray(),
))
}
}

View File

@@ -63,7 +63,8 @@ export abstract class Iterable<T> {
}
this.index = i + 1
return { done: false, value: await this.at(i) }
return { done: false,
value: await this.at(i) }
}
/**
@@ -72,7 +73,7 @@ export abstract class Iterable<T> {
* @param {ChunkCallback} callback
* @return Promise<void>
*/
public async chunk(size: number, callback: ChunkCallback<T>) {
public async chunk(size: number, callback: ChunkCallback<T>): Promise<void> {
const total = await this.count()
while ( this.index < total ) {
@@ -81,8 +82,11 @@ export abstract class Iterable<T> {
try {
await callback(items)
} catch ( error ) {
if ( error instanceof StopIteration ) break
else throw error
if ( error instanceof StopIteration ) {
break
} else {
throw error
}
}
this.index += size
@@ -94,9 +98,12 @@ export abstract class Iterable<T> {
* @param {number} index
* @return Promise<void>
*/
public async seek(index: number) {
if ( index < 0 ) throw new TypeError('Cannot seek to negative index.')
else if ( index >= await this.count() ) throw new TypeError('Cannot seek past last item.')
public async seek(index: number): Promise<void> {
if ( index < 0 ) {
throw new TypeError('Cannot seek to negative index.')
} else if ( index >= await this.count() ) {
throw new TypeError('Cannot seek past last item.')
}
this.index = index
}
@@ -105,15 +112,18 @@ export abstract class Iterable<T> {
* @return Promise<any|undefined>
*/
public async peek(): Promise<T | undefined> {
if ( this.index + 1 >= await this.count() ) return undefined
else return this.at(this.index + 1)
if ( this.index + 1 >= await this.count() ) {
return undefined
} else {
return this.at(this.index + 1)
}
}
/**
* Reset the iterable to the first index.
* @return Promise<any>
*/
public async reset() {
public async reset(): Promise<void> {
this.index = 0
}
}

View File

@@ -20,48 +20,73 @@ type WhereResult = any[]
* @param [operand]
* @return boolean
*/
const whereMatch = (item: AssociatedSearchItem, operator: WhereOperator, operand?: any): boolean => {
const whereMatch = (item: AssociatedSearchItem, operator: WhereOperator, operand?: unknown): boolean => {
switch ( operator ) {
case '&':
if ( item.key & operand ) return true
break
case '>':
if ( item.key > operand ) return true
break
case '>=':
if ( item.key >= operand ) return true
break
case '<':
if ( item.key < operand ) return true
break
case '<=':
if ( item.key <= operand ) return true
break
case '!=':
if ( item.key !== operand ) return true
break
case '<=>':
if ( item.key === operand && typeof item.key !== 'undefined' && item.key !== null )
return true
break
case '%':
if ( item.key % operand ) return true
break
case '|':
if ( item.key | operand ) return true
break
case '!':
if ( !item.key ) return true
break
case '~':
if ( ~item.key ) return true
break
case '=':
if ( item.key === operand ) return true
break
case '^':
if ( item.key ^ operand ) return true
break
case '&':
if ( item.key & Number(operand) ) {
return true
}
break
case '>':
if ( item.key > (operand as any) ) {
return true
}
break
case '>=':
if ( item.key >= (operand as any) ) {
return true
}
break
case '<':
if ( item.key < (operand as any) ) {
return true
}
break
case '<=':
if ( item.key <= (operand as any) ) {
return true
}
break
case '!=':
if ( item.key !== (operand as any) ) {
return true
}
break
case '<=>':
if ( item.key === operand && typeof item.key !== 'undefined' && item.key !== null ) {
return true
}
break
case '%':
if ( item.key % Number(operand) ) {
return true
}
break
case '|':
if ( item.key | Number(operand) ) {
return true
}
break
case '!':
if ( !item.key ) {
return true
}
break
case '~':
if ( ~item.key ) {
return true
}
break
case '=':
if ( item.key === operand ) {
return true
}
break
case '^':
if ( item.key ^ Number(operand) ) {
return true
}
break
}
return false
@@ -73,11 +98,12 @@ const whereMatch = (item: AssociatedSearchItem, operator: WhereOperator, operand
* @param {WhereOperator} operator
* @param [operand]
*/
const applyWhere = (items: AssociatedSearchItem[], operator: WhereOperator, operand?: any): WhereResult => {
const applyWhere = (items: AssociatedSearchItem[], operator: WhereOperator, operand?: unknown): WhereResult => {
const matches: WhereResult = []
for ( const item of items ) {
if ( whereMatch(item, operator, operand) )
if ( whereMatch(item, operator, operand) ) {
matches.push(item.item)
}
}
return matches