Skip to content

Commit

Permalink
Merge pull request #27 from ookami-kb/memoize
Browse files Browse the repository at this point in the history
  • Loading branch information
ookami-kb committed Jun 3, 2024
2 parents 4b13af3 + db84305 commit f5c32b1
Show file tree
Hide file tree
Showing 5 changed files with 132 additions and 1 deletion.
1 change: 1 addition & 0 deletions lib/dfunc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export 'src/map.dart';
export 'src/map_by.dart';
export 'src/map_indexed.dart';
export 'src/maybe.dart';
export 'src/memoize.dart';
export 'src/optional.dart';
export 'src/pipe.dart';
export 'src/scope.dart';
Expand Down
47 changes: 47 additions & 0 deletions lib/src/memoize.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/// {@template dfunc.memoize}
/// The `memoize` function is used to cache the results of expensive function
/// calls and return the cached result when the same inputs are provided again.
///
/// Usage:
///
/// ```dart
/// final memoized = memoize(expensiveFunction);
/// final result = memoized(arg);
/// ```
/// {@endtemplate}
Memoize<A, B> memoize<A, B>(
A Function(B lastArgument) f, {
int? capacity,
}) =>
Memoize<A, B>(f, capacity: capacity);

/// {@macro dfunc.memoize}
class Memoize<A, B> {
/// {@macro dfunc.memoize}
Memoize(this._f, {int? capacity}) : _capacity = capacity;

final Map<B, A> _cache = {};

final A Function(B lastArgument) _f;
final int? _capacity;

/// Returns the cached value for the argument [b] if it exists, otherwise
/// computes the value using the function [_f] and caches it.
A call(B b) {
if (_cache.containsKey(b)) return _cache[b] as A;

final value = _f(b);
_cache[b] = value;

// ignore: avoid-non-null-assertion, checked for null
if (_capacity != null && _cache.length > _capacity!) {
_cache.remove(_cache.keys.first);
}

return value;
}

/// Clears the memoization cache, subsequent call with the same argument will
/// recompute the value.
void reset() => _cache.clear();
}
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@ environment:

dev_dependencies:
melos: ^3.0.1
mews_pedantic: ^0.23.0
mews_pedantic: ^0.26.0
test: ^1.16.2
2 changes: 2 additions & 0 deletions test/intersperse_test.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// ignore_for_file: avoid-unnecessary-collections, avoid-duplicate-collection-elements

import 'package:dfunc/dfunc.dart';
import 'package:test/test.dart';

Expand Down
81 changes: 81 additions & 0 deletions test/memoize_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// ignore_for_file: avoid-duplicate-test-assertions, avoid-duplicate-collection-elements

import 'package:dfunc/dfunc.dart';
import 'package:test/test.dart';

void main() {
test('memoize', () {
int counter = 0;

final memoized = memoize((int arg) {
counter++;

return arg * 2;
});

expect(memoized(1), 2);
expect(counter, 1);

// Doesn't call the function again.
expect(memoized(1), 2);
expect(counter, 1);

// Calls the function for a different argument.
expect(memoized(2), 4);
expect(counter, 2);

// Doesn't call the function again.
expect(memoized(1), 2);
expect(counter, 2);
});

test('memoize with limited capacity', () {
int counter = 0;

final memoized = memoize(capacity: 1, (int arg) {
counter++;

return arg * 2;
});

expect(memoized(1), 2);
expect(counter, 1);

// Doesn't call the function again.
expect(memoized(1), 2);
expect(counter, 1);

// Calls the function for a different argument.
expect(memoized(2), 4);
expect(counter, 2);

// Calls the function again if capacity exceeded.
expect(memoized(1), 2);
expect(counter, 3);
});

test('memoize async', () async {
int counter = 0;

final memoized = memoize((int arg) async {
await Future<void>.delayed(Duration.zero);
counter++;

return arg * 2;
});

expect(await memoized(1), 2);
expect(counter, 1);

// Doesn't call the function again.
expect(await memoized(1), 2);
expect(counter, 1);

// Calls the function one time for a different argument.
expect(
await Future.wait([memoized(2), memoized(2), memoized(2)]),
[4, 4, 4],
);
expect(counter, 2);
});
}

0 comments on commit f5c32b1

Please sign in to comment.