How to join two half in RxJs - javascript

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);

Related

Improve useEffect mutating array objects and get difference beetween

I need help to see if I can improve this code that I have, first I describe the final result that I hope and maybe this way you can find how to improve a little this useEffect:
I need to receive that data object, compare it with one previously stored in localstorage, and compare them to see if there is any new record in that object, if it finds new data, extract them, add the isNew property only to those key that found new and create a new array with the data I received and newArray that includes the modified data.
const localStorageKey = 'laboratoryData'
const oldData = localStorage.getItem(localStorageKey)
useEffect(() => {
if (data) {
if ([null, undefined].includes(oldData)) {
localStorage.setItem(localStorageKey, JSON.stringify(data.data))
} else {
const arrayIsEqual = (data, oldData) => {
data.data === oldData ||
(data.data.length === oldData.length &&
data.data.every(
(f, i) => f.id === oldData[i].id && f.name === oldData[i].name,
))
}
if (arrayIsEqual) {
localStorage.setItem(localStorageKey, JSON.stringify(data.data))
} else {
const diff = data.data
.filter((x) => !oldData.includes(x))
.concat(oldData.filter((x) => !data.data.includes(x)))
const newArray = diff.map((obj) => ({...obj, isNew: 'true'}))
const finalArray = {...data, ...newArray}
localStorage.setItem(localStorageKey, JSON.stringify(finalArray))
}
}
}
}, [data])
Maybe i`m duplicating some code or missing a custom hook with a better performance
Thanks!
without seeing your data and based on your code snippet, hopefully your data.data and oldData are always be arrays of objects that have always have key id and name.
const localStorageKey = 'laboratoryData';
const oldData = localStorage.getItem(localStorageKey);
useEffect(() => {
if (!data) return;
if (!oldData) {
localStorage.setItem(localStorageKey, JSON.stringify(data.data));
return;
}
if (data.data.length === oldData.length && data.data.every((d, i) => d.id === oldData[i].id && d.name === oldData[i].name)) return;
const newData = data.data.filter(d => oldData.findIndex(od => od.id === d.id && od.name === d.name) === -1).map(d => d["isNew"] = true);
const finalArray = oldData.map(od => od["isNew"] = false).concat(newData);
localStorage.setItem(localStorageKey, JSON.stringify(finalArray));
}, [data])

How to join two arrays in a map function?

What I want is to join my arrays that I get from my map function.
I tried to do with reduce :
const onFinish = (values) => {
values.fields.map((el, idx) => {
console.log({ ...el, index: idx }); // this `console.log` prints me one array after the other
}).reduce((acc, x) => {console.log(acc,x)}); // I tried with `reduce` but it prints me null
Also tried to do with useState:
const onFinish = (values) => {
values.fields.map((el, idx) => {
if (myArray === null){
setMyArray(() => {
return { ...el, index: idx };
});
}
else {
setMyArray(() => {
return {...myArray,...el, index: idx };
});
}
});
console.log(myArray) // at my first call to this const onFinish myArray is printed as [] and at the second it prints with the last object only
};
Someone knows how to get these objects joined/merged ?
Sample data the way that it's print:
How I want to be:
Altghough it is still not very clear from the screenshots (always prefer to use text and not images for data/code), it looks like you want
const onFinish = (values) => {
const updatedValues = values.fields.map((field, index) => ({...field, index}));
console.log( updatedValues );
}
Does this help?
if (myArray === null){
setMyArray(() => {
return [{ ...el, index: idx }];
});
}
else {
setMyArray(() => {
return [...myArray, {...el, index: idx }];
});
}

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'))

Is there a better way to achieve this?

I am using React. On click of a button, the following function is executed:
const completeTaskHandler = (idValue) => {
setData((prevData) => {
const updatedData = [...prevData];
const updatedItem = updatedData.filter((ele) => ele.id === idValue)[0];
updatedItem.completed = true;
const newData = updatedData.filter((ele) => ele !== updatedItem);
newData.unshift(updatedItem);
return newData;
});
};
My data is an array of objects like this:
[{userId: 1, id: 2, title: "task 1", completed: true}, .....].
Basically I want to move the updated item to the start of the array. Is there any better solution for this?
updatedItem should not be mutated. And this string const newData = updatedData.filter((ele) => ele !== updatedItem); is not fine. You can do it like this :
const completeTaskHandler = (idValue) => {
setData((prevData) => {
const targetItem = prevData.find((ele) => ele.id === idValue);
const updatedItem = { ...targetItem, completed: true };
const filteredData = prevData.filter((ele) => ele.id !== idValue);
return [updatedItem, ...filteredData];
});
};
Even better to reducing an extra filter:
const completeTaskHandler = (idValue) => {
setData((prevData) => {
const targetIndex = prevData.findIndex((ele) => ele.id === idValue);
return [{ ...prevData[targetIndex], completed: true }].concat(prevData.slice(0, targetIndex + 1)) .concat(
prevData.slice(targetIndex + 1)
)
});
};
First find index of updated element using Array.findIndex(), then remove the same element using Array.splice() and add it to front of the array.
const completeTaskHandler = (idValue) => {
setData((prevData) => {
const updatedData = [...prevData];
const index = updatedData.findIndex(obj => obj.id === idValue);
const [updatedItem] = updatedData.splice(index, 1);
updatedItem.completed = true;
updatedData.unshift(updatedItem);
return updatedData;
});
};
The simplest one with only one forEach.
const completeTaskHandler = idValue => {
setData(prevData => {
let updatedItem = {}, newData = [];
prevData.forEach((ele) => {
if (ele.id === idValue) {
updatedItem = ele;
updatedItem.completed = true;
} else {
newData.push(ele);
}
});
newData.unshift(updatedItem);
return newData;
});
};

Categories

Resources