Javascript declarative/immutable version of 'maybe push'? - javascript

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]

Related

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

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)

ES6 iterate contain const

Can someone explain why i get error in this code?btw im still new in es6, thanks
selData.map((item,idx)=>({
const TargetItem = aclEntries.findIndex(rec=>rec.stakeholder_id===item.value)
console.log(TargetItem)
}))
ES6 functions can be written a few different ways.
// If you have more than 1 param, you have to wrap them with `()`
const functionName = (param1, param2) => { return console.log(param1, param2); }
// If you're just returning a value, you can omit the `{}`
const functionName = (param1, param2) => console.log(param1, param2);
// If you only have 1 param, you can omit the `()` around the params
const functionName = param1 => console.log(param);
I added another example into the code below, .find(), which will give you the results of aclEntries, not just the index position.
Also, I'm using forEach instead of map, as map creates a new array, and you're not assigning the return value of map to a variable for later use.
const aclEntries = [
{ stakeholder_id: '1234', taco: 'cat' }
];
const selData = [
{ value: '1234' }
];
selData.forEach((item) => {
const TargetItemIndex = aclEntries.findIndex(rec => rec.stakeholder_id === item.value);
const TargetItemContent = aclEntries.find(rec => rec.stakeholder_id === item.value);
console.log('index', TargetItemIndex);
console.log('content', TargetItemContent);
})

Redeclare variables defined in global scope with a loop

I am trying to redeclare variables defined in global scope. I'd like to wrap each function
const {values, map, each} = require('lodash')
const wrapFnInLog = (fn) => (...input) => {
console.log({name: fn.name, input})
const possiblePromise = fn.apply(null, input)
if (get(possiblePromise, 'then')) {
return possiblePromise.then(output => {
console.log({name: fn.name, output})
return output
})
} else {
console.log({name: fn.name, output: possiblePromise})
return possiblePromise
}
}
let a = (arr) => map(arr, i => i.name)
let b = (obj) => a(values(obj))
const provide = [a, b]
provide.forEach(fn => wrapFnInLog(fn))
const example = {
personTom: {
name: 'Tom'
},
personJerry: {
name: 'Jerry'
}
}
b(example)
I'd like the output to look like this:
{ name: 'b', input: [ { personTom: [Object], personJerry: [Object] } ] }
{ name: 'a', input: [ [ [Object], [Object] ] ] }
{ name: 'a', output: [ 'Tom', 'Jerry' ] }
{ name: 'b', output: [ 'Tom', 'Jerry' ] }
The only way I've been able to achieve this is without a loop and it's via deliberately overwriting each variable one by one.
a = wrapFnInLog(a)
b = wrapFnInLog(b)
I'm wondering if it's possible to loop over [a, b] and overwrite the function definition, while keeping them in global module scope.
as already commented, you can use a destructuring assignment to assign multiple variables at once
let a = (arr) => map(arr, i => i.name);
let b = (obj) => a(values(obj));
[a,b] = [a,b].map(wrapFnInLog);
but unlike a destructuring assignment in combination with a variable declaration (let [a,b] = ...) you have to be careful what you write before this assignment and that you properly seperate commands.
Because with automatic semicolon insertation or, JS not inserting a semicolon where one should be,
let a = (arr) => map(arr, i => i.name)
let b = (obj) => a(values(obj))
[a,b] = [a,b].map(wrapFnInLog)
will be interpreted as
let a = (arr) => map(arr, i => i.name);
let b = (obj) => {
return a(values(obj))[a,b] = [a,b].map(wrapFnInLog);
}
//or in other words
let b = (obj) => {
let tmp1 = a(values(obj));
a; //the `a,` in something[a,b];
let tmp2 = [a,b].map(wrapFnInLog);
tmp1[b] = tmp2;
return tmp2;
}

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