Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@thi.ng/transducers

Package Overview
Dependencies
Maintainers
1
Versions
331
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@thi.ng/transducers - npm Package Compare versions

Comparing version 0.3.2 to 0.4.0

20

index.d.ts

@@ -1,2 +0,2 @@

import { IDeref, Predicate } from "@thi.ng/api";
import { Comparator, IDeref, Predicate } from "@thi.ng/api";
export declare type Transducer<A, B> = (rfn: Reducer<any, B>) => Reducer<any, A>;

@@ -27,4 +27,9 @@ export interface Reducer<A, B> extends Array<any> {

export declare function map<A, B>(fn: (x: A) => B): Transducer<A, B>;
export declare function selectKeys(...keys: PropertyKey[]): Transducer<any, any>;
export declare function mapIndexed<A, B>(fn: (i: number, x: A) => B): Transducer<A, B>;
export declare function mapcat<A, B>(fn: (x: A) => Iterable<B>): Transducer<A, B>;
export declare function cat<T>(): Transducer<T[], T>;
export declare function flatten<T>(): Transducer<T | Iterable<T>, T>;
export declare function flattenOnly<T>(pred: Predicate<T>): Transducer<T | Iterable<T>, T>;
export declare function scan<A, B>(inner: Reducer<B, A>, acc?: B): Transducer<A, B>;
export declare function filter<T>(pred: Predicate<T>): Transducer<T, T>;

@@ -47,5 +52,7 @@ export declare function throttle<T>(delay: number): Transducer<T, T>;

export declare function partitionBy<T>(fn: (x: T) => any): Transducer<T, T[]>;
export declare function chunkSort<T>(n: number, key?: ((x) => any)): Transducer<T, T>;
export declare function streamSort<T>(n: number, key?: ((x) => any)): Transducer<T, T>;
export declare function chunkSort<T>(n: number, key?: ((x: T) => any), cmp?: Comparator<any>): Transducer<T, T>;
export declare function streamSort<T>(n: number, key?: ((x: T) => any), cmp?: Comparator<any>): Transducer<T, T>;
export declare function streamShuffle<T>(n: number, maxSwaps?: number): Transducer<T, T>;
export declare const push: Reducer<any[], any>;
export declare const pushCopy: Reducer<any[], any>;
export declare const conj: Reducer<Set<any>, any>;

@@ -55,4 +62,9 @@ export declare const assocObj: Reducer<any, [PropertyKey, any]>;

export declare const add: Reducer<number, number>;
export declare const count: Reducer<number, number>;
export declare const mul: Reducer<number, number>;
export declare const min: Reducer<number, number>;
export declare const max: Reducer<number, number>;
export declare function minCompare<T>(ident: () => T, cmp?: Comparator<T>): Reducer<T, T>;
export declare function maxCompare<T>(ident: () => T, cmp?: Comparator<T>): Reducer<T, T>;
export declare const frequencies: Reducer<Map<any, number>, any>;
export declare const noop: Reducer<any, any>;
export declare const last: Reducer<any, any>;

164

index.js

@@ -76,3 +76,3 @@ "use strict";

function step(tx) {
const rfn = tx(exports.noop)[2];
const rfn = tx(exports.last)[2];
return (x) => rfn(undefined, x);

@@ -144,2 +144,25 @@ }

exports.map = map;
function selectKeys(...keys) {
const [a, b, c] = keys;
switch (keys.length) {
case 0:
throw new Error(`no keys given`);
case 1:
return map((x) => x[a]);
case 2:
return map((x) => ({ [a]: x[a], [b]: x[b] }));
case 3:
return map((x) => ({ [a]: x[a], [b]: x[b], [c]: x[c] }));
default:
return map((x) => {
const res = {};
for (let i = keys.length - 1; i >= 0; i--) {
const k = keys[i];
res[k] = x[k];
}
return res;
});
}
}
exports.selectKeys = selectKeys;
function mapIndexed(fn) {

@@ -154,8 +177,11 @@ return (rfn) => {

function mapcat(fn) {
return comp(map(fn), cat());
}
exports.mapcat = mapcat;
function cat() {
return (rfn) => {
const r = rfn[2];
return compR(rfn, (acc, x) => {
const xs = fn(x);
if (xs) {
for (let y of xs) {
if (x) {
for (let y of x) {
acc = r(acc, y);

@@ -171,3 +197,39 @@ if (isReduced(acc)) {

}
exports.mapcat = mapcat;
exports.cat = cat;
function flatten() {
return flattenOnly((x) => x != null && x[Symbol.iterator] && typeof x !== "string");
}
exports.flatten = flatten;
function flattenOnly(pred) {
return (rfn) => {
const r = rfn[2], fn = (acc, x) => {
if (pred(x)) {
for (let y of x) {
acc = fn(acc, y);
if (isReduced(acc)) {
break;
}
}
return acc;
}
return r(acc, x);
};
return compR(rfn, fn);
};
}
exports.flattenOnly = flattenOnly;
function scan(inner, acc) {
return (outer) => {
acc = acc || inner[0]();
const ri = inner[2], ro = outer[2];
return compR(outer, (_acc, x) => {
acc = ri(acc, x);
if (isReduced(acc)) {
return ensureReduced(ro(_acc, acc.deref()));
}
return ro(_acc, acc);
});
};
}
exports.scan = scan;
function filter(pred) {

@@ -403,11 +465,11 @@ return (rfn) => {

exports.partitionBy = partitionBy;
function chunkSort(n, key = identity) {
return comp(partition(n, n, true), mapcat((chunk) => chunk.sort((a, b) => compare_1.compare(key(a), key(b)))));
function chunkSort(n, key = identity, cmp = compare_1.compare) {
return comp(partition(n, n, true), mapcat((chunk) => chunk.sort((a, b) => cmp(key(a), key(b)))));
}
exports.chunkSort = chunkSort;
function binarySearch(arr, key, x) {
function binarySearch(arr, key, cmp, x) {
const kx = key(x);
let low = 0, high = arr.length - 1;
while (low <= high) {
const mid = (low + high) >>> 1, c = compare_1.compare(key(arr[mid]), kx);
const mid = (low + high) >>> 1, c = cmp(key(arr[mid]), kx);
if (c < 0) {

@@ -425,3 +487,3 @@ low = mid + 1;

}
function streamSort(n, key = identity) {
function streamSort(n, key = identity, cmp = compare_1.compare) {
return ([i, c, r]) => {

@@ -439,3 +501,3 @@ const buf = [];

(acc, x) => {
buf.splice(binarySearch(buf, key, x), 0, x);
buf.splice(binarySearch(buf, key, cmp, x), 0, x);
if (buf.length === n) {

@@ -450,2 +512,36 @@ acc = r(acc, buf.shift());

exports.streamSort = streamSort;
function shuffle(buf, n) {
const l = buf.length;
n = n < l ? n : l;
while (--n >= 0) {
const a = (Math.random() * l) | 0, b = (Math.random() * l) | 0, t = buf[a];
buf[a] = buf[b];
buf[b] = t;
}
}
function streamShuffle(n, maxSwaps = n) {
return ([i, c, r]) => {
const buf = [];
return [
() => i(),
(acc) => {
while (buf.length && !isReduced(acc)) {
shuffle(buf, maxSwaps);
acc = r(acc, buf.shift());
}
acc = c(acc);
return acc;
},
(acc, x) => {
buf.push(x);
shuffle(buf, maxSwaps);
if (buf.length === n) {
acc = r(acc, buf.shift());
}
return acc;
}
];
};
}
exports.streamShuffle = streamShuffle;
exports.push = [

@@ -456,2 +552,7 @@ () => [],

];
exports.pushCopy = [
() => [],
(acc) => acc,
(acc, x) => ((acc = acc.slice()).push(x), acc)
];
exports.conj = [

@@ -474,10 +575,41 @@ () => new Set(),

() => 0,
(x) => x,
(x, y) => x + y,
(acc) => acc,
(acc, x) => acc + x,
];
exports.count = [
() => 0,
(acc) => acc,
(acc, _) => acc + 1,
];
exports.mul = [
() => 1,
(x) => x,
(x, y) => x * y,
(acc) => acc,
(acc, x) => acc * x,
];
exports.min = [
() => Number.POSITIVE_INFINITY,
(acc) => acc,
(acc, x) => Math.min(acc, x),
];
exports.max = [
() => Number.NEGATIVE_INFINITY,
(acc) => acc,
(acc, x) => Math.max(acc, x),
];
function minCompare(ident, cmp = compare_1.compare) {
return [
ident,
(acc) => acc,
(acc, x) => cmp(acc, x) <= 0 ? acc : x
];
}
exports.minCompare = minCompare;
function maxCompare(ident, cmp = compare_1.compare) {
return [
ident,
(acc) => acc,
(acc, x) => cmp(acc, x) >= 0 ? acc : x
];
}
exports.maxCompare = maxCompare;
exports.frequencies = [

@@ -488,3 +620,3 @@ () => new Map(),

];
exports.noop = [
exports.last = [
() => undefined,

@@ -491,0 +623,0 @@ (acc) => acc,

{
"name": "@thi.ng/transducers",
"version": "0.3.2",
"version": "0.4.0",
"description": "Lightweight transducer implementations for ES6 / TypeScript",

@@ -5,0 +5,0 @@ "main": "index.js",

@@ -5,4 +5,7 @@ # @thi.ng/transducers

Lightweight transducer implementations for ES6 / TypeScript (6KB minified).
Lightweight transducer implementations for ES6 / TypeScript (7.6KB minified).
The library provides 28 transducers and 14 reducers for composing data
transformation pipelines (more to come).
## Installation

@@ -12,3 +15,2 @@

yarn add @thi.ng/transducers
yarn run build
```

@@ -33,2 +35,9 @@

for(let x of tx.iterator(xform, [1, 2, 3, 4])) {
console.log(x);
}
// 3
// 9
// 15
tx.transduce(tx.map(x => x.toUpperCase()), tx.frequencies, "hello world")

@@ -40,10 +49,38 @@ // Map { 'H' => 1, 'E' => 1, 'L' => 3, 'O' => 2, ' ' => 1, 'W' => 1, 'R' => 1, 'D' => 1 }

for(let x of tx.iterator(xform, [1, 2, 3, 4])) {
console.log(x);
}
// early termination:
// result is realized after max. 7 values, irrespective of nesting
tx.transduce(
tx.comp(tx.flatten(), tx.take(7)),
tx.push,
[1, [2, [3, 4, [5, 6, [7, 8], 9, [10]]]]]
)
// [1, 2, 3, 4, 5, 6, 7]
// this transducer uses 2 scans (a scan = inner reducer per item)
// 1) counts incoming values
// 2) forms an array of the current counter value `x` & repeated `x` times
// 3) emits results as series of reductions in the outer array produced
// by the main reducer
// IMPORTANT: since arrays are mutable we use `pushCopy` as the inner reducer
// instead of `push` (the outer reducer)
xform = tx.comp(
tx.scan(tx.count),
tx.map(x => [...tx.iterator(tx.repeat(x), [x])]),
tx.scan(tx.pushCopy)
);
tx.transduce(xform, tx.push, [1, 1, 1, 1]);
// [ [ [ 1 ] ],
// [ [ 1 ], [ 2, 2 ] ],
// [ [ 1 ], [ 2, 2 ], [ 3, 3, 3 ] ],
// [ [ 1 ], [ 2, 2 ], [ 3, 3, 3 ], [ 4, 4, 4, 4 ] ] ]
// more simple & similar to previous, but without the 2nd xform step
tx.transduce(tx.comp(tx.scan(tx.count), tx.scan(tx.pushCopy)), tx.push, [1,1,1,1])
// [ [ 1 ], [ 1, 2 ], [ 1, 2, 3 ], [ 1, 2, 3, 4 ] ]
f = tx.step(tx.dedupe());
f(1); // 1
f(2); // 2
f(2); // undefined
f(2); // undefined -> skip repetiton
f(3); // 3

@@ -56,11 +93,58 @@ f(3); // undefined

### API
### Types
TODO
Apart from type aliases, the only real types defined are:
#### `reduce<A, B>(rfn: Reducer<A, B>, acc: A, xs: Iterable<B>): A`
#### Reducer
#### `transduce<A, B, C>(tx: Transducer<A, B>, rfn: Reducer<C, B>, acc: C, xs: Iterable<A>): C`
Reducers are the core building blocks of transducers. Unlike other
implementations using OOP approaches, a `Reducer` in this lib is a simple
3-element array of functions, each addressing a separate processing step.
#### `iterator<A, B>(tx: Transducer<A, B>, xs: Iterable<A>): IterableIterator<B>`
```typescript
interface Reducer<A, B> extends Array<any> {
/**
* Initialization, e.g. to provide a suitable accumulator value,
* only called when no initial accumulator has been provided by user.
*/
[0]: () => A,
/**
* Completion. When called usually just returns `acc`, but stateful
* transformers should flush/apply their outstanding results.
*/
[1]: (acc: A) => A,
/**
* Reduction step. Combines new input with accumulator.
* If reduction should terminate early, wrap result via `reduced()`
*/
[2]: (acc: A, x: B) => A | Reduced<A>,
}
// A concrete example:
const push: Reducer<any[], any> = [
// init
() => [],
// completion
(acc) => acc,
// step
(acc, x) => (acc.push(x), acc),
];
```
Currently only `partition` & `partitionBy` make use of the 1-arity completing function.
#### Reduced
```typescript
class Reduced<T> implements IDeref<T> {
protected value: T;
constructor(val: T);
deref(): T;
}
```
Simple type wrapper to identify early termination of a reducer. Does not modify
wrapped value by injecting magic properties. Instances can be created via
`reduced(x)` and handled via these helper functions:
#### `reduced(x: any): any`

@@ -74,2 +158,53 @@

### Transducer
From Rich Hickey's original definition:
> A transducer is a transformation from one reducing function to another
As shown in the examples above, transducers can be dynamically composed (using
`comp()`) to form arbitrary data transformation pipelines without causing large
overheads for intermediate collections.
```typescript
type Transducer<A, B> = (rfn: Reducer<any, B>) => Reducer<any, A>;
// concrete example of stateless transducer (expanded for clarity)
function map<A, B>(fn: (x: A) => B): Transducer<A, B> {
return (rfn: Reducer<any, B>) => {
return [
() => rfn[0](),
(acc) => rfn[1](acc),
(acc, x: A) => rfn[2](acc, fn(x))
];
};
}
// stateful transducer
// removes successive value repetitions
function dedupe<T>(): Transducer<T, T> {
return (rfn: Reducer<any, T>) => {
// stateful initialization
let prev = {};
return [
() => rfn[0](),
(acc) => rfn[1](acc),
(acc, x) => {
acc = prev === x ? acc : rfn[2](acc, x);
prev = x;
return acc;
}
];
};
}
```
### Transformations
#### `reduce<A, B>(rfn: Reducer<A, B>, acc: A, xs: Iterable<B>): A`
#### `transduce<A, B, C>(tx: Transducer<A, B>, rfn: Reducer<C, B>, acc: C, xs: Iterable<A>): C`
#### `iterator<A, B>(tx: Transducer<A, B>, xs: Iterable<A>): IterableIterator<B>`
#### `comp(f1, f2, ...)`

@@ -87,2 +222,12 @@

#### `cat<A>(): Transducer<A[], A>`
#### `flatten<T>(): Transducer<T | Iterable<T>, T>`
#### `flattenOnly<T>(pred: Predicate<T>): Transducer<T | Iterable<T>, T>`
#### `selectKeys(...keys: PropertyKey[]): Transducer<any, any>`
#### `scan<A, B>(rfn: Reducer<B, A>, acc?: B): Transducer<A, B>`
#### `filter<T>(pred: Predicate<T>): Transducer<T, T>`

@@ -122,6 +267,8 @@

#### `chunkSort<T>(n: number, key?: (x) => any): Transducer<T, T>`
#### `chunkSort<T>(n: number, key?: ((x: T) => any), cmp?: Comparator<any>): Transducer<T, T>`
#### `streamSort<T>(n: number, key?: (x) => any): Transducer<T, T>`
#### `streamSort<T>(n: number, key?: ((x: T) => any), cmp?: Comparator<any>): Transducer<T, T>`
#### `streamShuffle<T>(n: number, maxSwaps?: number): Transducer<T, T>`
### Reducers

@@ -131,2 +278,4 @@

#### `pushCopy: Reducer<any[], any>`
#### `conj: Reducer<Set<any>, any>`

@@ -140,4 +289,10 @@

#### `count: Reducer<number, number>`
#### `mul: Reducer<number, number>`
#### `min: Reducer<number, number>`
#### `max: Reducer<number, number>`
#### `frequencies: Reducer<Map<any, number>, any`

@@ -144,0 +299,0 @@

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc