Skip to content

Commit

Permalink
refactor(associative): update EquivMap, update SortedSet, add docs
Browse files Browse the repository at this point in the history
- add IEquivSet interface
- add EquivSetOpts & EquivMapOpts
- rename ArrayMap => EquivMap
- update EquivMap to support customizable key set impls
- rename ArraySet => LLSet
- add actual arraybased ArraySet
- add into() & disj() impls for SortedSet
- add various doc strings
  • Loading branch information
postspectacular committed Apr 13, 2018
1 parent d1178ac commit 1f8af6c
Show file tree
Hide file tree
Showing 13 changed files with 476 additions and 120 deletions.
91 changes: 62 additions & 29 deletions packages/associative/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,24 @@

## About

This package provided alternative Set & Map data type implementations
with customizable equality semantics, as well as common operations
working with these types:
This package provides alternative `Set` & `Map` data type
implementations with customizable equality semantics, as well as common
operations working with these types:

- Array based `ArrayMap` & `ArraySet` and
- Array based `ArraySet`, Linked List based `LLSet`,
[Skiplist](https://en.wikipedia.org/wiki/Skip_list) based `SortedMap`
& `SortedSet` implement the full ES6 Map/Set APIs and additional
features
& `SortedSet` and customizable `EquivMap` implement the full ES6
Map/Set APIs and additional features:
- range query iterators (via `entries()`, `keys()`, `values()`)
(sorted types only)
- `ICopy`, `IEmpty` & `IEquiv` implementations
- `ICompare` implementation for sorted types
- multiple value addition/deletion via `into()` and `disj()`
- configurable key equality & comparison
- multiple value additions / updates / deletions via `into()`,
`dissoc()` (maps) and `disj()` (sets)
- configurable key equality & comparison (incl. default
implementations)
- getters w/ optional "not-found" default value
- `fromObject()` converters (for maps only)
- Polymorphic set operations (union, intersection, difference) - works
with both native and custom Sets and retains their types
- Natural & selective
Expand All @@ -42,11 +45,14 @@ kept immutable (even if technically they're not).
### Comparison with ES6 native types

```ts
// two objects w/ equal values
// first two objects w/ equal values
a = [1, 2];
b = [1, 2];
```

Using native implementations

// using native implementations
```ts
set = new Set();
set.add(a);
set.has(b);
Expand All @@ -72,9 +78,22 @@ set.has(b);
set.has({a: 1});
// true

map = new assoc.ArrayMap();
set = new assoc.LLSet();
set.add(a);
set.add({a: 1});
// LLSet { [ 1, 2 ], { a: 1 } }
set.has(b);
// true
set.has({a: 1});
// true

// by default EquivMap uses ArraySet for its canonical keys
map = new assoc.EquivMap();

// with custom implementation
map = new assoc.EquivMap(null, { keys: assoc.ArraySet });
map.set(a, "foo");
ArrayMap { [ 1, 2 ] => 'foo' }
// EquivMap { [ 1, 2 ] => 'foo' }
map.get(b);
// "foo"

Expand All @@ -101,31 +120,40 @@ yarn add @thi.ng/associative

## Types

### ArrayMap
### IEquivSet

This `Map` implementation uses a native ES6 `Map` as backing storage for
key-value pairs and additional `ArraySet` for canonical keys. By default
it too uses
[@thi.ng/api/equiv](https://github.com/thi-ng/umbrella/tree/master/packages/api/src/equiv.ts)
for equivalence checking of keys.
All `Set` implementations in this package implement the
[IEquivSet](https://github.com/thi-ng/umbrella/tree/master/packages/associative/src/api.ts#L7)
interface, an extension of the native ES6 Set API.

### ArraySet

This `Set` implementation uses
[@thi.ng/dcons](https://github.com/thi-ng/umbrella/tree/master/packages/dcons)
as backing storage for values and by default uses
Simple array based `Set` implementation which by default uses
[@thi.ng/api/equiv](https://github.com/thi-ng/umbrella/tree/master/packages/api/src/equiv.ts)
for equivalence checking.
for value equivalence checking.

### LLSet

Similar to `ArraySet`, but uses
[@thi.ng/dcons](https://github.com/thi-ng/umbrella/tree/master/packages/dcons) linked list
as backing storage for values.

### EquivMap

This `Map` implementation uses a native ES6 `Map` as backing storage for
its key-value pairs and an additional `IEquivSet` implementation for
canonical keys. By default uses `ArraySet` for this purpose.

### SortedMap

This class is an alternative implementation of the ES6 Map API using a
Skip list as backing store and supports configurable key equality and
sorting semantics.
Alternative implementation of the ES6 Map API using a Skip list as
backing store and support for configurable key equality and sorting
semantics. Like with sets, uses @thi.ng/api/equiv & @thi.ng/api/compare
by default.

William Pugh (creator of this data structure) description:
William Pugh's (creator of this data structure) description:

> "Skip lists are a probabilistic data structures that have the same
> "Skip lists are probabilistic data structures that have the same
asymptotic expected time bounds as balanced trees, are simpler, faster
and use less space."

Expand All @@ -143,15 +171,20 @@ map = new assoc.SortedMap([
]);
// SortedMap { 'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4 }

// forward w/ given start key
// forward selection w/ given start key
// also works with `keys()` and `values()`
[...map.entries("c")]
// [ [ 'c', 3 ], [ 'd', 4 ] ]

// unknown start keys are ok
[...map.entries("cc")]
// [ [ 'd', 4 ] ]

// reverse
// reverse order
[...map.entries(undefined, true)]
// [ [ 'd', 4 ], [ 'c', 3 ], [ 'b', 2 ], [ 'a', 1 ] ]

// reverse order from start key
[...map.entries("c", true)]
// [ [ 'c', 3 ], [ 'b', 2 ], [ 'a', 1 ] ]
```
Expand Down
40 changes: 30 additions & 10 deletions packages/associative/src/api.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,47 @@
import { Predicate2, Comparator } from "@thi.ng/api/api";
import { Comparator, ICopy, IEmpty, IEquiv, Predicate2 } from "@thi.ng/api/api";

export type Pair<K, V> = [K, V];

export const SEMAPHORE = Symbol("SEMAPHORE");

export interface ArrayMapOpts<K> {
equiv: Predicate2<K>;
export interface IEquivSet<T> extends
Set<T>,
ICopy<IEquivSet<T>>,
IEmpty<IEquivSet<T>>,
IEquiv {

readonly [Symbol.species]: EquivSetConstructor;
into(xs: Iterable<T>): this;
disj(xs: Iterable<T>): this;
get(val: T, notFound?: any): any;
first(): T;
opts(): EquivSetOpts<T>;
}

export interface ArraySetOpts<T> {
equiv: Predicate2<T>;
export interface EquivSetConstructor {
new(): IEquivSet<any>;
new <T>(values?: Iterable<T>, opts?: EquivSetOpts<T>): IEquivSet<T>;
readonly prototype: IEquivSet<any>;
}

/**
* SortedMapOpts implementation config settings.
*/
export interface SortedMapOpts<K> {
export interface EquivSetOpts<T> {
/**
* Key equivalence predicate. MUST return truthy result if given
* keys are considered equal.
*
* Default: `@thi.ng/api/equiv`
*/
equiv: Predicate2<K>;
equiv: Predicate2<T>;
}

export interface EquivMapOpts<K> extends EquivSetOpts<K> {
keys: EquivSetConstructor;
}

/**
* SortedMapOpts implementation config settings.
*/
export interface SortedMapOpts<K> extends EquivSetOpts<K> {
/**
* Key comparison function. Must follow standard comparator contract
* and return:
Expand All @@ -38,6 +57,7 @@ export interface SortedMapOpts<K> {
/**
* Initial capacity before resizing (doubling) occurs.
* This value will be rounded up to next pow2.
*
* Default: 16
*/
capacity: number;
Expand Down
64 changes: 28 additions & 36 deletions packages/associative/src/array-set.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { ICopy, IEmpty, IEquiv, Predicate2 } from "@thi.ng/api/api";
import { Predicate2 } from "@thi.ng/api/api";
import { equiv } from "@thi.ng/api/equiv";
import { DCons } from "@thi.ng/dcons";

import { ArraySetOpts, Pair, SEMAPHORE } from "./api";
import { EquivSetOpts, IEquivSet, Pair, SEMAPHORE } from "./api";

interface SetProps<T> {
vals: DCons<T>;
vals: T[];
equiv: Predicate2<T>;
}

Expand All @@ -21,14 +20,11 @@ const __private = new WeakMap<ArraySet<any>, SetProps<any>>();
* `IEquiv` interfaces itself.
*/
export class ArraySet<T> extends Set<T> implements
Iterable<T>,
ICopy<ArraySet<T>>,
IEmpty<ArraySet<T>>,
IEquiv {
IEquivSet<T> {

constructor(vals?: Iterable<T>, eq: Predicate2<T> = equiv) {
constructor(vals?: Iterable<T>, opts: Partial<EquivSetOpts<T>> = {}) {
super();
__private.set(this, { equiv: eq, vals: new DCons<T>() });
__private.set(this, { equiv: opts.equiv || equiv, vals: [] });
vals && this.into(vals);
}

Expand All @@ -45,23 +41,22 @@ export class ArraySet<T> extends Set<T> implements
}

copy() {
const $this = __private.get(this);
const s = new ArraySet<T>(null, $this.equiv);
__private.get(s).vals = $this.vals.copy();
const s = new ArraySet<T>(null, this.opts());
__private.get(s).vals = [...__private.get(this).vals];
return s;
}

empty() {
return new ArraySet<T>(null, __private.get(this).equiv);
return new ArraySet<T>(null, this.opts());
}

clear() {
__private.get(this).vals.clear();
__private.get(this).vals.length = 0;
}

first() {
if (this.size) {
return __private.get(this).vals.head.value;
return __private.get(this).vals[0];
}
}

Expand Down Expand Up @@ -91,31 +86,29 @@ export class ArraySet<T> extends Set<T> implements
get(x: T, notFound?: any) {
const $this = __private.get(this);
const eq = $this.equiv;
let i = $this.vals.head;
while (i) {
if (eq(i.value, x)) {
return i.value;
const vals = $this.vals;
for (let i = vals.length - 1; i >= 0; i--) {
if (eq(vals[i], x)) {
return vals[i];
}
i = i.next;
}
return notFound;
}

delete(x: T) {
const $this = __private.get(this)
const eq = $this.equiv;
let i = $this.vals.head;
while (i) {
if (eq(i.value, x)) {
$this.vals.splice(i, 1);
const vals = $this.vals;
for (let i = vals.length - 1; i >= 0; i--) {
if (eq(vals[i], x)) {
vals.splice(i, 1);
return true;
}
i = i.next;
}
return false;
}

disj(...xs: T[]) {
disj(xs: Iterable<T>) {
for (let x of xs) {
this.delete(x);
}
Expand All @@ -132,21 +125,20 @@ export class ArraySet<T> extends Set<T> implements
if (this.size !== o.size) {
return false;
}
let i = __private.get(this).vals.head;
while (i) {
if (!o.has(i.value)) {
const vals = __private.get(this).vals;
for (let i = vals.length - 1; i >= 0; i--) {
if (!o.has(vals[i])) {
return false;
}
i = i.next;
}
return true;
}

forEach(fn: (val: T, val2: T, set: Set<T>) => void, thisArg?: any) {
let i = __private.get(this).vals.head;
while (i) {
fn.call(thisArg, i.value, i.value, this);
i = i.next;
const vals = __private.get(this).vals;
for (let i = vals.length - 1; i >= 0; i--) {
const v = vals[i];
fn.call(thisArg, v, v, this);
}
}

Expand All @@ -164,7 +156,7 @@ export class ArraySet<T> extends Set<T> implements
yield* this.keys();
}

getOpts(): ArraySetOpts<T> {
opts(): EquivSetOpts<T> {
return { equiv: __private.get(this).equiv };
}
}
Loading

0 comments on commit 1f8af6c

Please sign in to comment.