Chaining and Function Composition - javascript

Here is a piece of code I am trying to understand:
const seq2 = (f1, f2) => {
return (...args) => {
return f2( f1( ...args) );
}
}
const seq = ( f1, ...fRest) =>
fRest.reduce( seq2, f1 );
const elevator = {
floor: 5
};
const up = elevator => {
return {
floor: elevator.floor + 1
}
};
const down = elevator => {
return {
floor: elevator.floor - 1
}
};
const move = seq( up, up, down, up);
const newElevator = move( elevator );
console.log( newElevator.floor ) // shows 7
this is an example from js functional programming course. And I am trying to figure out if I could simplify seq function so it would look like this
const seq = ( ...fRest) =>
fRest.reduce( seq2 );
????
Is there any specific reason why I have to pass f1 as a first argument and then pass it further to the reduce method as an initialValue ?? When I won't specify the initialValue in the reduce method wouldn't it treat the first array element - accumulator - as an initialValue by default? I will much appreciate if someone could explain to me what is going on in that code :)

seq() would attempt to reduce an empty array without an accumulator, which is an error – not that f1 fixes this the way the author has it written here.
initialValue – Value to use as the first argument to the first call of the callback. If no initial value is supplied, the first element in the array will be used. Calling reduce() on an empty array without an initial value is an error – source MDN: Array.prototype.reduce
A more robust implementation of seq will not cause an error when seq() is used to build an empty sequence
const identity = x =>
x
const seq2 = (f, g) =>
(...args) => f (g (...args))
const seq = (...fs) =>
fs.reduce (seq2, identity)
const up = elevator =>
({ floor: elevator.floor + 1 })
const down = elevator =>
({ floor: elevator.floor - 1 })
console.log
( seq (up, up, down, up) ({ floor: 3 }) // { floor: 5 }
, seq () ({ floor: 3 }) // { floor: 3 }
)
A simplified version of seq that promotes better functional hygiene by disallowing composition of variadic functions
const identity = x =>
x
const seq = (f = identity, ...rest) =>
f === identity
? f
: x => seq (...rest) (f (x))
const up = elevator =>
({ floor: elevator.floor + 1 })
const down = elevator =>
({ floor: elevator.floor - 1 })
console.log
( seq (up, up, down, up) ({ floor: 3 }) // { floor: 5 }
, seq () ({ floor: 3 }) // { floor: 3 }
)

Related

Javascript declarative/immutable version of 'maybe push'?

I have code like this:
A = [1,2,3]
if (condition) {A.push(4)}
A.push(5)
But my array is actually immutable so I can't do that. How can I do it all in one expression? I don't want a null value in the middle.
Here is one way:
A = [1, 2, 3, ...(condition ? [4] : []), 5]
If this is common in your codebase, and you want to keep undefined then you write a filter function with a sentinel.
const REMOVE = symbol('removeme')
const A = clean([1, 2, 3, condition ? 4 : REMOVE, 5])
function clean(arr) {return arr.filter(x=>x!==REMOVE)}
You can use the Writer monad here.
// type Writer t a = { value :: a, array :: [t] }
// pure :: a -> Writer t a
const pure = (value) => ({ value, array: [] });
// bind :: (Writer t a, a -> Writer t b) -> Writer t b
const bind = (writerA, arrow) => {
const writerB = arrow(writerA.value);
const array = [...writerA.array, ...writerB.array];
return { value: writerB.value, array };
};
// tell :: [t] -> Writer t ()
const tell = (array) => ({ value: undefined, array });
const writer = (gen) => {
const next = (data) => {
const { value, done } = gen.next(data);
return done ? value : bind(value, next);
};
return next(undefined);
};
const example = (condition) => writer(function* () {
yield tell([1, 2, 3]);
if (condition) yield tell([4]);
return tell([5]);
}());
console.log(example(true).array); // [1,2,3,4,5]
console.log(example(false).array); // [1,2,3,5]

Call a function with many arguments in JavaScript

I have a compose function what should be called with n number of function, depending by what function was added as parameter.
For example:
const props = ['link', 'letter'];
const mapp = {
letter: (v) => {
v + 'letter'
},
link: (v) => {
v + 'nr'
}
}
const compose = (...fns) =>
fns.reduce(
(prevFn, nextFn) =>
(...args) =>
nextFn(prevFn(...args)),
(value) => value,
);
const res = compose(props);
console.log(res('test'))
So, i expect, if the const props = ['link', 'letter']; the compose function should be called like: const res = compose(mapp[letter], mapp[link]);, or if const props = ['letter'];, the compose function should be called as: compose(mapp[letter]). At the moment the code does not work as expeted. Question: How to fix the code and to get the expected result?
There was 2 problems with your code
Missing return statement(s) in the mapp object's functions (or remove the { and } - see below)
You needed to map the string array into references to the functions and spread (...) the result when calling compose
Working example:
const props = ['link', 'letter'];
const mapp = {
letter: (v) => {
return v + 'letter'
},
link: (v) => {
return v + 'nr'
}
}
const compose = (...fns) =>
fns.reduce(
(prevFn, nextFn) => (...args) => nextFn(prevFn(...args)),
(value) => value,
);
const res = compose(...props.map(p => mapp[p]));
console.log(res('test'))
Regarding 1. above that object could be written as below:
const mapp = {
letter: (v) => v + 'letter',
link: (v) => v + 'nr'
}
Edit: The need to destructure the call to map is because of the way you defined compose to take discrete parameters rather than an array. ie, this would also work (and not need to destructure map)
const props = ['link', 'letter'];
const mapp = {
letter: (v) => {
return v + 'letter'
},
link: (v) => {
return v + 'nr'
}
}
const compose = (fns) => /// compose now takes an array of functions
fns.reduce(
(prevFn, nextFn) => (...args) => nextFn(prevFn(...args)),
(value) => value,
);
const res = compose(props.map(p => mapp[p]));
console.log(res('test'))
I do not see how you are using the object with the functions and you are not returning correctly in a few places. It should look something like
const props = ['link', 'letter'];
const mapp = {
letter: (v) => v + 'letter',
link: (v) => v + 'nr',
}
const compose = (obj, fns) => (...orgArgs) =>
fns.reduce((args, fnName) => obj[fnName].apply(obj, [args]), orgArgs);
const res = compose(mapp, props);
console.log(res('test'))

Cant understand what is happening in this compose reduce example in javascript?

var user1 = {
name: 'Nady',
active: true,
cart: [],
purchase: [],
};
var compose = function test1(f, g) {
return function test2(...args) {
return f(g(...args));
};
};
function userPurchase(...fns) {
return fns.reduce(compose);
}
userPurchase(
empty,
addItemToPurchase,
applayTax,
addItemToCart
)(user1, { name: 'laptop', price: 876 });
function addItemToCart(user, item) {
return { ...user, cart: [item] };
}
function applayTax(user) {
var { cart } = user;
var taxRate = 1.3;
var updatedCart = cart.map(function updateCartItem(item) {
return { ...item, price: item.price * taxRate };
});
return { ...user, cart: updatedCart };
}
function addItemToPurchase(user) {
return { ...user, purchase: user.cart };
}
function empty(user) {
return { ...user, cart: [] };
}
I don't understand this example well. I tried stepping through it with the debugger and concluded the following:
When I call function userPurchase the reduce will work and at its end f will be test2 and g will be addItemToCart then test2 is returned as the accumulated. Then we call it passing (user1, { name: 'laptop', price: 876 }) as arguments and g is called in it that is addItemToCart.
I don't understand how g is changed to applayTax, then addItemToPurchase, then empty every time function test2 call itself.
How or why this is happening?
The thing that may have gotten you confused is taking the term accumulator literally. By convention that's the name of the first argument to a reducer. But it's not necessary to use it to accumulate a value. In this case it is used to compose a series of functions.
The real meaning of the first argument to a reducer is previouslyReturnedValue:
function compose(previouslyReturnedValue, g) {
return function (...args) {
return previouslyReturnedValue(g(...args));
};
}
So let's walk through this loop:
[empty, addItemToPurchase, applayTax, addItemToCart].reduce(
(f,g) => {
return (...args) => {
return f(g(...args));
}
}
);
The first round of the loop, f = empty and g = addItemToPurchase. This will cause compose to return:
return (...args) => {
return empty(addItemToPurchase(...args));
}
Leaving the array to become: [applayTax, addItemToCart]
The second round of the loop f = (...args) => {return empty(addItemToPurchase(...args))} and g = applyTax. This will cause compose to return:
return (...args) => {
return empty(addItemToPurchase(applyTax(...args)));
}
We continue with this logic until we finally get compose to return the full chain of functions:
return (...args) => {
return empty(addItemToPurchase(applyTax(addItemToCart(...args))));
}
Alternate view of the same process
If the above is a bit hard to follow then let's name the anonymous function that becomes f in each loop.
In the first round we get:
function f1 (...args) {
return empty(addItemToPurchase(...args));
}
In the second round we get:
function f2 (...args) {
return f1(applyTax(...args));
}
In the final round we get:
function f3 (...args) {
return f2(addItemToCart(...args));
}
It is this function f3 that is returned by reduce(). So when you call the return value of reduce it will try to do:
f2(addItemToCart(...args))
Which will call addItemToCart() and then call f2 which will execute:
f1(applyTax(...args))
Which will call applyTax() and then call f1 which will execute:
empty(addItemToPurchase(...args))
Which will call addItemToPurchase() and then call empty()
TLDR
Basically all this is doing:
let tmp;
tmp = addItemToCart(args);
tmp = applyTax(tmp);
tmp = addItemToPurchase(tmp);
tmp = empty(tmp);
More readable version
There is a way to implement this logic which is more readable and easier to understand if we abandon reduce(). I personally like the functional array methods like map() and reduce() but this is one of those rare situations where a regular for loop may lead to much more readable and debuggable code. Here's a simple alternative implementation that does exactly the same thing:
function userPurchase(...fns) {
return function(...args) {
let result = args;
// The original logic apply the functions in reverse:
for (let i=fns.length-1; i>=0; i--) {
let f = fns[i];
result = f(result);
}
return result;
}
}
Personally I find the implementation of userPurchase using a for loop much more readable than the reduce version. It clearly loops through the functions in reverse order and keep calling the next function with the result of the previous function.
Does this help at all?
var user1 = {
name: 'Nady',
active: true,
cart: [],
purchase: [],
};
function compose(f, g) {
const composition = function(...args) {
console.log('f name', f.name);
console.log('g name', g.name);
return f(g(...args));
};
Object.defineProperty(composition, 'name', {
value: 'composition_of_' + f.name + '_and_' + g.name,
writable: false
});
return composition;
};
function userPurchase(...fns) {
return fns.reduce(compose);
}
function addItemToCart(user, item) {
return { ...user, cart: [item] };
}
function applayTax(user) {
var { cart } = user;
var taxRate = 1.3;
var updatedCart = cart.map(function updateCartItem(item) {
return { ...item, price: item.price * taxRate };
});
return { ...user, cart: updatedCart };
}
function addItemToPurchase(user) {
return { ...user, purchase: user.cart };
}
function empty(user) {
return { ...user, cart: [] };
}
const result = userPurchase(
empty,
addItemToPurchase,
applayTax,
addItemToCart
)(user1, { name: 'laptop', price: 876 });
console.log(result);
Let's say you need to take in a number, add ten to it and then double it. You could write some functions like:
const add_ten = function(num) { return num + 10; };
const double = function(num) { return num * 2; };
const add_ten_and_double = function(num) { return double(add_ten(num)); };
You could also write the same thing as:
function compose(outer_function, inner_function) {
return function(num) {
return outer_function(inner_function(num));
};
};
const add_ten = function(num) { return num + 10; };
const double = function(num) { return num * 2; };
const add_ten_and_double = compose(double, add_ten);
console.log('typeof add_ten_and_double:', typeof add_ten_and_double);
console.log('add_ten_and_double:', add_ten_and_double(4));
Using compose, we've created a function that does the same thing as our original add_ten_and_double function. Does that make sense up to here? (Point A).
If we then decided to add five we could have:
function compose(outer_function, inner_function) {
return function(num) {
return outer_function(inner_function(num));
};
};
const add_ten = function(num) { return num + 10; };
const double = function(num) { return num * 2; };
const add_five = function(num) { return num + 5; };
const add_ten_and_double_and_add_five = compose(compose(add_five, double), add_ten);
console.log('typeof add_ten_and_double_and_add_five :', typeof add_ten_and_double_and_add_five);
console.log('add_ten_and_double_and_add_five :', add_ten_and_double_and_add_five(4));
Now we've run compose using a function and a function that was composed of two other functions, but what we've got back is still just a function that takes a number and returns a number. Does that make sense up to here? (Point B).
If we wanted to add a few more functions then we would end up with a lot of compose calls in our code, so we could just say "give me the composition of all of these functions", and it might look like:
function compose(outer_function, inner_function) {
return function(num) {
return outer_function(inner_function(num));
};
};
const add_ten = function(num) { return num + 10; };
const double = function(num) { return num * 2; };
const add_five = function(num) { return num + 5; };
functions_to_compose = [add_five, double, add_ten];
let composition;
functions_to_compose.forEach(function(to_compose) {
if(!composition)
composition = to_compose;
else
composition = compose(composition, to_compose);
});
const add_ten_and_double_and_add_five = composition;
console.log('typeof add_ten_and_double_and_add_five:', typeof add_ten_and_double_and_add_five);
console.log('add_ten_and_double_and_add_five:', add_ten_and_double_and_add_five(4));
The fns.reduce(compose); in userPurchase in your code is basically doing the same thing as the forEach loop here, but neater. Does that make sense why add_ten_and_double_and_add_five is a function that you can pass in a number and all of the operations from functions_to_compose are being applied (last to first) to the number? (Point C).
Renaming
Part of the problem is simply in naming. It would help to introduce one more function, and to rename two others.
var composeTwo = function test1(f, g) {
return function test2(...args) {
return f(g(...args));
};
};
function composeMany(...fns) {
return fns.reduce(composeTwo);
}
const userPurchase = composeMany(
empty,
addItemToPurchase,
applyTax,
addItemToCart
)
userPurchase(user1, { name: 'laptop', price: 876 });
//=> {active: true, cart: [], name: "Nady", purchase: [{name: "laptop", price: 1138.8}]}
// other functions elided
composeTwo (originally called compose) is a function that accepts two functions and returns a new one which accepts some input, calls the second function with that input and then calls the first function with the result. This is straightforward mathematical composition of functions.
composeMany (which as originally called -- very confusingly -- userPurchase) extends this composition to work on a list of functions, using reduce to sequentially call each function on the result of the pipeline so far, starting with the arguments passed. Note that it works from the last item in the list to the first one.
We use this to define the new userPurchase, which passes empty, addItemToPurchase, applyTax and addItemToCart to pipeline. This returns a function that will then apply them in sequence, doing something equivalent to function (...args) {return empty1(addItemToPurchase(applyTax(addItemToCart(...args))))}
We can see this in action in this snippet:
var user1 = {
name: 'Nady',
active: true,
cart: [],
purchase: [],
};
var composeTwo = function test1(f, g) {
return function test2(...args) {
return f(g(...args));
};
};
function composeMany (...fns) {
return fns.reduce(composeTwo);
}
const userPurchase = composeMany (
empty,
addItemToPurchase,
applyTax,
addItemToCart
)
console .log (
userPurchase(user1, { name: 'laptop', price: 876 })
)
function addItemToCart(user, item) {
return { ...user, cart: [item] };
}
function applyTax(user) {
var { cart } = user;
var taxRate = 1.3;
var updatedCart = cart.map(function updateCartItem(item) {
return { ...item, price: item.price * taxRate };
});
return { ...user, cart: updatedCart };
}
function addItemToPurchase(user) {
return { ...user, purchase: user.cart };
}
function empty(user) {
return { ...user, cart: [] };
}
.as-console-wrapper {max-height: 100% !important; top: 0}
Modern Syntax
However, I would find this much cleaner with a more modern JS syntax. This is very much equivalent, but cleaner:
const composeTwo = (f, g) => (...args) =>
f (g (...args))
const composeMany = (...fns) =>
fns .reduce (composeTwo)
// .. other functions elided
const userPurchase = composeMany (
empty,
addItemToPurchase,
applyTax,
addItemToCart
)
Here it should be obvious that composeTwo takes two functions and returns a function. And in knowing that and understanding .reduce, it should be clear that composeMany takes a list of functions and returns a new function. This version, which also applies this change to the remaining functions is available in this snippet:
var composeTwo = (f, g) => (...args) =>
f (g (...args))
const composeMany = (...fns) =>
fns .reduce (composeTwo)
const addItemToCart = (user, item) =>
({ ...user, cart: [item] })
const applyTax = (user) => {
var { cart } = user;
var taxRate = 1.3;
var updatedCart = cart .map (item => ({ ...item, price: item .price * taxRate }))
return { ...user, cart: updatedCart };
}
const addItemToPurchase = (user) =>
({ ...user, purchase: user.cart })
const empty = (user) =>
({ ...user, cart: [] })
const userPurchase = composeMany (
empty,
addItemToPurchase,
applyTax,
addItemToCart
)
const user1 = {
name: 'Nady',
active: true,
cart: [],
purchase: [],
};
console .log (
userPurchase (user1, { name: 'laptop', price: 876 })
)
.as-console-wrapper {max-height: 100% !important; top: 0}
How reduce and composeTwo work together
Here we try to demonstrate how reduce works with composeTwo to compose multiple functions into one.
In the first step, the init parameter to reduce is missing, so JS uses the first value in the array as the initial one, and starts iterating with the second one. So reduce first calls composeTwo with empty and addItemToPurchase, yielding a function equivalent to
(...args) => empty (addItemsToPurchase (...args))
Now reduce passes that function and applyTax to compose, yielding a function like
(...args) => ((...args2) => empty (addItemsToPurchase (...args2))) (applyTax (...args))
Now this is structured like the following:
(x) => ((y) => f ( g (y)) (h (x))
where x represents ...args, y represents ...args2, f represents empty, g represents addItems, and h represents applyTax.
but the right-hand side is a function ((y) => f ( g ( y))) with the value h (x) applied to it. This is the same as replacing y in the body with h(x), yielding f (g (h (x))), so that this function is equivalent to (x) => f (g (h (x))), and by replacing our original values, this ends up as
(...args) => empty (addItemsToPurchase (applyTax ( ...args)))
Do note that this application of values to the function does not happen now when the function is being built. It will happen when the resulting function is called. In memory, this is still something like (...args) => ((...args2) => empty (addItems (...args2))) (applyTax (...args)). But this logical version shows how it will work.
Of course we now do it again for addItemToCart:
(...args) => ((...args2) => empty (addItemsToPurchase (applyTax ( ...args2)))) (addItemToCart (...args))
and by the same sort of application, we get the equivalent of
(...args) => empty (addItems (applyTax ( addItemToCart (...args))))
Which is the basic definition of the composition of those functions.
Cleaning up the tax rate
There's something strange about the hard-coded tax rate. We can fix that by making a parameter used in calling userPurchase. This also allows us to clean up the applyTax function:
const applyTax = (taxRate) => ({cart, ...rest}) => ({
... rest,
cart: cart .map (item => ({ ...item, price: item .price * taxRate }))
})
const userPurchase = (taxRate) => composeMany (
empty,
addItemToPurchase,
applyTax(taxRate),
addItemToCart
)
// ...
userPurchase (1.3) (user1, { name: 'laptop', price: 876 })
Note that the curried nature of this parameter lets us choose to apply just this value to get back a tax-rate specific function:
const someDistrictPurchase = userPurchase (1.12) // 12% tax
someDistrictPurchase(user, item)
Which we can see in one more snippet:
var composeTwo = (f, g) => (...args) =>
f (g (...args))
const composeMany = (...fns) =>
fns .reduce (composeTwo)
const addItemToCart = (user, item) =>
({ ...user, cart: [item] })
const applyTax = (taxRate) => ({cart, ...rest}) => ({
... rest,
cart: cart .map (item => ({ ...item, price: item .price * taxRate }))
})
const addItemToPurchase = (user) =>
({ ...user, purchase: user.cart })
const empty = (user) =>
({ ...user, cart: [] })
const userPurchase = (taxRate) => composeMany (
empty,
addItemToPurchase,
applyTax(taxRate),
addItemToCart
)
var user1 = { name: 'Nady', active: true, cart: [], purchase: []}
console .log (
userPurchase (1.3) (user1, { name: 'laptop', price: 876 })
)
.as-console-wrapper {max-height: 100% !important; top: 0}
Lessons
Function composition is an essential part of functional programming (FP). While it helps to have functions like composeTwo and composeMany, it is much better if they have understandable names. (Note that compose is a perfectly legitimate name for the first one. My change here was just to make the distinction with composeMany clearer.) The biggest problem, to my mind was the original name of userPurchase as a composition function. That confuses a lot of things.
Modern JS (arrow functions, rest/spread, and destructuring) makes for code that is not only more succinct but generally easier to understand.

Map on Javascript object values without extra variable?

I'm using the following piece of code (which is working fine)
const result = {}
Object.keys(timers).forEach(key => {
result[key] = hydrate(timers[key])
})
return result
}
I'm wondering if this is possible in one method? So without having to fill the result object?
Convert to entries with Object.entries(), iterate the entries with Array.map() and hydrate the values, and convert back to an object with Object.fromEntries():
const fn = timers => Object.fromEntries(
Object.entries(timers).map(([k, v]) => [k, hydrate(v)])
)
Just use reduce
var timers = {
a: 2,
b: 3,
c: 4
}
const hydrate = x => 2*x
var result = Object.entries(timers).reduce((o, [key, value]) => {
o[key] = hydrate(value)
return o
}, {})
console.log(result)
without fat arrow
var timers = {
a: 2,
b: 3,
c: 4
}
function hydrate (x) { return 2 * x }
var result = Object.entries(timers).reduce(function(o, entry) {
o[entry[0]] = hydrate(entry[1])
return o
}, {})
console.log(result)

How to join two half in RxJs

I have a stream like this
---ab---ab---a---ba---bab---ab---ab---ab--->
And I want this.
---ab---ab------ab----ab-ab-ab---ab---ab--->
The point is, that I have data with beginning and end (JSON) and sometimes the data is cut in the half in the stream, and I want to join them again. How can I do that?
Looks like a job for the scan operator
// substitute appropriate real-world logic
const isProperlyFormed = (x) => x === 'ab'
const isIncomplete = (x) => x[0] === 'a' && x.length === 1
const startsWithEnding = (x) => x[0] === 'b'
const getCorrected = (buffer, x) => buffer.prev + x[0]
const getTail = (buffer, x) => x.slice(1)
const initialBuffer = {
emit: [],
prev: null
}
const result = source
.scan((buffer, x) => {
if (isProperlyFormed(x)) {
buffer = {emit: [x], prev:null}
}
if (isIncomplete(x)) {
buffer = {emit: [], prev:x}
}
if (startsWithEnding(x)) {
const corrected = getCorrected(buffer, x)
const tail = getTail(buffer, x)
if (isProperlyFormed(tail)) {
buffer = {emit: [corrected, tail], prev: null}
} else {
buffer = {emit: [corrected], prev: tail}
}
}
return buffer
}, initialBuffer)
.flatMap(x => x.emit)
Working CodePen
Edit
Looking at the test input stream, I think a case is missing, which will break the above.
I changed the test from
---ab---ab---a---ba---bab---ab---ab---ab--->
to
---ab---ab---a---ba---bab---aba---b---ab--->
and also slimmed down the algorithm
const getNextBuffer = (x) => {
const items = x.split(/(ab)/g).filter(y => y) // get valid items plus tail
return {
emit: items.filter(x => x === 'ab'), // emit valid items
save: items.filter(x => x !== 'ab')[0] // save tail
}
}
const initialBuffer = {
emit: [],
save: null
}
const result = source
.scan((buffer, item) => {
const bufferAndItem = (buffer.save ? buffer.save : '') + item
return getNextBuffer(bufferAndItem)
}, initialBuffer)
.flatMap(x => x.emit)
Working example CodePen
First split the stream into full responses and partial. Then check if response is full. Full responses are good as such. Partial responses need to be synchronized, so we split their stream into first and second halves and just zip those streams together.
The strange looking Rx.Observable.of(g.partition(x => x[0] === 'a')) is because partition operator returns pair of observables, which cannot be chained.
const testStream = Rx.Observable.of('a1', 'a2', '_ab', 'b1', 'a3', 'b2', '_ab', 'a4', 'b3', '_ab', 'b4', 'a5', 'b5', '_ab')
testStream
.groupBy(x => (x[0] === '_' && 'full') || 'partial')
.mergeMap(g =>
Rx.Observable.if(
() => g.key == 'full',
g,
Rx.Observable.of(g.partition(x => x[0] === 'a'))
.mergeMap(([as, bs]) => Rx.Observable.zip(as, bs))
)
)
.do(x => console.log(x))
.subscribe()
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.4.3/Rx.min.js"></script>
This is how I solved:
import Rx from 'rxjs/Rx';
import {last} from 'lodash';
const data$ = Rx.Observable.of('ab','ab','a','ba','bab','aba','b','ab');
const line$ = data$.flatMap(data => {
const lines = data.match(/[^b]+b?|b/g); // https://stackoverflow.com/a/36465144/598280 https://stackoverflow.com/a/25221523/598280
return Rx.Observable.from(lines);
});
const isComplete$ = line$.scan((acc, value) => {
const isLineEndingLast = last(acc.value) === 'b';
const id = isLineEndingLast ? acc.id + 1 : acc.id;
const complete = last(value) === 'b';
return {value, id, complete};
}, {value: 'b', id: 0, complete: true});
const grouped$ = isComplete$
.groupBy(data => data.id, data => data, group => group.first(data => data.complete))
.flatMap(group => group.reduce((acc, data) => acc + data.value, ''));
grouped$.subscribe(console.log);

Categories

Resources