dynamic access to an array in Javascript - javascript

Maybe there is already a solution, but I can't find it. I try to access different slots in a multi dimensional array dynamical but with the challenge of different depths. Basically, it looks like this:
var source = [];
source['lvl1'] = [];
source['lvl1']['lvl2a'] = [];
source['lvl1']['lvl2a']['lvl3'] = "ping";
source['lvl1']['lvl2b'] = "pong";
If the depth is fix, I could write code like this:
var path1 = ["lvl1","lvl2a","lvl3"];
var path2 = ["lvl1","lvl2b"];
console.log(source[path1[0]][path1[1]][path[2]]); // => ping
console.log(source[path2[0]][path2[1]]); // => pong
My problem is to write a code that works for both variants. This would work:
switch(path.length)
{
case 1:
console.log(source[path[0]]);
break;
case 2:
console.log(source[path[0]][path[1]]);
break;
case 3:
console.log(source[path[0]][path[1]][path[2]]);
break;
}
But this is neither efficient nor elegant. Has somebody another solution that works for example with some kind of loop?!?
Thanks
Thomas

This question has been answered quite some time ago, but I'd like to show a really simple one line solution using the array reducer:
const getRoute = (o, r) => r.split(".").reduce((c, s) => c[s], o);
let q = {a:{b:{c:{d:"hello world"}}}};
console.log(getRoute(q, 'a.b.c.d'));
This might help someone else :)

If you are sure that all the values in the array will exist, then you can simply use Array.prototype.reduce, like this
console.log(path1.reduce(function(result, currentKey) {
return result[currentKey];
}, source));
# ping
You can make it generic, like this
function getValueFromObject(object, path) {
return path.reduce(function(result, currentKey) {
return result[currentKey];
}, object);
}
And then invoke it like this
console.assert(getValueFromObject(source, path1) === "ping");
console.assert(getValueFromObject(source, path2) === "pong");
Note: You need to make sure that the source is a JavaScript object. What you have now is called an array.
var source = {}; # Note `{}`, not `[]`

You can loop and build up the value of source before logging it. Try this out:
var sourceValue = source[path[0]];
for (var i = 1; i < path.length; i++) {
sourceValue = sourceValue[path[i]];
}
console.log(sourceValue);
Here's a JSFiddle that demonstrates this approach works.

You can get a value from a path (tested code):
var getValue = function(path, context) {
if ('object' !== typeof context) {
throw new Error('The context must be an object; "' + typeof context + '" given instead.');
}
if ('string' !== typeof path) {
throw new Error('The path must be a string; "' + typeof context + '" given instead.');
}
var fields = path.split('.'),
getValueFromFields = function(fields, context) {
var field = fields.shift();
if (0 === fields.length) {
return context[field];
}
if ('object' !== typeof context[field]) {
throw new Error('The path "' + path + '" has no value.');
}
return getValueFromFields(fields, context[field]);
}
;
return getValueFromFields(fields, context);
}
var source = [];
source['lvl1'] = [];
source['lvl1']['lvl2a'] = [];
source['lvl1']['lvl2a']['lvl3'] = "ping";
source['lvl1']['lvl2b'] = "pong";
console.log(getValue('lvl1.lvl2a.lvl3', source)); // ping
console.log(getValue('lvl1.lvl2b', source)); // pong

Related

memoize any given recursive function in JavaScript

I am interested in the scenario where we have some function f which is recursive and which we are not provided the source code to.
I would like a function memoizer: Function -> Function which takes in say f and returns a function g such that g = f (in the sense they return the same value given the same arguments) which when called first checks if the called arguments are in its 'cache' (memory of results it has calculated before) and if so returns the result from this, otherwise it should compute f, should f call itself with some arguments, this is tantamount to calling g with those arguments and I would like that f first check if the cache of g contains those arguments and if so return the result from this, otherwise ...
This is easy (in Javascript) to do given the source code of f, I simply define memoize in the obvious way and do something like
let f = memoize((...args) => {/* source code of f */});
But this doesn't appeal to me at all (mainly because I might want a memoized and non memoized version of the same function and then I'd have to write the same function twice) and won't work if I don't know how to implement f.
In case it's not clear what I'm asking,
I would like a function memoize which takes a function such as
fact = n => n === 0 ? 1 : n * fact(n - 1);
And returns some new function g such that fact(n) = g(n) for all n and which for example when g(10) is computed stores the values of fact(0), ..., fact(10) which are computed while computing g(10) and then if I ask for say g(7) it finds the result in the cache and returns it to me.
I've thought that conceptually it's possible to detect when f is called since I have it's address and maybe I could replace all calls to f with a new function where I compute f and store the result and then pass the value on to where it would normally go. But I don't know how to do this (and it sounds unpleasant).
maybe I could replace all calls to f with a new function where I compute f and store the result and then pass the value on to where it would normally go.
This is actually very easy to do, as Bergi referred to in a comment.
// https://stackoverflow.com/questions/24488862/implementing-automatic-memoization-returns-a-closured-function-in-javascript/
function memoize(func) {
var memo = {};
var slice = Array.prototype.slice;
return function() {
var args = slice.call(arguments);
if (args in memo)
return memo[args];
else
return (memo[args] = func.apply(this, args));
}
}
function fib(n) {
if (n <= 1) return 1;
return fib(n - 1) + fib(n - 2);
}
fib = memoize(fib);
console.log(fib(100));
I might want a memoized and non memoized version of the same function and then I'd have to write the same function twice
Yes, you need to. The recursive call to fact(n - 1) inside the function can only refer to one fact function - either a memoized or an unmemoized one.
So what you need to do to avoid code duplication is define fact with the Y combinator:
const makeFact = rec => n => n === 0 ? 1 : n * rec(n - 1);
// ^^^ ^^^
const factA = Y(makeFact);
const factB = memoizingY(makeFact);
function Y(make) {
const f = make((...args) => f(...args)); // const f = make(f) is easier to understand
return f; // but doesn't work with eager evaluation
}
I'll leave the definition of memoizingY as an exercise to the reader :-)
Possibly simpler approach:
const makeFact = annotate => {
const f = annotate(n => n === 0 ? 1 : n * f(n - 1));
return f;
}
const factA = makeFact(identity);
const factB = makeFact(memoize);
In my limited experience, we do have access to JavaScript source code. We could thus attempt to generate new source code for the memoized function.
// Redefine Function.prototype.bind
// to provide access to bound objects.
// https://stackoverflow.com/questions/7616461/generate-a-hash-from-string-in-javascript
var _bind = Function.prototype.apply.bind(Function.prototype.bind);
Object.defineProperty(Function.prototype, 'bind', {
value: function(obj) {
var boundFunction = _bind(this, arguments);
boundFunction.boundObject = obj;
return boundFunction;
}
});
// Assumes the parameters for the function,
// f, can be consistently mapped.
function memo(f){
if (!(f instanceof Function))
throw TypeError('Argument is not an instance of Function.');
// Generate random variable names
// to avoid conflicts with unknown
// source code
function randomKey(numBytes=8){
let ranges = [[48, 10], [65, 26], [97, 26]];
let key = '_';
for (let i=0; i<numBytes; i++){
let idx = Math.floor(Math.random() * ranges.length);
key += String.fromCharCode(ranges[idx][0] + Math.random() * ranges[idx][1]);
}
return key;
}
let fName = f.name;
let boundObject;
let fCode;
const nativeCodeStr = '(){[nativecode]}';
// Possible Proxy
try {
fCode = f.toString();
} catch(error){
if (error.constructor == TypeError){
if (Function(`return ${ fName }.toString()`)() != nativeCodeStr){
throw TypeError(`Possible Proxy detected: function has a name but no accessible source code. Consider memoizing the target function, ${ fName }.`);
} else {
throw TypeError(`Function has a name but no accessible source code. Applying toString() to its name, ${ fName }, returns '[native code]'.`);
}
} else {
throw Error('Unexpected error calling toString on the argument.');
}
}
if (!fName){
throw Error('Function name is falsy.');
// Bound functions
// Assumes we've monkey-patched
// Function.prototype.bind previously
} else if (fCode.replace(/^[^(]+|\s+/g, '') == nativeCodeStr){
if (/^bound /.test(fName)){
fName = fName.substr(6);
boundObject = f.boundObject;
// Bound functions return '[native code]' for
// their toString method call so get the code
// from the original function.
fCode = Function(`return ${ fName }.toString()`)();
} else {
throw Error("Cannot access source code, '[native code]' provided.");
}
}
const fNameRegex = new RegExp('(\\W)' + fName + '(\\W)', 'g');
const cacheName = randomKey();
const recursionName = randomKey();
const keyName = randomKey();
fCode = fCode.replace(/[^\(]+/,'')
.replace(fNameRegex, '$1' + recursionName + '$2')
.replace(/return/g, `return ${ cacheName }[${ keyName }] =`)
.replace(/{/, `{\n const ${ keyName } = Array.from(arguments);\n\n if (${ cacheName }[${ keyName }])\n return ${ cacheName }[${ keyName }];\n`);
const code = `function(){\nconst ${ cacheName } = {};\n\nfunction ${ recursionName + fCode }\n\nreturn ${ recursionName }.apply(${ recursionName }, arguments);}`;
let g = Function('"use strict";return ' + code)();
if (boundObject){
let h = (g).bind(boundObject);
h.toString = () => code;
return h;
} else {
return g;
}
} // End memo function
function fib(n) {
if (n <= 1) return 1;
return fib(n - 1) + fib(n - 2);
}
const h = fib.bind({a: 37});
const g = memo(h);
console.log(`g(100): ${ g(100) }`);
console.log(`g.boundObject:`, g.boundObject);
console.log(`g.toString():`, g.toString());
try{
memo(function(){});
} catch(e){
console.log('Caught error memoizing anonymous function.', e)
}
const p = new Proxy(fib, {
apply: function(target, that, args){
console.log('Proxied fib called.');
return target.apply(target, args);
}
});
console.log('Calling proxied fib.');
console.log(`p(2):`, p(2));
let memoP;
try {
memoP = memo(p);
} catch (e){
console.log('Caught error memoizing proxied function.', e)
}

NodeJS require with asynch functions when synch is wanted

I have the following code
var utils = require(`${__dirname}/../../utils/utils.js`);
...
let object = utils.parse(input);
if (object === undefined){
let helper = utils.recognize(input);
msg.channel.sendMessage("\"" + input + "\" not recognized. Did you mean \"" + helper[0] + "\"?");
object = utils.parse(helper[0]);
}
//code related to object
console.log(object.strLength);
where "parse" tries to match the input to an object in a database, and "recognize" tries to find the best match if the input is spelled incorrectly (Levenshtein) (along with additional info such as how close the match was).
Currently the issue is that the code is ran asynchronously; "object.strLength" returns an undefined before utils.recognize() returns a value. If I copy/paste the recognize() and parse() functions into the file, then the code is run synchronously and I do not run into any issues. However I would rather keep those functions in a separate file as I reuse them in other files.
Is there a way to specify that the functions in utils must be synch? I know that there are libraries that convert asynch into synch but I prefer to use as few libraries as I can help it. I tried to have the recognize functions return a Promise but it ended up as a jumbled mess
edit: here's parse. I did not think it was necessary to answer this question so I did not include it initially:
var db = require(`${__dirname}/../data/database.js`);
...
var parse = (input) => {
let output = db[output];
if (output === null) {
Object.keys(db).forEach((item) => {
if (db[item].num === parseInt(input) || (db[item].color + db[item].type === input)){
output = db[item];
return false;
}
});
}
return output;
}
I solved the issue, thanks everyone. Here's what was wrong, it was with recognize(). It was my mistake to not show the code for it initially.
Original recognize:
var recognize = (item) => {
//iterate through our databases and get a best fit
let bestItem = null;
let bestScore = 99999; //arbitrary large number
//let bestType = null;
//found algorithm online by milot-mirdita
var levenshtein = function(a, b) {
if (a.length == 0) { return b.length; }
if (b.length == 0) { return a.length; }
// swap to save some memory O(min(a,b)) instead of O(a)
if(a.length > b.length) {
let tmp = a;
a = b;
b = tmp;
}
let row = [];
for(let i = 0; i <= a.length; i++) {
row[i] = i;
}
for (let i = 1; i <= b.length; i++) {
let prev = i;
for (let j = 1; j <= a.length; j++) {
let val;
if (b.charAt(i-1) == a.charAt(j-1)) {
val = row[j-1]; // match
} else {
val = Math.min(row[j-1] + 1, // substitution
prev + 1, // insertion
row[j] + 1); // deletion
}
row[j - 1] = prev;
prev = val;
}
row[a.length] = prev;
}
return row[a.length];
}
//putting this here would make the code work
//console.log("hi");
Object.keys(db).forEach((key) => {
if (levenshtein(item, key) < bestScore) {
bestItem = key;
bestScore = levenshtein(item, key);
}
});
return [bestItem, bestScore];
}
My solution was to move the levenshtein function outside of the recognize function, so if I wanted to I can call levenshtein from another function
#user949300 and #Robert Moskal, I changed the forEach loop into a let...in loop. There is no functional difference (as far as I can tell) but the code does look cleaner.
#Thomas, I fixed the let output = db[output]; issue, oops.
Again, thanks for all of your help, I appreciate it. And happy New Year too

How to convert this C# scripts to Javascript

I have a C# script like below:
public List<MazePath> BreakIntoConnectedPaths()
{
List<MazeVertex> remainVertices = new List<MazeVertex>(vertices);
List<MazePath> paths = new List<MazePath>();
while (remainVertices.Count > 0)
{
MazePath path = new MazePath();
path.entrancePosition = entrancePosition;
path.exitPosition = exitPosition;
VisitCell(path, remainVertices.First(), null, remainVertices);
paths.Add(path);
//Store the coordinate for entrance and exit
}
return paths;
}
void VisitCell(MazePath path, MazeVertex ver, MazeVertex parent, List<MazeVertex> remainVertices)
{
remainVertices.Remove(ver);
path.Add(ver);
for (int i = 0; i < ver.connectVertices.Count; i++)
{
MazeVertex ver2 = ver.connectVertices[i];
if (ver2 != parent)
{
VisitCell(path, ver2, ver, remainVertices);
}
}
}
I want to convert it to javascript as below
BreakIntoConnectedPaths = function() {
var remainVertices = _.cloneDeep(this.vertices);
var paths = [];
while (remainVertices.length > 0) {
var path = new Path();
path.entrancePos = this.entrancePos;
path.exitPos = this.exitPos;
this.VisitCell(path, remainVertices[0], null, remainVertices);
paths.push(path);
// Store the coordinate for entrance and exit
}
return paths;
}
VisitCell = function(path, vertex, parentVertex, remainVertices) {
_.remove(remainVertices, function(v) {
return v.x === vertex.x && v.z === vertex.z;
});
path.Add(vertex);
for (var i = 0; i < vertex.connectVertices.length; i++) {
var connectedVertex = vertex.connectVertices[i];
// if (parentVertex && (connectedVertex.x !== parentVertex.x || connectedVertex.z !== parentVertex.z)) {
if(parentVertex && _.isEqual(connectedVertex, parentVertex)) {
VisitCell(path, connectedVertex, vertex, remainVertices);
}
}
}
The _ symbol here is lodash sign.
After I convert to javascript code, the behavior of these functions is difference with the C# one. With the same vertices data, the paths array had returned with difference size.
Thanks you for reading and pls help me if you see my mistake here.
In the C# version, your VisitCell function has a condition that says if(ver2 != parent), but in the JS version you check that they are equal instead of not equal.
Also, that condition would never pass any way because in your first call to that function you pass in null for the parent, but in that condition you check that the parent is "truthy".
Lodash's isEqual can handle null values, so I'm not sure why you're checking if the parent is truthy there. Perhaps you meant to do this?
if(!_.isEqual(connectedVertex, parentVertex)) {
There are several ways to improve your JavaScript code. When transpiling code, it is better to not copy/paste and fix, but to rewrite using the target language instead.
I would prefer to have this written, for example:
var vertices;
var entrancePos;
var exitPos;
function Path(entrancePos, exitPos){
this.entrancePos = entrancePos;
this.exitPos = exitPos;
this.Add = function() {
// your Add() code here
}
}
function breakIntoConnectedPaths() {
var remainingVertices = _.cloneDeep(vertices);
var paths = [];
while (remainVertices.length) {
var path = new Path(entrancePos, exitPos);
visitCell(path, remainingVertices.shift());
// Store the coordinate for entrance and exit
paths.push(path);
}
return paths;
}
function visitCell(path, vertex, parentVertex) {
path.Add(vertex);
for (var i = 0; i < vertex.connectVertices.length; i++) {
var connectedVertex = vertex.connectVertices[i];
if(_.isEqual(connectedVertex, parentVertex)) {
visitCell(path, connectedVertex, vertex);
}
}
}
Keep in mind that the variables vertices, entrancePos, exitPos and Path are not available to me on your C# code, so I only declare them on JavaScript. Implement them as you may.
Does that fix it, by the way?

js build object path in property assignment

is there a way to automatically create subobjects in an assignment after construction, i.e.
var obj = {};
obj.a.b.c=13;
the above gives me a "obj.a is undefined" error
i wrote a function to do this, but wondered if there was an easier way
_setObjectProperty(obj,13,['a','b','c']);
function _setObjectProperty(obj,value,loc)
{
if(loc.length>1) {
obj[loc[0]] = obj[loc[0]] || {};
_setObjectProperty(obj[loc[0]],value,loc.splice(1));
}
else if(loc.length===1) {
obj[loc[0]]=value;
}
}
No, there's no built in way to do this in JavaScript. The only way is to create your own function like you did. If you want the convenience of the dot operator/notation you can use the following function:
var set = function(path, value, root) {
var segments = path.split('.'),
cursor = root || window,
segment,
i;
for (i = 0; i < segments.length - 1; ++i) {
segment = segments[i];
cursor = cursor[segment] = cursor[segment] || {};
}
return cursor[segments[i]] = value;
};
set("a.b.c", 2);
console.log(a.b.c) // => 2

Can't iterate array returned from Sizzle

From my understanding Sizzle returns an array of objects (DOMElements), I am trying to walk that array of objects in a for loop but I am getting errors. When I try to get a property with the
obj[index-number]["property"]
it works fine, but when I try to access it after passing it to another function
obj[index-number][arguments[index-number]]
I am getting a return of undefined. I have tried many different ways, including eval to parse the dot notation to no avail. I am stumped. Any pointers or ideas would be awesome. Also, I have verified all input to the function is correct (through alerting them out), also, hard coding the values to get what I want in the function works as well. Here is my code: (sorry it's lengthy).....
var ecmafw = function() {
// Creates the new instance of the object.
// Sets up the objects global properties:
this.error = false;
// Checks to see if arguments were supplied, if none are then it returns false.
if (arguments.lenght == 0) {
this.error = "No arguments were supplied.";
return false;
}
// Gives a reference to the result set.
this.results = Sizzle(arguments[0]);
this.attr = function() {
/* Purpose: To add/remove/update an attribute from the results set.
*
* Can be used in two ways:
* 1: .attr("attribute1='value' attribute2='value' attribute3='value'") // adds/removes them all. [negate value to be removed with "-" (used for class)]
* 2: .attr("attribute", "value") // adds the one. [negate value to be removed with "-" (used for class)]
* 3: .attr("attribute") // removes the one.
* 4: .attr("attribute1 attribute2 attribute3") // removes them all.
*/
var len = this.results.length;
switch (arguments.length) {
case 1:
for (var a=0; a < len; a++) {
var re = new RegExp("=", "g");
if (re.test(arguments[0])) {
// Provided a list of attributes to update/create.
valuePairs = arguments[0].split("' ");
for (var i=0; i < valuePairs.length; i++) {
var attributeValue = valuePairs[i].split("=");
var newRE = new RegExp(/^-/);
var value = attributeValue[1].replace(/'/g, "");
if (newRE.test(value)) {
this.removeAttr(attributeValue[0], a, value);
} else {
this.setAttr(attributeValue[0], value, a);
}
}
} else {
var attributeSplit = arguments[0].split(" ");
if (attributeSplit.length == 1) {
// Provided a single attributes to remove.
this.removeAttr(arguments[0], a);
} else {
// Provided multiple attributes to remove.
for (var i=0; i < attributeSplit.length; i++) {
this.removeAttr(attributeSplit[i], a);
}
}
}
}
break;
case 2:
// Provided a single name/value pair to update.
for (var a=0; a < len; a++) {
this.setAttr(arguments[0], arguments[1], a)
}
break;
default:
// Either 0 or more than 2 arguments were supplied.
this.error = "There were no arguments supplied with the attr() function, or there were too many supplied.";
return false
break;
}
};
this.setAttr = function() {
// Counters for IE className
if (document.all && !window.opera) {
arguments[0] = arguments[0].replace(/class/gi, "className");
}
if (arguments[0] == "class" || arguments[0] == "className") {
if (this.results[arguments[2]][arguments[0]] != undefined) {
arguments[1] += " " + this.results[arguments[2]][arguments[0]]; // Failing
}
}
if (this.results[arguments[2]].setAttribute) {
this.results[arguments[2]].setAttribute(arguments[0], arguments[1]);
} else {
this.results[arguments[2]][arguments[0]] = arguments[1];
}
};
this.removeAttr = function() {
arguments[0] = arguments[0].replace(/class/gi, "className");
var item = this.results[arguments[1]];
if (arguments[0] == "className") {
arguments[2] = arguments[2].replace("-", "");
var replaceRE = new RegExp(arguments[2], "gi");
// For some reason it would find it like this, This is fine but it is not working
// in Opera. Opera is failing to convert item[eachItem] to an object. (so it says in its error log)
for (var eachItem in item) {
if (arguments[0] == eachItem) {
item[eachItem] = item[eachItem].replace(replaceRE, " ");
item[eachItem] = item[eachItem].replace(/ /gi, " ");
item[eachItem] = item[eachItem].replace(/^ /gi, "");
item[eachItem] = item[eachItem].replace(/ $/gi, "");
}
}
} else {
if (this.results[arguments[1]].removeAttribute) {
this.results[arguments[1]].removeAttribute(arguments[0]);
} else {
this.results[arguments[1]][arguments[0]] = "";
}
}
};
// Returns a reference to itself.
return this;
}
Not sure if this might be the problem, but in the removeAttr function you are accessing the 3rd argument passed in on this line:
arguments[2] = arguments[2].replace("-", "");
However, in 2 of the 3 calls to this function you only pass in 2 arguments. If the above line runs in either of those cases arguments[2] would be undefined and calling replace("-", "") on it would throw an error.
Also, you have a typo in your initial arguments check near the top: arguments.lenght.

Categories