Transducers
There's heaps of answers here to help you reach your answer in a practical way – filter
this, map
that, and voilà, the result you're looking for. There's other answers using primitive for
loops, but those make you sad.
So you're wondering, "is it possible to filter and map without iterating through the array more than once?" Yes, using transducers.
Runnable demo
I might update this paragraph with more code explanation if necessary. ES6 comin' at you …
// Trans monoid
const Trans = f => ({
runTrans: f,
concat: ({runTrans: g}) =>
Trans(k => f(g(k)))
})
Trans.empty = () =>
Trans(k => k)
const transduce = (t, m, i) =>
i.reduce(t.runTrans((acc, x) => acc.concat(x)), m.empty())
// complete Array monoid implementation
Array.empty = () => []
// transducers
const mapper = f =>
Trans(k => (acc, x) => k(acc, f(x)))
const filterer = f =>
Trans(k => (acc, x) => f(x) ? k(acc, x) : acc)
const logger = label =>
Trans(k => (acc, x) => (console.log(label, x), k(acc, x)))
// your function, implemented with transducers
const foo = o => {
const t = logger('filtering')
.concat(filterer(k => o[k] !== null))
.concat(logger('mapping'))
.concat(mapper(k => ({ [k]: o[k] })))
.concat(logger('result'))
return transduce(t, Array, Object.keys(o))
}
console.log(foo({a: null, b: 2, c: 3}))
Output; notice the steps appear interlaced – filtering, mapping, result, repeat – this means each of the combined transducers run for each iteration of the input array. Also notice how because a
's value is null
, there is no mapping or result step for a
; it skips right to filtering b
– all of this means we only stepped thru the array once.
// filtering a
// filtering b
// mapping b
// result { b: 2 }
// filtering c
// mapping c
// result { c: 3 }
// => [ { b: 2 }, { c: 3 } ]
Finishing up
Of course that foo
function has lots of console.log
stuff tho. In case it's not obvious, we just want to remove the logger
transducers for our actual implementation
const foo = o => {
const t = filterer(k => o[k] !== null)
.concat(mapper(k => ({ [k]: o[k] })))
return transduce(t, Array, Object.keys(o))
}
console.log(foo({a: null, b: 2, c: 3}))
// => [ {b: 2}, {c: 3} ]
Attribution
My enlightenment on the subject is owed exclusively to Brian Lonsdorf and accompanying work: Monoidal Contravariant Functors Are Actually Useful