Skip to content

Commit

Permalink
feat: add memoLastCall function
Browse files Browse the repository at this point in the history
  • Loading branch information
aleclarson committed Feb 4, 2025
1 parent de0d6d1 commit c9f1125
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 0 deletions.
24 changes: 24 additions & 0 deletions docs/curry/memoLastCall.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
title: memoLastCall
description: Creates a memoized version of a function that caches only its most recent call
---

### Usage

Creates a memoized version of a function that caches only its most recent call. When the function is called with the same arguments as the previous call, it returns the cached result instead of recalculating. This is useful for optimizing expensive calculations when only the latest result needs to be cached, making it more memory-efficient than traditional memoization.

```ts
import * as _ from 'radashi'

const expensiveCalculation = (x: number, y: number): number => {
console.log('Calculating...')
return x + y
}

const memoizedCalc = _.memoLastCall(expensiveCalculation)

console.log(memoizedCalc(2, 3))
console.log(memoizedCalc(2, 3))
console.log(memoizedCalc(3, 4))
console.log(memoizedCalc(2, 3))
```
46 changes: 46 additions & 0 deletions src/curry/memoLastCall.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/**
* Creates a memoized version of a function that caches only its most recent call.
* When the function is called with the same arguments as the previous call, it returns the cached result instead of recalculating.
* This is useful for optimizing expensive calculations when only the latest result needs to be cached, making it more memory-efficient than traditional memoization.
*
* @example
* ```ts
* const expensiveCalculation = (x: number, y: number): number => {
* console.log('Calculating...');
* return x + y;
* };
*
* const memoizedCalc = memoLastCall(expensiveCalculation);
*
* console.log(memoizedCalc(2, 3)); // Outputs: "Calculating..." then 5
* console.log(memoizedCalc(2, 3)); // Outputs: 5 (uses cached result)
* console.log(memoizedCalc(3, 4)); // Outputs: "Calculating..." then 7
* console.log(memoizedCalc(2, 3)); // Outputs: "Calculating..." then 5 (previous cache was overwritten)
* ```
*
* @param fn The function to memoize.
* @returns A memoized version of the function.
*/
export function memoLastCall<Args extends any[], Result>(
fn: (...args: Args) => Result,
): (...args: Args) => Result {
let lastArgs: Args | null = null
let lastResult: Result | null = null

return (...args: Args): Result => {
// Check if we have cached args and if they match current args
if (
lastArgs &&
lastArgs.length === args.length &&
lastArgs.every((arg, i) => Object.is(arg, args[i]))
) {
return lastResult!
}

// If no match, calculate new result and cache it
const result = fn(...args)
lastArgs = args
lastResult = result
return result
}
}
1 change: 1 addition & 0 deletions src/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export * from './curry/compose.ts'
export * from './curry/debounce.ts'
export * from './curry/flip.ts'
export * from './curry/memo.ts'
export * from './curry/memoLastCall.ts'
export * from './curry/once.ts'
export * from './curry/partial.ts'
export * from './curry/partob.ts'
Expand Down
46 changes: 46 additions & 0 deletions tests/curry/memoLastCall.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import * as _ from 'radashi'

describe('memoLastCall', () => {
test('caches the last result', () => {
let calculationCount = 0
const expensiveCalculation = (x: number, y: number): number => {
calculationCount++
return x + y
}

const memoizedCalc = _.memoLastCall(expensiveCalculation)

expect(memoizedCalc(2, 3)).toBe(5)
expect(calculationCount).toBe(1)

expect(memoizedCalc(2, 3)).toBe(5)
expect(calculationCount).toBe(1)

expect(memoizedCalc(3, 4)).toBe(7)
expect(calculationCount).toBe(2)

expect(memoizedCalc(2, 3)).toBe(5)
expect(calculationCount).toBe(3)
})

test('handles different argument types', () => {
const memoizedFn = _.memoLastCall(
(a: string, b: number, c: boolean) => `${a}-${b}-${c}`,
)
expect(memoizedFn('foo', 123, true)).toBe('foo-123-true')
expect(memoizedFn('foo', 123, true)).toBe('foo-123-true')
expect(memoizedFn('bar', 456, false)).toBe('bar-456-false')
})

test('handles functions with no arguments', () => {
let callCount = 0
const memoizedFn = _.memoLastCall(() => {
callCount++
return 'result'
})
expect(memoizedFn()).toBe('result')
expect(callCount).toBe(1)
expect(memoizedFn()).toBe('result')
expect(callCount).toBe(1)
})
})

0 comments on commit c9f1125

Please sign in to comment.