@rimbu/proximity
Version:
Immutable ProximityMap implementation for TypeScript
268 lines (189 loc) • 10.4 kB
Markdown
<p align="center">
<img src="https://github.com/rimbu-org/rimbu/raw/main/assets/rimbu_logo.svg" height="96" alt="Rimbu Logo" />
</p>
<div align="center">
[](https://www.npmjs.com/package/@rimbu/proximity)





</div>
# `@rimbu/proximity`
**Immutable proximity-based maps for TypeScript & JavaScript.**
`@rimbu/proximity` provides the **ProximityMap** collection: an immutable map where lookups are resolved
by **closest key**, according to a configurable `DistanceFunction`. Instead of only matching exact keys,
you can express “nearness” (numbers, vectors, coordinates, scores, etc.) and always retrieve the value
whose key is _closest_ to the one you query.
Use it when you need **nearest-neighbour lookups**, **tolerant matching**, or when reasoning about
**distance between keys** is more natural than exact equality.
---
## Table of Contents
1. [Why `@rimbu/proximity`?](#why-rimbu-proximity)
2. [Feature Highlights](#feature-highlights)
3. [Quick Start](#quick-start)
4. [Core Concepts & Types](#core-concepts--types)
5. [Configuring Distance Functions & Contexts](#configuring-distance-functions--contexts)
6. [Installation](#installation)
7. [FAQ](#faq)
8. [Ecosystem & Integration](#ecosystem--integration)
9. [Contributing](#contributing)
10. [License](#license)
---
## Why `@rimbu/proximity`?
Classic maps answer the question: **“What value is stored for this _exact_ key?”**
Sometimes you really need: **“What value is stored for the key _closest_ to this one?”**
Examples:
- **Numeric thresholds** – map score ranges to labels, but query by actual score.
- **Timestamps** – find the closest recorded event to a given time.
- **Spatial / metric data** – locations, distances, or any metric space.
- **Fuzzy matching** – use a custom distance (e.g. edit distance) instead of strict equality.
`ProximityMap` focuses on:
- **Proximity-aware lookups** – `get` uses a `DistanceFunction` to find the nearest key.
- **Immutable operations** – all updates return new maps, with structural sharing.
- **Configurable metric** – plug in any `DistanceFunction<T>` that returns a non‑negative number.
- **Familiar map semantics** – insertion, removal, streaming, and builders behave like other Rimbu maps.
---
## Feature Highlights
- **Nearest-key lookup** – `get` returns the value whose key has the smallest distance from the query key.
- **Configurable distance** – use the default `DistanceFunction.defaultFunction` (based on `===`), or supply
your own for numbers, vectors, dates, etc.
- **Immutable & persistent** – efficient structural sharing, ideal for functional and reactive code.
- **Builders for bulk updates** – use `ProximityMap.Builder` to perform many mutations before freezing.
- **Context-based configuration** – use `ProximityMap.createContext` to configure the distance function and
underlying `HashMap` context once and reuse it across instances.
---
## Quick Start
```ts
import { ProximityMap } from '@rimbu/proximity';
// Default context uses a strict equality-based distance:
// DistanceFunction.defaultFunction: 0 if a === b, +Infinity otherwise.
const map = ProximityMap.of<[number, string]>(
[10, 'low'],
[20, 'medium'],
[30, 'high']
);
// Exact match: behaves like a normal Map
console.log(map.get(20)); // 'medium'
// Nearest neighbour lookup:
// the closest stored key to 18 is 20
console.log(map.get(18)); // 'medium'
```
Try Rimbu (including `@rimbu/proximity`) live in the browser using the
[Rimbu Sandbox on CodeSandbox](https://codesandbox.io/s/github/vitoke/rimbu-sandbox/tree/main?previewwindow=console&view=split&editorsize=65&moduleview=1&module=/src/index.ts).
---
## Core Concepts & Types
### Exported Types (from `@rimbu/proximity/map`)
| Name | Description |
| ----------------------------- | -------------------------------------------------------------------------------------------------------- |
| `ProximityMap<K, V>` | Immutable, type‑invariant map where lookups are resolved using a `DistanceFunction` over keys. |
| `ProximityMap.NonEmpty<K, V>` | Non‑empty refinement of `ProximityMap<K, V>` with stronger type guarantees. |
| `ProximityMap.Context<UK>` | Factory/context for creating proximity maps; holds the `distanceFunction` and backing `HashMap` context. |
| `ProximityMap.Builder<K, V>` | Mutable builder for efficiently constructing or transforming a `ProximityMap` before freezing it. |
From `@rimbu/proximity/common`:
| Name | Description |
| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| `Distance` | A non‑negative `number` representing the distance between two values (0 = equal, `Number.POSITIVE_INFINITY` = no match). |
| `DistanceFunction<T>` | `(one: T, another: T) => Distance` – measures the distance between two values. |
| `DistanceFunction.defaultFunction` | Default distance function based on `===`. |
| `NearestKeyMatch<K, V>` | Result object describing the closest key, its value, and the associated distance. |
| `findNearestKeyMatch` | Utility to find the nearest key/value pair in an iterable of entries, used internally by `ProximityMap`. |
See the full [Proximity docs](https://rimbu.org/docs/collections/map) and
[API reference](https://rimbu.org/api/rimbu/proximity) for all operations.
### Basic Operations
```ts
import { ProximityMap } from '@rimbu/proximity';
// Construction
const empty = ProximityMap.empty<number, string>();
const fromEntries = ProximityMap.of<[number, string]>(
[0, 'origin'],
[5, 'near'],
[10, 'far']
);
// Size & emptiness
empty.isEmpty; // true
fromEntries.size; // 3
// Nearest-key lookups
fromEntries.get(4); // 'near' (closest key to 4 is 5)
fromEntries.get(8); // 'far' (closest key to 8 is 10)
// Updating (returns new ProximityMap)
const updated = fromEntries.set(7, 'mid');
// Iteration / streaming (via Rimbu's stream API)
for (const [k, v] of updated) {
console.log(k, v);
}
```
---
## Configuring Distance Functions & Contexts
By default, the `ProximityMap` context uses `DistanceFunction.defaultFunction`, which behaves like a
regular equality-based map. To unlock proximity behaviour, supply a custom `DistanceFunction`:
```ts
import { ProximityMap } from '@rimbu/proximity';
import { DistanceFunction } from '@rimbu/proximity/common';
// Example: distance on numbers (absolute difference)
const numericDistance: DistanceFunction<number> = (a, b) => Math.abs(a - b);
const NumericProximityMap = ProximityMap.createContext<number>({
distanceFunction: numericDistance,
});
const numericMap = NumericProximityMap.of<[number, string]>(
[10, 'low'],
[20, 'medium'],
[40, 'high']
);
numericMap.get(22); // 'medium' (closest key is 20)
numericMap.get(35); // 'high' (closest key is 40)
```
You can also customize the underlying `HashMap` context via the `hashMapContext` option if you need
different hashing or equality semantics.
For more advanced usage (builders, non‑empty variants, streaming, etc.), see the
[ProximityMap API docs](https://rimbu.org/api/rimbu/proximity/map/ProximityMap/interface).
---
## Installation
### Node / Bun / npm / Yarn
```sh
npm install @rimbu/proximity
# or
yarn add @rimbu/proximity
# or
bun add @rimbu/proximity
# or
deno add npm:@rimbu/proximity
```
### Browser / ESM
`@rimbu/proximity` ships both **ESM** and **CJS** builds. Use it with any modern bundler
(Vite, Webpack, esbuild, Bun, etc.) or directly in Node ESM projects.
---
## FAQ
**Q: How is a `ProximityMap` different from a regular `Map`?**
A `ProximityMap` doesn’t only match exact keys – it uses a `DistanceFunction` to find the _closest_ key
and returns its value, making it ideal for numeric, temporal, or spatial data.
**Q: What distance function is used by default?**
By default, `DistanceFunction.defaultFunction` is used, which returns `0` when `a === b` and
`Number.POSITIVE_INFINITY` otherwise – effectively behaving like a standard map.
**Q: Is the structure mutable?**
No. All updates return new `ProximityMap` instances; existing ones remain unchanged and can be shared safely.
For batch mutations, use a `ProximityMap.Builder`.
**Q: Can I iterate keys or values separately?**
Yes – `ProximityMap` implements the Rimbu `RMap` interfaces, so you can stream entries, keys, and values
using the standard Rimbu streaming utilities.
---
## Ecosystem & Integration
- Part of the broader **Rimbu** collection ecosystem – interoperates with `@rimbu/hashed`,
`@rimbu/collection-types`, and `@rimbu/stream`.
- Ideal for modelling nearest‑neighbour queries, thresholds, scoring systems, and fuzzy matching.
- Works seamlessly with other Rimbu collections and utilities for building rich, immutable data models.
Explore more at the [Rimbu documentation](https://rimbu.org) and the
[Proximity API docs](https://rimbu.org/api/rimbu/proximity).
---
## Contributing
We welcome contributions! See the
[Contributing guide](https://github.com/rimbu-org/rimbu/blob/main/CONTRIBUTING.md) for details.
<img src="https://contrib.rocks/image?repo=rimbu-org/rimbu" alt="Contributors" />
_Made with [contributors-img](https://contrib.rocks)._
---
## License
MIT © Rimbu contributors. See [LICENSE](./LICENSE) for details.
---
## Attributions
Created and maintained by [Arvid Nicolaas](https://github.com/vitoke) and
[Gianluca Costa](https://gianlucacosta.info). Logo © Rimbu.