Trying to implement inline Webworker for a recursive function - javascript

I have a first version of using a recursive function with Javascript which produces expected results. Below a working version :
// Call the recursive function and get final (a,b) results
var HitTemp = JSON.parse(JSON.stringify(HitCurrent));
var result= recursiveFunction(HitTemp, HitTemp.playerCurrent, maxNodes);
var a = HitTemp.coordPlayable[0];
var b = HitTemp.coordPlayable[1];
// Recursive function
function recursiveFunction(HitCurrent, colorCurrent, depth) {
// Indices
var i, j, k;
// Evaluation
var arrayTemp, eval, e;
// Set current color to HitCurrent
HitCurrent.playerCurrent = colorCurrent;
// Deep copy of arrayCurrent array
arrayTemp = JSON.parse(JSON.stringify(HitCurrent.arrayCurrent));
// If depth equal to 0
if (depth == 0)
return evaluation(HitCurrent);
// Starting evaluation
eval = -infinity;
// Browse all possible hits
for (i = 0; i < 8; i++)
for (j = 0; j < 8; j++) {
if (HitCurrent.arrayPlayable[i][j] == 'playable') {
for (k = 0; k < 8; k++) {
// Explore line started from (i,j) with direction "k"
exploreHitLine(HitCurrent, i, j, k, 'drawing');
}
// Recursive call
e = recursiveFunction(JSON.parse(JSON.stringify(HitCurrent)), ((JSON.stringify(HitCurrent.playerCurrent) == JSON.stringify(playerBlack)) ? playerWhite : playerBlack), depth-1);
if (e > eval) {
HitCurrent.coordPlayable = [i,j];
eval = e;
}
}
// Restore arrayCurrent array
HitCurrent.arrayCurrent = JSON.parse(JSON.stringify(arrayTemp));
}
return eval;
}
From this, I would like to use "inline" WebWorkers to dedicate the recursion to WebWorker and avoid hanging process in browsers.
I tried to follow this link and this other link
I don't know if I have to "postmessage" the object HitCurrent or the value eval to the main thread : by using WebWorker, I make confusions between the return instruction (which returns a value in the terminal case) and the objet HitCurrent argument passed for next recursive call.
If someone could give some clues to reproduce this original algorithm by using inline webworker (or with classical way of using webworker).

Inline webworker example:
As your code, there is no function evaluation, function exploreHitLine.
Before use following code, you must insert them into code.
{
let workerScript = URL.createObjectURL( new Blob( [ `
"use strict";
// Recursive function
function recursiveFunction( HitCurrent, colorCurrent, depth ) {
// Indices
var i, j, k;
// Evaluation
var arrayTemp, eval, e;
// Set current color to HitCurrent
HitCurrent.playerCurrent = colorCurrent;
// Deep copy of arrayCurrent array
arrayTemp = JSON.parse(JSON.stringify(HitCurrent.arrayCurrent));
// If depth equal to 0
if ( depth === 0 ) return evaluation(HitCurrent);
// Starting evaluation
eval = -infinity;
// Browse all possible hits
for (i = 0; i < 8; i++) {
for (j = 0; j < 8; j++) {
if (HitCurrent.arrayPlayable[i][j] === 'playable') {
for (k = 0; k < 8; k++) {
// Explore line started from (i,j) with direction "k"
exploreHitLine(HitCurrent, i, j, k, 'drawing');
}
// Recursive call
e = recursiveFunction(JSON.parse(JSON.stringify(HitCurrent)), ((JSON.stringify(HitCurrent.playerCurrent) == JSON.stringify(playerBlack)) ? playerWhite : playerBlack), depth-1);
if (e > eval) {
HitCurrent.coordPlayable = [i,j];
eval = e;
}
}
// Restore arrayCurrent array
HitCurrent.arrayCurrent = JSON.parse(JSON.stringify(arrayTemp));
}
}
return eval;
}
onmessage = function ( event ) {
let params = event.data;
postMessage( { result: recursiveFunction( ...params ) } );
}
` ], { type: "plain/text" } ) );
// Call the recursive function and get final (a,b) results
new Promise( resolve => {
let HitTemp = JSON.parse(JSON.stringify(HitCurrent));
let firstWorker = new Worker( workerScript );
firstWorker.onmessage = function ( event ) {
resolve( event.data ); //{ result: XXX }
}
firstWorker.postMessage( HitTemp, HitTemp.playerCurrent, maxNodes );
} ).then( ( { result } ) => {
let [ a, b ] = result.coordPlayable;
console.log( result );
} );
}
Additionally following is working inline WebWorker:
{
let workerScript = URL.createObjectURL( new Blob( [ `
"use strict";
onmessage = function ( event ) {
let sum = 0, count = event.data;
for ( let i = 0; i < count**count; i++ ) {
sum += i;
}
postMessage( { result: sum, count } );
}
` ], { type: "plain/text" } ) );
let firstWorker = new Worker( workerScript );
let firstAlive = setTimeout( () => {
firstWorker.terminate();
console.log( "terminated" );
}, 3000 );
firstWorker.onmessage = function ( event ) {
clearTimeout( firstAlive );
console.log( event.data );
}
firstWorker.postMessage( 10 );
let secondWorker = new Worker( workerScript );
let secondAlive = setTimeout( () => {
secondWorker.terminate();
console.log( "terminated" );
}, 3000 );
secondWorker.onmessage = function ( event ) {
clearTimeout( secondAlive );
console.log( event.data );
}
secondWorker.postMessage( 5 );
}
Update 1.
{
// Inline webworker version
let workerScript = URL.createObjectURL( new Blob( [ `
"use strict";
// Recursive function
function recursiveFunction( HitCurrent, colorCurrent, depth ) {
// Indices
var i, j, k;
// Evaluation
var arrayTemp, evaluated, e;
// Set current color to HitCurrent
HitCurrent.playerCurrent = colorCurrent;
// Deep copy of arrayCurrent array
arrayTemp = JSON.parse(JSON.stringify(HitCurrent.arrayCurrent));
// If depth equal to 0
if (depth == 0)
return evaluation(HitCurrent);
// Starting evaluation
evaluated = -infinity;
// Browse all possible hits
for (i = 0; i < 8; i++) {
for (j = 0; j < 8; j++) {
if (HitCurrent.arrayPlayable[i][j] == 'playable') {
for (k = 0; k < 8; k++) {
// Explore line started from (i,j) with direction "k"
exploreHitLine(HitCurrent, i, j, k, 'drawing');
}
// Recursive call
e = recursiveFunction(JSON.parse(JSON.stringify(HitCurrent)), ((JSON.stringify(HitCurrent.playerCurrent) == JSON.stringify(playerBlack)) ? playerWhite : playerBlack), depth-1);
if ( e > evaluated ) {
HitCurrent.coordPlayable = [i,j];
evaluated = e;
}
if (e == -infinity) { HitCurrent.coordPlayable = [ i, j ]; }
// Restore arrayCurrent array
HitCurrent.arrayCurrent = JSON.parse(JSON.stringify(arrayTemp));
}
}
}
return evaluated;
}
onmessage = function ( event ) {
let params = event.data;
//postMessage( { result: recursiveFunction( HitCurrent, HitCurrent.playerCurrent, maxNodes ) } );
postMessage( { result: recursiveFunction( ...params ) } );
};
` ], { type: "plain/text" } ) );
// Call the recursive function and get final (a,b) results
new Promise( resolve => {
let HitTemp = JSON.parse(JSON.stringify(HitCurrent));
let firstWorker = new Worker( workerScript );
firstWorker.onmessage = function ( event ) {
resolve( event.data ); //{ result: XXX }
}
firstWorker.postMessage( [ HitTemp, HitTemp.playerCurrent, maxNodes ] );
} ).then( ( { result } ) => {
let [ a, b ] = result.coordPlayable;
console.log( result );
} );
}
Explanation of my faults:
It is impossible to use "eval" as the name of a variable when in "strict mode".
=>
from: eval
to: evaluated
Worker.postMessage( aMessage, Transferrable ), in this case, you don't need to use the second parameter.
=>
from: firstWorker.postMessage( HitTemp, HitTemp.playerCurrent, maxNodes );
to: firstWorker.postMessage( [ HitTemp, HitTemp.playerCurrent, maxNodes ] );
(https://developer.mozilla.org/en-US/docs/Web/API/Worker/postMessage)
Continue to 2, passing parameter to recursiveFunction is fixed.

eval is a keyword; use some other variable name.
postMessage only
accepts one argument (it also accepts a transferable, but that is not applicable to your case), and if that argument is not a primitive value, it should
be an object that can be serializable (e.g. you cannot pass functions
or methods directly to the web worker)
I might be misunderstanding what you are trying to do, but you might want to reconsider recursively spawning an unkown number of webworkers! Just spawn one webworker and pass the parameters of your function to it and do your recursive calculations inside that webworker synchronously without spawning a new worker, if you just want to free up your main stack. Spawning too many web workers will consume a lot of a resources and will actually slow down your calculations! FYI, spawning each new web worker takes ~40ms and takes up resources. This is a general observation with regard to utilizing multithreading for calculating recursive functions! This might help:
https://softwareengineering.stackexchange.com/questions/238729/can-recursion-be-done-in-parallel-would-that-make-sense
Concerning all functions called into URL.createObjectURL( new Blob( ... )) block, Have I got to include them in this block or can I write them outside of it (as you say for evaluation and explotHitLine functions) ?
Your webworker is a completely separate JS file and execution context; you will not have access to anything that is not defined within its context or send to it as a message.
Btw, there are a few errors in your code which will prevent it from compiling correctly anyways: infinity should be Infinity, playerBlack and playerWhite are undefined, etc.

Related

Merging tagged template literal fragments : best algorithm?

I have a tagged template literal fragment builder that looks like this :
function fragment(chunks, ...args) {
return new Fragment(chunks, args);
};
class Fragment {
constructor(chunks, args) {
this.chunks = chunks;
this.args = args;
}
}
The idea is to being able to split tagged template literals in different parts and having a merge function that transforms an arbitrarily deeply nested fragment tree into a chunks array and an args array as if it was just one big tagged template literal to begin with :
function f() {}
function g() {}
const part = fragment`A ${f} B`
const nested = fragment`C ${part} D ${g} E ${part}`
const [chunks, args] = merge(nested)
console.log(chunks) // ["C A ", " B D ", " E A ", " B"]
console.log(args) // [function f, function g, function f]
What would be the nicest and most efficient algorithm to implement merge ? Thank you.
It's a bit ugly since template literals have two parallel arrays, but here's what I'd do:
function mergeInto(target, acc, {chunks: srcChunks, args: srcArgs}) {
const {chunks: targetChunks, args: targetArgs} = target;
for (var i=0; i<srcArgs.length; i++) {
acc += srcChunks[i];
if (srcArgs[i] instanceof Fragment) {
acc = mergeInto(target, acc, srcArgs[i]);
} else {
targetChunks.push(acc);
targetArgs.push(srcArgs[i]);
acc = "";
}
}
return acc + srcChunks[i];
}
function merge(fragment) {
const chunks = [], args = [];
chunks.push(mergeInto({chunks, args}, "", fragment));
return [chunks, args];
}
Here's another interpretation, although am not fully confident in its execution without seeing more data samples. In short, there is no need for the merge function if one tackles the management of chunks and args in the fragment function...
function fragment(chunks, ...args) {
let chunksCopy = [];
let argsCopy = [];
for ( i = 0; i < args.length; i++ ) {
if ( args[ i ] instanceof Fragment ) {
chunksCopy[ i ] = chunks[ i ] + args[ i ].chunks[ 0 ];
chunksCopy[ i + 1 ] = args[ i ].chunks[ 1 ] + chunks[ i + 1 ];
//argsCopy.splice( i, 0, args[ i ].args[ 0 ] );
argsCopy[ i ] = args[ i ].args[ 0 ];
} else {
if ( chunksCopy[ i ] == null ) {
chunksCopy[ i ] = chunks[ i ];
}
argsCopy[ i ] = args[ i ];
}
}
if ( chunksCopy[ i ] == null ) {
chunksCopy[ i ] = chunks[ i ];
}
return new Fragment( chunksCopy, argsCopy );
};
class Fragment {
constructor( chunks, args ) {
this.chunks = chunks;
this.args = args;
}
}
function f() {}
function g() {}
const part = fragment`A ${f} B`;
console.log( part );
const nested = fragment`C ${part} D ${g} E ${part}`;
console.log( nested )
console.log( nested.chunks ); // ["C A ", " B D ", " E A ", " B"]
console.log( nested.args ) // [function f, function g, function f]
Note when using "Run code snippet", that console.log( nested.args ) appears to show the third function argument as a reference back to the first function argument of f(). For a clearer interpretation of the results, copy and run the above in browser debug mode...

Recursive function returning undefined value

I want to fetch the object from multi level structure
I written function for it but even on return its not coming out from function and returning value, its continue with next recursion. I know its returning value to the previously called function and as its scope is block its getting overridden and that's why returning undefined value
var selectedObj = findObjectByUid( existingStructure, selectedUid);
function findObjectByUid( root, selectedUid ) {
if( root.uniqueId === selectedUid ) {
return root;
}
if( root.children && root.children.length > 0 ) {
for( var k in root.children ) {
if( root.children[ k ].uniqueId === selectedUid ) {
return root.children[ k ];
} else if( root.children.length ) {
return findObjectByUid( root.children[ k ], selectedUid );
}
}
}
}
Here i want to get back to my initial calling function when it got matching uid.
Actually you return with the first child, regardless of the found node.
You could take a temporary variable and store the result of the children check and if not falsy return this value.
BTW, you could take the child directly of the array for the recursion.
function findObjectByUid(root, selectedUid) {
if (root.uniqueId === selectedUid) return root;
if (!root.children || !root.children.length) return;
for (let child of root.children) {
let temp = findObjectByUid(child, selectedUid);
if (temp) return temp;
}
}
var selectedObj = findObjectByUid(existingStructure, selectedUid);
There are three problems with using this approach on arrays. First, the for...in also iterates over an object's prototype properties if those properties are enumerable. For example:
Array.prototype.voice = "James Earl Jones";
var tMinus = [
"Two",
"One",
"Blast off!"
];
var countdown = "";
for (var step in tMinus) {
countdown += tMinus[step] + "\n";
}
console.log(countdown);
// => "Two
// One
// Blast Off!
// James Earl Jones
// "
That can be solved by using hasOwnProperty to exclude prototype properties.
Example:
for (var step in tMinus) {
if (tMinus.hasOwnProperty(step)) {
countdown += tMinus[step] + "\n";
}
}
Here are corrected code. You had used return findObjectByUid in inner calling by which code was terminating before completing loop.
function findObjectByUid( root, selectedUid ,foundArr) {
if( root.uniqueId === selectedUid ) {
foundArr.push(root);
return root;
}
else if( root.children && root.children.length > 0 ) {
for( var k in root.children ) {
findObjectByUid( root.children[k], selectedUid,foundArr );
if(root.children[k]=== selectedUid){break;}
}
}
return foundArr.length>0?foundArr[0]:null;
}
Sample json and calling method
var root = {uniqueId:1,children:[{uniqueId:10},{uniqueId:11,children:[{uniqueId:21,children:[]},{uniqueId:22,children:[]},{uniqueId:23,children:[{uniqueId:31,children:[]},{uniqueId:32,children:[]}]}]},{uniqueId:12,children:[]},{uniqueId:13,children:[]}]};
findObjectByUid(root,32,[]);

piping functions in JavaScript

How can I have a JavaScript function let's say piper() which takes several functions as its arguments and it returns a new function that will pass its argument to the first function, then pass the result to the second, then
pass the result of the second to the third, and so on, finally returning the output of the last function.
Something like piper(foo, fee, faa)(10, 20, 30) would be equivalent to calling faa(fee(foo(10,20,30))).
ps:
It was a part of an interview, that I did few days ago.
For an arbritrary number of functions you could use this ES6 function:
function piper(...fs) {
return (...args) => fs.reduce((args,f) => [f.apply(this,args)],args)[0];
}
// Example call:
var result = piper(Math.min, Math.abs, Math.sqrt)(16, -9, 0)
// Output result:
console.log(result);
The same in ES5 syntax:
function piper(/* functions */) {
var fs = [].slice.apply(arguments);
return function (/* arguments */) {
return fs.reduce(function (args,f) {
return [f.apply(this,args)];
}.bind(this), [].slice.apply(arguments))[0];
}.bind(this);
}
// Example call:
var result = piper(Math.min, Math.abs, Math.sqrt)(16, -9, 0)
// Output result:
console.log(result);
Enjoy. Pure ES5 solution. Preserves this.
function piper(){
var i = arguments.length,
piped = arguments[ --i ];
while( --i >= 0 ){
piped = pipeTwo( arguments[ i ], piped );
}
return piped;
}
function pipeTwo( a, b ){
return function(){
return a.call( this, b.apply( this, arguments ) );
}
}
Or, if you want the fancy solution.
function piperES6( ...args ){
return args.reverse().reduce( pipeTwo );
}
Loops can be reversed depending on the desired direction.
Very similar to #trincot's answer (preserves context), but composes in the correct order and is marginally faster since it does not create intermediary arrays:
const piper = (...steps) => function(...arguments) {
let value = steps[0].apply(this, arguments);
for (let i = 1; i < steps.length; ++i) {
value = steps[i].call(this, value);
}
return value;
};
// Usage:
let p = piper(
x => x + 1,
x => x * 2,
x => x - 1
);
console.log(p(2)); // 5
Here is an alternative answer involving method chaining. I shall use ES6, though of course this can be transpiled to ES5. On benefit of this solution is that is has a very succinct TypeScript counterpart with perfect typeability.
class Pipe {
constructor(value) {
this.value = value;
}
then(f) {
return new Pipe(f(this.value));
}
}
const pipe = value => new Pipe(value);
// Example
const double = x => 2 * x;
pipe(42).then(double).then(console.log); // 84
const result = pipe(42).then(double).then(double).value;
console.log(result); // 168
A simple solution based on JS higher-order functions usage:
function pipe(...rest) {
return x => rest.reduce((y, f) => f(y), x);
}
Usage:
pipe((a) => a + 1, (a) => a * 2)(3) // 8
pipe((a) => a + 1, (a) => a * 2)(2) // 2
function f(f1, f2, f3){
return (args => f3(f2(f1(args))));
}
I think what you are trying to do is chaining.
var funct={
total:0,
add:function(a) {
console.log(funct.total,funct.total+a);
funct.total+=a;
return funct;
}
};
funct.add(5).add(6).add(9);

How call back function of a function works

I have the following code
$.map( [ 0, 1, 2 ], function( n ) {
return n > 0 ? n + 1 : null;
});
Out Put: [ 2, 3 ]
I know $.map Translate all items in an array or object to new array of items.(Documentation).
What I want to know how call back function in a .map works(internal implentation)?
One possible answer could be
.map has some loop which passes each element of array to call back method, that return some value.
.map manage each value return from call back method.In this case push in some internal array.
At the end of loop .map return array.
EDIT
But I am not sure how it works, is it works as I explained??
But I am not sure is this how it works??
Yes, that's basically how it works. Full details, as always, in the source code (that line number will rot over time...). Currently, it looks like this:
map: function( elems, callback, arg ) {
var value,
i = 0,
length = elems.length,
isArray = isArraylike( elems ),
ret = [];
// Go through the array, translating each of the items to their new values
if ( isArray ) {
for ( ; i < length; i++ ) {
value = callback( elems[ i ], i, arg );
if ( value != null ) {
ret.push( value );
}
}
// Go through every key on the object,
} else {
for ( i in elems ) {
value = callback( elems[ i ], i, arg );
if ( value != null ) {
ret.push( value );
}
}
}
// Flatten any nested arrays
return concat.apply( [], ret );
},
Well, there is no better way to check than looking at the source code
function (elems, callback, arg) {
var value, i = 0,
length = elems.length,
isArray = isArraylike(elems),
ret = [];
// Go through the array, translating each of the items to their new values
if (isArray) {
for (; i < length; i++) {
value = callback(elems[i], i, arg);
if (value != null) {
ret.push(value);
}
}
// Go through every key on the object,
} else {
for (i in elems) {
value = callback(elems[i], i, arg);
if (value != null) {
ret.push(value);
}
}
}
// Flatten any nested arrays
return concat.apply([], ret);
}
Yes, whether its an Array version or object, it is looping and calling the callback to set the value
Yes, for both loops, the value is pushed
Yes, it is returning a flattened array by calling concat

javascript magically inserting an empty var into a new array / changing its length?

So i have a piece of code like this:
this.convertParametersToGroups = function (nodes) {
self.selectedParametersGroup([]);
var emptyParametersList = [];
var pos = 0;
var group;
var tempParametersGroup = [];
// tempParametersGroup: Array[1]
// 0: undefined
$.each(nodes, function (key, val) {
emptyParametersList.push(0);
group = self.convertNodeToParameterGroup(val);
group.position = pos;
tempParametersGroup.push(group);
pos++;
});
self.selectedParametersGroup(tempParametersGroup);
return emptyParametersList;
};
The commented results don't change when I reorder the declarations.
When i had "group" in an $.each loop, it inserted an undefined value in the first iteration, before executing the first statement in the callback.
However, when I change their names around like this:
this.convertParametersToGroups = function (nodes) {
self.selectedParametersGroup([]);
var emptyParametersList = [];
var pos = 0;
var g;
var tempParametersGr = [];
// tempParametersGr: Array[0]
$.each(nodes, function (key, val) {
emptyParametersList.push(0);
g = self.convertNodeToParameterGroup(val);
g.position = pos;
tempParametersGr.push(g);
pos++;
});
self.selectedParametersGroup(tempParametersGr);
return emptyParametersList;
};
It works as in comments. Seems like javascript somehow maps the "group" name to the "Group" suffix of the array and inserts it or forces length increment. Am I missing some known language feature or...?
You can try debugging this way, it should be very clear when the undefined was actually added to the array:
function makeClone( arr ) {
return JSON.parse( JSON.stringify( arr ) );
}
this.convertParametersToGroups = function (nodes) {
self.selectedParametersGroup([]);
var emptyParametersList = [];
var pos = 0;
var group;
var tempParametersGroup = [];
console.log( "before loop", makeClone( tempParametersGroup ) );
$.each(nodes, function (key, val) {
console.log( "before iteration", key, makeClone( tempParametersGroup ) );
emptyParametersList.push(0);
group = self.convertNodeToParameterGroup(val);
group.position = pos;
tempParametersGroup.push(group);
pos++;
console.log( "after iteration", key, makeClone( tempParametersGroup ) );
});
console.log( "after loop", makeClone( tempParametersGroup ) );
self.selectedParametersGroup(tempParametersGroup);
return emptyParametersList;
};

Categories