Javascript Parse Nested String Functions with parameters - javascript

I am trying to parse this string into a organized set of functions:
var str = "a(b, c(e, f(h,i,j), g(k,l,m(o,p,q)) ), d(r,s,t))"
Ideally I would like to turn it into an object like this:
var obj = {
func:'a',
params:[
{p:'b'},
{p: {
func:'c',
params:[
{
p:'e',
p:{
func:'f',
params:[
{p:'h'},
{p:'i'},
{p:'j'}
]
},
p:'g',
params:[
{p:'k'},
{p:'l'},
{p:{
func:'m',
params:[
{p:'o'},
{p:'p'},
{p:'q'}
]
}}
]
}
]
}},
{
p:'d',
params:[
{p:'r'},
{p:'s'},
{p:'t'}
]
}
]
}
I have tried about 8 hours of mixed str.replace() str.substring(), and str.indexOf() and not had any luck.
Any help about how to go about achieving my goal would be appreocated.
note: the functions could take any number params and is not set to 3
UPDATE -- I stopped trying to do string manipulation and approached it character by character. To create desired output:
var str = "a(b, c(e, f(h,i,j), g(k,l,m(o,p,q)) ), d(r,s,t))";
str = str.replace('/ /g,""');
var strArr = str.split('');
var firstPass = "";
var final;
var buildObj = function(){
for(var i = 0; i < strArr.length; i++){
var letters = /^[0-9a-zA-Z]+$/;
if(strArr[i].match(letters)){
if(strArr[i + 1] == '('){
firstPass += '},{"func":' + '"' + strArr[i] + '"';
} else {
firstPass += '"' + strArr[i] + '"';
}
}
if(strArr[i] == '('){
firstPass += ',"params":[{"p":';
}
if(strArr[i] == ')'){
firstPass += '}],';
}
if(strArr[i] == ','){
firstPass += '},{"p":';
}
//console.log(job + '}')
}
var secondPass = firstPass;
secondPass += '}'
secondPass = secondPass.replace(/,{"p":}/g,'');
secondPass = secondPass.replace('},','');
secondPass = secondPass.replace(/],}/g,']}');
final = secondPass
console.log(final)
console.log(JSON.parse(final))
};

Regular expressions and string hacking isn't going to work; regexes cannot handle (directly) any text with nested structures (people keep learning this...). Switching to single characters doesn't improve anything.
Classically what you want is a lexer that produces tokens (language elements) and a parser (that checks the elements are organized properly).
As a practical matter, you can combine these into one coherent structure for simple languages like the one that interests OP. Check out this SO answer on how to build a recursive descent parser easily; follow that answer to one that tells how to build a tree (in essence, how to build the result structure you want).

I saw the problem, and thought it might be interesting to try. I'll walk through my thought process and hope that helps you.
The object I produce does not entirely map to yours, but could easily be. It was easier to end up with the object I produced without additional work without getting distracted by "extraneous" details like putting things in an array.
1.) I'm assuming whitespace is useless. The first step was to replace all whitespace with nothing.
function processStatement(statement) {
return statement.replace(/[ ]/g, '');
}
// Output: a(b,c(e,f(h,i,j),g(k,l,m(o,p,q))),d(r,s,t))
2.) I proceeded with the goal of creating a tree like object, with parameters that do not lead into more functions as dead ends. I needed a way to parse "roots", the code should explain more:
function produceNodeFromStatement(statement) {
var regex = new RegExp('([a-zA-Z])\\((.+)\\)', 'g');
var results = regex.exec(statement);
// This regex matches for the widest pattern: identifier(stuff-inside)
// Running the previous output would result in matching groups of:
// identifier: a
// stuff-inside: b,c(e,f(h,i,j),g(k,l,m(o,p,q))),d(r,s,t)
var root = {}
// We need a way to split the stuff-inside by commas that are not enclosed in parenthesis.
// We want to turn stuff-inside into the following array:
// [ 'b', 'c(e,f(h,i,j),g(k,l,m(o,p,q)))', 'd(r,s,t)' ]
// Since my regex-fu is bad, I wrote a function to do this, explained in the next step.
var parameters = splitStatementByExternalCommas(results[2]);
var node = {};
parameters.forEach(function (parameter) {
if (parameter.indexOf('(') == -1) {
node[parameter] = null;
} else {
// Recursion. This function produces an anonymous wrapper object around a node.
// I will need to unwrap my result.
var wrappedNode = deconstructStatement(parameter);
var key;
for (key in wrappedNode) {
node[key] = wrappedNode[key];
}
}
});
// Assign node to the node's identifier
root[results[1]] = node;
return root;
}
3.) The only missing piece to the formula is the function that splits a string by only external commas - since I can't figure out a regex, here's splitStatementByExternalCommas.
function splitStatementByExternalCommas(statement) {
statement += ','; // so I don't have to handle the "last, leftover parameter"
var results = [];
var chars = statement.split('');
var level = 0; // levels of parenthesis, used to track how deep I am in ().
var index = 0; // determines which parameter am I currently on.
var temp = '';
// this is somewhat like your implementation in the edits, I walk through the characters one by one, and do something extra if it's a special character.
chars.forEach(function (char) {
switch (char) {
case '(':
temp += char;
level++;
break;
case ')':
temp += char;
level--;
break;
case ',':
// if the comma is between a set of parenthesis, ignore.
if (level !== 0) { temp += char; }
// if the comma is external, split the string.
else { results[index] = temp; temp = ''; index++; }
break;
default:
temp += char;
break;
}
});
return results;
}
4.) Putting everything together, to turn your string statement into an intermediate object:
var str = "a(b, c(e, f(h,i,j), g(k,l,m(o,p,q)) ), d(r,s,t))";
str = processStatement(str);
var root = produceNodeFromStatement(str);
// Output is something like:
{
a: {
b: null,
c: {
e: null,
f: { h: null, i: null, j: null },
g: {
k: null, l: null,
m: { o: null, p: null, q: null }
}
},
d: { r: null, s: null, t: null }
}
}
5.) I'm going to assume mapping this intermediate object to your intended target is straightforward from here on out?

You cannot use the same property name for the 3 values, so you cannot do this.
func:'c',
params:[
{
p:'e',
p:{
func:'f',
params:[
{p:'h'},
{p:'i'},
{p:'j'}
]
},
p:'g',
If we remove this part and fix other inconsistent parts of your example (at least try to write an example which is not a failure on its own), it's relative easy to transform your code into javascript with eval:
parser:
var parse = function (str) {
var compiled = str.replace(/(\w+)\s*(\W)/g, function (match, name, token) {
if (token == "(")
return "q(\"" + name + "\",";
else
return "p(\"" + name + "\")" + token;
}).replace(/,\s*\)/g, ")");
function q(name) {
return {
p: {
func: name,
params: Array.prototype.slice.call(arguments, 1)
}
};
}
function p(name) {
return {
p: name
};
}
var f = eval("(function (){return " + compiled + ";})");
return f().p;
};
test:
describe("x", function () {
it("y", function () {
var str = "a(b, c(e), d(r,s,t))";
var obj = {
func: 'a',
params: [
{p: "b"},
{
p: {
func: 'c',
params: [
{
p: 'e'
}
]
}
},
{
p: {
func: 'd',
params: [
{p: 'r'},
{p: 's'},
{p: 't'}
]
}
}
]
};
expect(parse(str)).toEqual(obj);
});
});
note:
I agree with Ira Baxter, you have to read more about how to do this in general.

Related

Store count of integers in order using javascript

I have string like the following:
11222233344444445666
What I would like to do is output the number followed the times it was displayed:
112433475163
Question is, I want this to be efficient. I can store this in an object as the following:
1: { id: 1, displayed: 2},
2: { id: 2, displayed: 1},
3: { id: 3, displayed: 2},
etc.
I can access this object and increment displayed.
My issues is, there is no guarantee in the order. I would like to store the keys in the order they are in the string. How do I accomplish the importance of the order in the object?
This is a proposal for run length coding with an array which holds infomation about one charcter and the count of it:
{
"char": "1",
"count": 2
},
var string = "11222233344444445666",
array = function () {
var r = [], o = {};
string.split('').forEach(function (a, i, aa) {
if (a !== aa[i - 1]) {
o[a] = { char: a, count: 0 };
r.push(o[a]);
}
o[a].count++;
});
return r;
}(string);
document.write('<pre>' + JSON.stringify(array, 0, 4) + '</pre>');
Quick solution with for loop:
var str = "7771122229933344444445666",
obj = {},
len = str.length,
val = null,
count_str = "",
key = "";
for (var i = 0; i < len; i++) {
val = str[i], key = 'k' + val;
if (!obj[key]) {
obj[key] = {'id': val, 'displayed': 1};
} else {
obj[key].displayed++;
}
}
for (var p in obj) {
count_str += obj[p]['id'] + obj[p]['displayed'];
}
console.log(count_str); // "7312249233475163"
because you have such a small set of distinct numbers, I seen no reason why you can't use a array (yeah it's not super ideal memorywise if you skip values and it becomes sparse, but for such a small subset it won't affect you enough to worry of it). Then you can use (number-1) as the index and increment that number as needed.
var counts = [];
var str = "11222233344444445666";
for(var i in str){
var index = parseInt(str[i])-1
counts[index] = (counts[index]||0)+1;
}
for(var i in counts){
var which = 1+parseInt(i);
var count = counts[i];
console.log("# of " + which +"'s: "+count);
}
https://jsfiddle.net/ga0fqpqn/
note: You shouldn't need the parseInt(i)... just +i should work but I think jsfiddle has a bug with it about it defaulting i to handle like a string.
You could store an additional array with the order of the numbers, which you only append to if the object doesn't yet contain the given number. Then once you're done counting, iterate through that array and output the number and the count from the lookup dictionary.
var chars = "1234576123452345".split("");
var order = [];
var hash = {};
chars.forEach(function(char) {
if (!hash[char]) {
hash[char] = 1;
order.push(char);
} else {
hash[char]++;
}
});
console.log(order.map(function(char) {
return char + hash[char];
}).join(""));
// "12233343537161"

Javascript How to split string by symbols count [duplicate]

As the title says, I've got a string and I want to split into segments of n characters.
For example:
var str = 'abcdefghijkl';
after some magic with n=3, it will become
var arr = ['abc','def','ghi','jkl'];
Is there a way to do this?
var str = 'abcdefghijkl';
console.log(str.match(/.{1,3}/g));
Note: Use {1,3} instead of just {3} to include the remainder for string lengths that aren't a multiple of 3, e.g:
console.log("abcd".match(/.{1,3}/g)); // ["abc", "d"]
A couple more subtleties:
If your string may contain newlines (which you want to count as a character rather than splitting the string), then the . won't capture those. Use /[\s\S]{1,3}/ instead. (Thanks #Mike).
If your string is empty, then match() will return null when you may be expecting an empty array. Protect against this by appending || [].
So you may end up with:
var str = 'abcdef \t\r\nghijkl';
var parts = str.match(/[\s\S]{1,3}/g) || [];
console.log(parts);
console.log(''.match(/[\s\S]{1,3}/g) || []);
If you didn't want to use a regular expression...
var chunks = [];
for (var i = 0, charsLength = str.length; i < charsLength; i += 3) {
chunks.push(str.substring(i, i + 3));
}
jsFiddle.
...otherwise the regex solution is pretty good :)
str.match(/.{3}/g); // => ['abc', 'def', 'ghi', 'jkl']
Building on the previous answers to this question; the following function will split a string (str) n-number (size) of characters.
function chunk(str, size) {
return str.match(new RegExp('.{1,' + size + '}', 'g'));
}
Demo
(function() {
function chunk(str, size) {
return str.match(new RegExp('.{1,' + size + '}', 'g'));
}
var str = 'HELLO WORLD';
println('Simple binary representation:');
println(chunk(textToBin(str), 8).join('\n'));
println('\nNow for something crazy:');
println(chunk(textToHex(str, 4), 8).map(function(h) { return '0x' + h }).join(' '));
// Utiliy functions, you can ignore these.
function textToBin(text) { return textToBase(text, 2, 8); }
function textToHex(t, w) { return pad(textToBase(t,16,2), roundUp(t.length, w)*2, '00'); }
function pad(val, len, chr) { return (repeat(chr, len) + val).slice(-len); }
function print(text) { document.getElementById('out').innerHTML += (text || ''); }
function println(text) { print((text || '') + '\n'); }
function repeat(chr, n) { return new Array(n + 1).join(chr); }
function textToBase(text, radix, n) {
return text.split('').reduce(function(result, chr) {
return result + pad(chr.charCodeAt(0).toString(radix), n, '0');
}, '');
}
function roundUp(numToRound, multiple) {
if (multiple === 0) return numToRound;
var remainder = numToRound % multiple;
return remainder === 0 ? numToRound : numToRound + multiple - remainder;
}
}());
#out {
white-space: pre;
font-size: 0.8em;
}
<div id="out"></div>
If you really need to stick to .split and/or .raplace, then use /(?<=^(?:.{3})+)(?!$)/g
For .split:
var arr = str.split( /(?<=^(?:.{3})+)(?!$)/ )
// [ 'abc', 'def', 'ghi', 'jkl' ]
For .replace:
var replaced = str.replace( /(?<=^(?:.{3})+)(?!$)/g, ' || ' )
// 'abc || def || ghi || jkl'
/(?!$)/ is to not stop at end of the string. Without it's:
var arr = str.split( /(?<=^(?:.{3})+)/ )
// [ 'abc', 'def', 'ghi', 'jkl' ] // is fine
var replaced = str.replace( /(?<=^(.{3})+)/g, ' || ')
// 'abc || def || ghi || jkl || ' // not fine
Ignoring group /(?:...)/ is to prevent duplicating entries in the array. Without it's:
var arr = str.split( /(?<=^(.{3})+)(?!$)/ )
// [ 'abc', 'abc', 'def', 'abc', 'ghi', 'abc', 'jkl' ] // not fine
var replaced = str.replace( /(?<=^(.{3})+)(?!$)/g, ' || ' )
// 'abc || def || ghi || jkl' // is fine
My solution (ES6 syntax):
const source = "8d7f66a9273fc766cd66d1d";
const target = [];
for (
const array = Array.from(source);
array.length;
target.push(array.splice(0,2).join(''), 2));
We could even create a function with this:
function splitStringBySegmentLength(source, segmentLength) {
if (!segmentLength || segmentLength < 1) throw Error('Segment length must be defined and greater than/equal to 1');
const target = [];
for (
const array = Array.from(source);
array.length;
target.push(array.splice(0,segmentLength).join('')));
return target;
}
Then you can call the function easily in a reusable manner:
const source = "8d7f66a9273fc766cd66d1d";
const target = splitStringBySegmentLength(source, 2);
Cheers
const chunkStr = (str, n, acc) => {
if (str.length === 0) {
return acc
} else {
acc.push(str.substring(0, n));
return chunkStr(str.substring(n), n, acc);
}
}
const str = 'abcdefghijkl';
const splittedString = chunkStr(str, 3, []);
Clean solution without REGEX
My favorite answer is gouder hicham's. But I revised it a little so that it makes more sense to me.
let myString = "Able was I ere I saw elba";
let splitString = [];
for (let i = 0; i < myString.length; i = i + 3) {
splitString.push(myString.slice(i, i + 3));
}
console.log(splitString);
Here is a functionalized version of the code.
function stringSplitter(myString, chunkSize) {
let splitString = [];
for (let i = 0; i < myString.length; i = i + chunkSize) {
splitString.push(myString.slice(i, i + chunkSize));
}
return splitString;
}
And the function's use:
let myString = "Able was I ere I saw elba";
let mySplitString = stringSplitter(myString, 3);
console.log(mySplitString);
And it's result:
>(9) ['Abl', 'e w', 'as ', 'I e', 're ', 'I s', 'aw ', 'elb', 'a']
try this simple code and it will work like magic !
let letters = "abcabcabcabcabc";
// we defined our variable or the name whatever
let a = -3;
let finalArray = [];
for (let i = 0; i <= letters.length; i += 3) {
finalArray.push(letters.slice(a, i));
a += 3;
}
// we did the shift method cause the first element in the array will be just a string "" so we removed it
finalArray.shift();
// here the final result
console.log(finalArray);
var str = 'abcdefghijkl';
var res = str.match(/.../g)
console.log(res)
here number of dots determines how many text you want in each word.
function chunk(er){
return er.match(/.{1,75}/g).join('\n');
}
Above function is what I use for Base64 chunking. It will create a line break ever 75 characters.
Here we intersperse a string with another string every n characters:
export const intersperseString = (n: number, intersperseWith: string, str: string): string => {
let ret = str.slice(0,n), remaining = str;
while (remaining) {
let v = remaining.slice(0, n);
remaining = remaining.slice(v.length);
ret += intersperseWith + v;
}
return ret;
};
if we use the above like so:
console.log(splitString(3,'|', 'aagaegeage'));
we get:
aag|aag|aeg|eag|e
and here we do the same, but push to an array:
export const sperseString = (n: number, str: string): Array<string> => {
let ret = [], remaining = str;
while (remaining) {
let v = remaining.slice(0, n);
remaining = remaining.slice(v.length);
ret.push(v);
}
return ret;
};
and then run it:
console.log(sperseString(5, 'foobarbaztruck'));
we get:
[ 'fooba', 'rbazt', 'ruck' ]
if someone knows of a way to simplify the above code, lmk, but it should work fine for strings.
Coming a little later to the discussion but here a variation that's a little faster than the substring + array push one.
// substring + array push + end precalc
var chunks = [];
for (var i = 0, e = 3, charsLength = str.length; i < charsLength; i += 3, e += 3) {
chunks.push(str.substring(i, e));
}
Pre-calculating the end value as part of the for loop is faster than doing the inline math inside substring. I've tested it in both Firefox and Chrome and they both show speedup.
You can try it here
Here's a way to do it without regular expressions or explicit loops, although it's stretching the definition of a one liner a bit:
const input = 'abcdefghijlkm';
// Change `3` to the desired split length.
const output = input.split('').reduce((s, c) => {
let l = s.length-1;
(s[l] && s[l].length < 3) ? s[l] += c : s.push(c);
return s;
}, []);
console.log(output); // output: [ 'abc', 'def', 'ghi', 'jlk', 'm' ]
It works by splitting the string into an array of individual characters, then using Array.reduce to iterate over each character. Normally reduce would return a single value, but in this case the single value happens to be an array, and as we pass over each character we append it to the last item in that array. Once the last item in the array reaches the target length, we append a new array item.
Some clean solution without using regular expressions:
/**
* Create array with maximum chunk length = maxPartSize
* It work safe also for shorter strings than part size
**/
function convertStringToArray(str, maxPartSize){
const chunkArr = [];
let leftStr = str;
do {
chunkArr.push(leftStr.substring(0, maxPartSize));
leftStr = leftStr.substring(maxPartSize, leftStr.length);
} while (leftStr.length > 0);
return chunkArr;
};
Usage example - https://jsfiddle.net/maciejsikora/b6xppj4q/.
I also tried to compare my solution to regexp one which was chosen as right answer. Some test can be found on jsfiddle - https://jsfiddle.net/maciejsikora/2envahrk/. Tests are showing that both methods have similar performance, maybe on first look regexp solution is little bit faster, but judge it Yourself.
var b1 = "";
function myFunction(n) {
if(str.length>=3){
var a = str.substring(0,n);
b1 += a+ "\n"
str = str.substring(n,str.length)
myFunction(n)
}
else{
if(str.length>0){
b1 += str
}
console.log(b1)
}
}
myFunction(4)
function str_split(string, length = 1) {
if (0 >= length)
length = 1;
if (length == 1)
return string.split('');
var string_size = string.length;
var result = [];
for (let i = 0; i < string_size / length; i++)
result[i] = string.substr(i * length, length);
return result;
}
str_split(str, 3)
Benchmark: http://jsben.ch/HkjlU (results differ per browser)
Results (Chrome 104)

Construct array from flattened string in JavaScript

JSFiddle: http://jsfiddle.net/3WdzL/1/
I need to convert locale JS object files to flattened versions and back again:
Orig locale object:
var localeObj = {
toolbar: {
link: {
back: 'Back',
menu: 'Menu',
},
flatTest: 'something'
},
countries: [
["AF", "Afghanistan"],
["AX", "Åland Islands"],
['nested', [1, 2, 3, 4]],
["AL", "Albania"]
]
};
Using the following function:
function flattenObj(obj) {
var flattenedObj = {};
var walk = function(obj, stringMap) {
for(k in obj) {
var computedKey = stringMap + (stringMap ? '.' + k : k);
if(typeof obj[k] !== 'object') {
flattenedObj[computedKey] = obj[k];
} else {
walk(obj[k], computedKey);
}
}
};
walk(obj, '');
return flattenedObj;
}
Would produce a flattened object:
{
toolbar.link.back: Back
toolbar.link.menu: Menu
toolbar.flatTest: something
countries.0.0: AF
countries.0.1: Afghanistan
countries.1.0: AX
countries.1.1: Åland Islands
countries.2.0: nested
countries.2.1.0: 1
countries.2.1.1: 2
countries.2.1.2: 3
countries.2.1.3: 4
countries.3.0: AL
countries.3.1: Albania
}
Converting back with the following func works fine for objects:
function deepenObj(obj) {
var deepenedObj = {}, tmp, parts, part;
for (var k in obj) {
tmp = deepenedObj;
parts = k.split('.');
var computedKey = parts.pop();
while (parts.length) {
part = parts.shift();
tmp = tmp[part] = tmp[part] || {};
}
tmp[computedKey] = obj[k];
}
return deepenedObj;
}
But produces a structure like this for the arrays:
region: {
country: {
0: {
0: 'AF',
1: 'Afghanistan'
},
...
2: {
0: 'nested',
1: {
0: 1,
1: 2,
3: 4,
4: 5
}
}
}
}
Obviously this isn't the desired results for the arrays and I haven't been able to come up with a safe, elegant or even working solution yet. PS I am happy to save the arrays to strings differently if it makes converting back easier. Thanks!
You should either keep track if an object is actually an array:
var walk = function(obj, stringMap) {
if (Array.isArray(obj) {
for (var k = 0; k < obj.length; k++)
var computedKey = stringMap ? stringMap + ',' + k : k;
} else {
for (var k in obj) {
var computedKey = stringMap ? stringMap + '.' + k : k;
...
Then, when deepening:
for (var k in obj) {
tmp = deepenedObj;
parts = ["."].concat(k.split(/([\.,])/));
var computedKey = parts.pop(), sign;
while (parts.length) {
sign = parts.shift();
part = !parts.length ? computedKey : parts.shift();
tmp = tmp[part] = tmp[part] || (sign === "," ? [] : {});
}
tmp[computedKey] = obj[k];
}
Note that Array.isArray could be undefined. You can use obj instanceof Array instead.
This solution works if localeObj is an object literal and not an array, because the first point/comma isn't saved in the computed key. You can modify the function if you need to.
The trick here is to use an unusual behaviour of split that pushes captured groups in the splitted array when used with regular expressions, so before every key part there's the proper separator.
Use JSON.stringify() and JSON.parse():
var flattenedObj = JSON.stringify(localeObj);
vat deepenedObj = JSON.parse(flattenedObj);
Demo

Javascript: Quickly lookup value in object (like we can with properties)

I have an object that has pairs of replacement values used for simple encoding / decoding (not for security, just for a convenience; too complicated to explain it all here). It's in the form
var obj = {x: y,
x: y,
...
};
where 'x' is the value when encoded and 'y' is the decoded value.
Decoding is simple: I loop through the characters of the string, and look up the charAt(i) value in the object via brackets: obj[ str.charAt(i) ]. (I'm leaving out the check to see whether we need an uppercase or lowercase version (all key/values in the object are lowercase), but that's simple enough.)
To encode, I of course have to look for the value in the object, rather than the property. Currently, I'm looping through the properties with a for ... in ... loop and checking the values against the charAt(i) value. My current code is:
var i, j,
output = '',
str = 'Hello World!',
obj = {'s':'d',
'm':'e',
'e':'h',
'x':'l',
'z':'o',
'i':'r',
'a':'w',
'o':'!',
'-':' '};
for (i = 0; i < str.length; i++) {
for (j in obj) {
if (Object.prototype.hasOwnProperty.call(obj, j) &&
Object.prototype.propertyIsEnumerable.call(obj, j)) {
if (obj[j] === str.charAt(i)) {
output += j;
break;
} else if (obj[j].toUpperCase() === str.charAt(i)) {
output += j.toUpperCase();
break;
}
}
}
}
alert(output);
I innately feel like there should be a more efficient way of doing this. (Of course having a reversed object, {y: x}, is an option. But not a good one.) Is this the best way, or is there a better? In essence, I'd love to be able to do var prop = obj[value] just like I can do var value = obj[prop].
It's more efficient to loop just once beforehand to create a reverse map:
var str = "Hello World!",
output = '',
map = {
"s":"d", "m":"e",
"e":"h", "x":"l",
"z":"o", "i":"r",
"a":"w", "o":"!",
"-":" "
},
reverseMap = {}
for (j in map){
if (!Object.prototype.hasOwnProperty.call(map, j)) continue
reverseMap[map[j]] = j
}
output = str.replace(/./g, function(c){
return reverseMap[c] || reverseMap[c.toLowerCase()].toUpperCase()
})
console.log(output)
Instead of doing str.length * map.length, you'll do map.length + str.length operations.
A reverse encoder would make more sense, but you can write a replace function without all the hasOwnProperty etc.tests.
var str= 'Hello World!',
obj={
's':'d',
'm':'e',
'e':'h',
'x':'l',
'z':'o',
'i':'r',
'a':'w',
'o':'!',
'-':' '
}
str= str.replace(/./g, function(w){
for(var p in obj){
if(obj[p]=== w) return p;
if(obj[p]=== w.toLowerCase()) return p.toUpperCase();
};
return w;
});
returned value: (String) Emxxz-Azixso
You can create a reversed version of the mapping programmatically (instead of by hand) and use it instead.
var rev = {}
for (key in obj)
rev[obj[key]] = key
If you're looking for array keys check here.
https://raw.github.com/kvz/phpjs/master/functions/array/array_keys.js
function array_keys (input, search_value, argStrict) {
var search = typeof search_value !== 'undefined', tmp_arr = [], strict = !!argStrict, include = true, key = '';
if (input && typeof input === 'object' && input.change_key_case) {
return input.keys(search_value, argStrict);
}
for (key in input) {
if (input.hasOwnProperty(key)) {
include = true;
if (search) {
if (strict && input[key] !== search_value) include = false;
else if (input[key] != search_value) include = false;
}
if (include) tmp_arr[tmp_arr.length] = key;
}
}
return tmp_arr;
}

What's the fastest way to interpret a nested javascript object attribute accessor string?

Say I have a nested object structure like:
var o = { a: { b: { c: 1 } } };
and I have a an accessor String like "a.b.c".
What's the fastest function to return the specified nested value (to any depth [1..n])?
I.e. in this case, getNested(o, 'a.b.c') === 1 and getNested(o, 'a') === {b:{c:1}}.
What's the best implementation of getNested?
one more variant:
function getNested(obj, path) {
path.replace(/[^\.]+/g, function (p) {
obj = obj[p];
});
return obj;
}
I hate to give an answer without profiling the code myself, but given that eval is probably slow, and forEach is slower than just running through a for-loop, I would start with:
// precondtion: path is a nonempty string
function getNested(obj, path) {
var fields = path.split(".");
var result = obj;
for (var i = 0, n = fields.length; i < n; i++) {
result = result[fields[i]];
}
return result;
}
But I would test this against other approaches.
I don't think that trying to optimize away the array construction on split would be worthwhile, but this is only one thing to try if you are interested in the fastest way.
ADDENDUM
Here is a transcript so you can see it in action:
$ node
> function getNested(obj, path) {
... var fields = path.split(".");
... var result = obj;
... for (var i = 0, n = fields.length; i < n; i++) {
... result = result[fields[i]];
... }
... return result;
... }
> var o = { a: { b: { c: 1 } } };
> getNested(o, "a")
{ b: { c: 1 } }
> getNested(o, "a.b.c")
1
** ADDENDUM 2 **
So embarassing -- I forgot the var in front of result before. That might speed it up a bit!
Other things to try:
Forget about the "optimization" with n and just do the for-loop test with i < test.length (might be optimized away anyway)
Replace split with substrings and indexOfs
Do the split with a regex /\./ instead of a raw string "."
Maybe something like this:
function getNested(o, path) {
var parts = path.split('.');
var member = o;
while (member && parts.length) {
member = member[parts.shift()];
}
return member;
}
It's probably not the fastest possible solution, but might be a useful starting point.
try{
var prop=eval(string);
}
catch(er){
prop=undefined;
}
My approach
var o = { a: { b: { c: 1 } } };
var getNested = function(p) {
var t;
p.split('.').forEach(function(e) {
t = o[e] || t[e]
});
return t
}
you can try:
getNested('a.b.c')

Categories