Skip to content

round, ceil, floor, abs, clamp, mean additions #10

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 131 additions & 0 deletions src/collect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -706,6 +706,137 @@ function createCollectionOperations<T>(collection: Collection<T>): CollectionOpe
}, 0)
},

round(this: CollectionOperations<any>, keyOrPrecision?: keyof T | number, precisionOrUndefined?: number) {
function roundNumberCollection(this: CollectionOperations<number>, precision: number = 0): CollectionOperations<number> {
const multiplier = 10 ** precision
return collect(
collection.items.map(item =>
Math.round(Number(item) * multiplier) / multiplier
)
)
}

function roundObjectCollection<K extends keyof T>(key: K, precision: number = 0): CollectionOperations<T> {
const multiplier = 10 ** precision
return collect(
collection.items.map(item => ({
...item,
[key]: Math.round(Number(item[key]) * multiplier) / multiplier
}))
)
}

// If first argument is number or undefined, treat as number collection
if (typeof keyOrPrecision === 'number' || keyOrPrecision === undefined) {
return roundNumberCollection.call(this, keyOrPrecision ?? 0)
}

// Otherwise treat as object collection
return roundObjectCollection(keyOrPrecision, precisionOrUndefined ?? 0)
},

ceil(this: CollectionOperations<any>, keyOrPrecision?: keyof T | number, precisionOrUndefined?: number) {
function ceilNumberCollection(this: CollectionOperations<number>, precision: number = 0): CollectionOperations<number> {
const multiplier = 10 ** precision
return collect(
collection.items.map(item =>
Math.ceil(Number(item) * multiplier) / multiplier
)
)
}

function ceilObjectCollection<K extends keyof T>(key: K, precision: number = 0): CollectionOperations<T> {
const multiplier = 10 ** precision
return collect(
collection.items.map(item => ({
...item,
[key]: Math.ceil(Number(item[key]) * multiplier) / multiplier
}))
)
}

if (typeof keyOrPrecision === 'number' || keyOrPrecision === undefined) {
return ceilNumberCollection.call(this, keyOrPrecision ?? 0)
}
return ceilObjectCollection(keyOrPrecision, precisionOrUndefined ?? 0)
},

floor(this: CollectionOperations<any>, keyOrPrecision?: keyof T | number, precisionOrUndefined?: number) {
function floorNumberCollection(this: CollectionOperations<number>, precision: number = 0): CollectionOperations<number> {
const multiplier = 10 ** precision
return collect(
collection.items.map(item =>
Math.floor(Number(item) * multiplier) / multiplier
)
)
}

function floorObjectCollection<K extends keyof T>(key: K, precision: number = 0): CollectionOperations<T> {
const multiplier = 10 ** precision
return collect(
collection.items.map(item => ({
...item,
[key]: Math.floor(Number(item[key]) * multiplier) / multiplier
}))
)
}

if (typeof keyOrPrecision === 'number' || keyOrPrecision === undefined) {
return floorNumberCollection.call(this, keyOrPrecision ?? 0)
}
return floorObjectCollection(keyOrPrecision, precisionOrUndefined ?? 0)
},

abs(this: CollectionOperations<any>, key?: keyof T) {
function absNumberCollection(this: CollectionOperations<number>): CollectionOperations<number> {
return collect(
collection.items.map(item => Math.abs(Number(item)))
)
}

function absObjectCollection<K extends keyof T>(key: K): CollectionOperations<T> {
return collect(
collection.items.map(item => ({
...item,
[key]: Math.abs(Number(item[key]))
}))
)
}

if (key === undefined) {
return absNumberCollection.call(this)
}
return absObjectCollection(key)
},

clamp(this: CollectionOperations<any>, keyOrMin: keyof T | number, minOrMax: number, maxOrUndefined?: number) {
function clampNumberCollection(this: CollectionOperations<number>, min: number, max: number): CollectionOperations<number> {
return collect(
collection.items.map(item =>
Math.min(Math.max(Number(item), min), max)
)
)
}

function clampObjectCollection<K extends keyof T>(key: K, min: number, max: number): CollectionOperations<T> {
return collect(
collection.items.map(item => ({
...item,
[key]: Math.min(Math.max(Number(item[key]), min), max)
}))
)
}

if (typeof keyOrMin === 'number') {
return clampNumberCollection.call(this, keyOrMin, minOrMax)
}
return clampObjectCollection(keyOrMin, minOrMax, maxOrUndefined!)
},

mean(this: CollectionOperations<any>, key?: keyof T) {
return this.avg(key)
},

avg(key?: keyof T): number {
return collection.length ? this.sum(key) / collection.length : 0
},
Expand Down
48 changes: 48 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,54 @@ export interface CollectionOperations<T> extends Collection<T> {
median: (key?: keyof T) => number | undefined
mode: (key?: keyof T) => T | undefined

/**
* Round numeric values to the specified precision
*/
round: {
(this: CollectionOperations<number>, precision?: number): CollectionOperations<number>
<K extends keyof T>(key: K, precision?: number): CollectionOperations<T>
}

/**
* Ceil numeric values
*/
ceil: {
(this: CollectionOperations<number>, precision?: number): CollectionOperations<number>
<K extends keyof T>(key: K, precision?: number): CollectionOperations<T>
}

/**
* Floor numeric values
*/
floor: {
(this: CollectionOperations<number>, precision?: number): CollectionOperations<number>
<K extends keyof T>(key: K, precision?: number): CollectionOperations<T>
}

/**
* Get absolute values
*/
abs: {
(this: CollectionOperations<number>): CollectionOperations<number>
<K extends keyof T>(key: K): CollectionOperations<T>
}

/**
* Clamp values between min and max
*/
clamp: {
(this: CollectionOperations<number>, min: number, max: number): CollectionOperations<number>
<K extends keyof T>(key: K, min: number, max: number): CollectionOperations<T>
}

/**
* Calculate mean (alias for avg)
*/
mean: {
(this: CollectionOperations<number>): number
<K extends keyof T>(key: K): number
}

// Advanced Mathematical Operations
product: (key?: keyof T) => number
standardDeviation: (key?: keyof T) => StandardDeviationResult
Expand Down
133 changes: 133 additions & 0 deletions test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1956,6 +1956,139 @@ describe('Collection Aggregation Methods', () => {
})
})

describe('round()', () => {
it('should round numbers', () => {
const numbers = collect([1.234, 5.678, 9.123])
expect(numbers.round().toArray()).toEqual([1, 6, 9])
expect(numbers.round(2).toArray()).toEqual([1.23, 5.68, 9.12])
})

it('should round by key', () => {
const items = collect([
{ value: 1.234 },
{ value: 5.678 },
{ value: 9.123 },
])
expect(items.round('value', 2).toArray()).toEqual([
{ value: 1.23 },
{ value: 5.68 },
{ value: 9.12 },
])
})
})

describe('ceil()', () => {
it('should ceil numbers', () => {
const numbers = collect([1.234, 5.678, 9.123])
expect(numbers.ceil().toArray()).toEqual([2, 6, 10])
expect(numbers.ceil(2).toArray()).toEqual([1.24, 5.68, 9.13])
})

it('should ceil by key', () => {
const items = collect([
{ value: 1.234 },
{ value: 5.678 },
{ value: 9.123 },
])
expect(items.ceil('value', 2).toArray()).toEqual([
{ value: 1.24 },
{ value: 5.68 },
{ value: 9.13 },
])
})
})

describe('floor()', () => {
it('should floor numbers', () => {
const numbers = collect([1.234, 5.678, 9.123])
expect(numbers.floor().toArray()).toEqual([1, 5, 9])
expect(numbers.floor(2).toArray()).toEqual([1.23, 5.67, 9.12])
})

it('should floor by key', () => {
const items = collect([
{ value: 1.234 },
{ value: 5.678 },
{ value: 9.123 },
])
expect(items.floor('value', 2).toArray()).toEqual([
{ value: 1.23 },
{ value: 5.67 },
{ value: 9.12 },
])
})
})

describe('abs()', () => {
it('should get absolute values', () => {
const numbers = collect([-1.5, 2.5, -3.5, 4.5])
expect(numbers.abs().toArray()).toEqual([1.5, 2.5, 3.5, 4.5])
})

it('should get absolute values by key', () => {
const items = collect([
{ value: -1.5 },
{ value: 2.5 },
{ value: -3.5 },
])
expect(items.abs('value').toArray()).toEqual([
{ value: 1.5 },
{ value: 2.5 },
{ value: 3.5 },
])
})
})

describe('clamp()', () => {
it('should clamp numbers', () => {
const numbers = collect([1, 5, 10, 15, 20])
expect(numbers.clamp(5, 15).toArray()).toEqual([5, 5, 10, 15, 15])
})

it('should clamp by key', () => {
const items = collect([
{ value: 1 },
{ value: 5 },
{ value: 10 },
{ value: 15 },
{ value: 20 },
])
expect(items.clamp('value', 5, 15).toArray()).toEqual([
{ value: 5 },
{ value: 5 },
{ value: 10 },
{ value: 15 },
{ value: 15 },
])
})
})

describe('mean()', () => {
it('should calculate mean of numbers', () => {
const numbers = collect([1, 2, 3, 4, 5])
expect(numbers.mean()).toBe(3)
})

it('should calculate mean by key', () => {
const items = collect([
{ value: 10 },
{ value: 20 },
{ value: 30 },
])
expect(items.mean('value')).toBe(20)
})

it('should handle empty collections', () => {
const empty = collect([])
expect(empty.mean()).toBe(0)
})

it('should handle NaN values', () => {
const withNaN = collect([1, Number.NaN, 3, 4, Number.NaN])
expect(withNaN.mean()).toBe(1.6) // (1 + 0 + 3 + 4 + 0) / 5
})
})

describe('median()', () => {
it('should find median of odd length collection', () => {
const numbers = collect([1, 2, 3, 4, 5])
Expand Down
Loading