Skip to content

Commit

Permalink
flowReduce
Browse files Browse the repository at this point in the history
More abstract universal
[foldWhile](#58),
for infinite reducers.
  • Loading branch information
sadasant committed Mar 15, 2017
1 parent ab6b15d commit 753c998
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 22 deletions.
12 changes: 5 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# futil
# futil
[![Greenkeeper badge](https://badges.greenkeeper.io/smartprocure/futil-js.svg)](https://greenkeeper.io/)
[![npm version](https://badge.fury.io/js/futil-js.svg)](https://badge.fury.io/js/futil-js)
![dependencies](https://david-dm.org/smartprocure/futil-js.svg)
Expand Down Expand Up @@ -158,9 +158,7 @@ nested Arrays and Plain Objects. Also works for any other iterable data type as
two other values are sent: a mapping function, and a type checker (See the
unit tests for deepMap).

## foldWhile
`foldWhile :: (a -> b) -> [a] -> [b]`
Folds a recursive iterable by a given function which receives
an accumulator, a value and a key. Returns the accumulator
when the given function returns false at any point.
Works just like haskell's `fold takeWhile`.
## flowReduce
`flowReduce :: (a -> b ... -> c) -> [a] -> [b]`
Recursive reduce nested objects of any kind that
stops at any point if a falsy value is returned.
24 changes: 18 additions & 6 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,24 @@ export const map = _.curry((f, x) => (_.isArray(x) ? _.map : _.mapValues)(f, x))
export const deepMap = _.curry((fn, obj, _map = map, is = isTraversable) =>
_map(e => is(e) ? deepMap(fn, fn(e), _map, is) : e, obj))

// Recursive fold takeWhile (like in haskell)
export const foldWhile = _.curry((fn, obj, r = []) => {
let innerFold = o => !isTraversable(o) ? o : _.every(k => fn(r, o[k], k) && innerFold(o[k]), _.keys(o))
innerFold(obj)
return r
})
// Recursive reduce nested objects of any kind that
// stops at any point if a falsy value is returned.
export const flowReduce = (...fns) => (obj, acc = []) => {
if (typeof obj !== 'object') return acc
let _acc = acc
let type = typeof acc
// eslint-disable-next-line lodash-fp/no-unused-result
_.every(k => {
let cont = _.every(f => {
let r = f(_acc, obj[k], k)
if (typeof r === type) _acc = r
return r
}, fns)
if (cont) _acc = flowReduce(...fns)(obj[k], _acc)
return cont
}, _.keys(obj))
return _acc
}

// Misc
// ----
Expand Down
34 changes: 25 additions & 9 deletions test/algebras.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,11 @@ describe('Algebras', () => {
})
})

it('foldWhile simple test', () => {
it('flowReduce with integer accumulator', () => {
expect(f.flowReduce(acc => acc < 3, acc => ++acc)([ 1, 2, 3, 4 ], 0)).to.equal(3)
})

it('flowReduce simple test', () => {
const target = {
N1: [ {
N2: [ { N21: [ 210 ], N22: [ 220 ] } ],
Expand All @@ -145,18 +149,21 @@ describe('Algebras', () => {
}

const keyToInt = k => parseInt(k.slice(1))
const pushIfPair = (k, r) => (k % 2 === 0) && r.push(k) || true
const concatIfPair = (k, r) => (k % 2 === 0) ? r.concat(k) : r

let pairKeys = f.foldWhile((r, v, k) => pushIfPair(keyToInt(k), r), target)
let pairKeys = f.flowReduce((r, v, k) => concatIfPair(keyToInt(k), r))(target)

expect(pairKeys).to.deep.equal([ 2, 22, 4, 42, 6, 62, 8, 82, 32, 52, 72, 92 ])

pairKeys = f.foldWhile((r, v, k) => pushIfPair(keyToInt(k), r) && r.length < 4, target)
pairKeys = f.flowReduce(
r => r.length < 4,
(r, v, k) => concatIfPair(keyToInt(k), r)
)(target)

expect(pairKeys).to.deep.equal([ 2, 22, 4, 42 ])
})

it('foldWhile nested objects', () => {
it('flowReduce nested objects', () => {
const target = {
something: {
mostly_empty: {}
Expand All @@ -170,14 +177,17 @@ describe('Algebras', () => {
}
}

let found = f.foldWhile((r, v, k) => k === 'matching' ? r.push({ [k]: v }) && r.length < 2 : true, target)
let found = f.flowReduce(
r => r.length < 2,
(r, v, k) => k === 'matching' ? r.concat({ [k]: v }) : r
)(target)

expect(found).to.deep.equal([{
matching: { key: 'value' }
}])
})

it('foldWhile nested objects, ignoring fields', () => {
it('flowReduce nested objects, ignoring fields', () => {
const target = {
something: {
mostly_empty: {}
Expand Down Expand Up @@ -212,7 +222,10 @@ describe('Algebras', () => {
}
}

let found = f.foldWhile((r, v, k) => (k === 'matching' && v && !v.ignore) ? r.push({ [k]: v }) && r.length < 2 : true, target)
let found = f.flowReduce(
r => r.length < 2,
(r, v, k) => (k === 'matching' && v && !v.ignore) ? r.concat({ [k]: v }) : r
)(target)

expect(found).to.deep.equal([{
matching: { key: 'value' }
Expand All @@ -224,7 +237,10 @@ describe('Algebras', () => {
v.matching.modified = true
}

found = f.foldWhile((r, v, k) => (k === 'matching' && v && !v.ignore) ? r.push({ [k]: v }) && r.length < 2 : true, target)
found = f.flowReduce(
r => r.length < 2,
(r, v, k) => (k === 'matching' && v && !v.ignore) ? r.concat({ [k]: v }) : r
)(target)

expect(found).to.deep.equal([{
matching: {
Expand Down

0 comments on commit 753c998

Please sign in to comment.