Finding all unique paths in tree structure - javascript

Let's say I have a directory structure laid out in a text file
root1
child1
child2
grandchild1
grandchild2
child3
root2
child1
child2
grandchild1
greatgrandchild1
How can I turn the above tree structure into a nested array that looks like this:
[
[ "root1", "child1" ],
[ "root1", "child2", "grandchild1" ],
[ "root1", "child2", "grandchild2" ],
[ "root1", "child3" ],
[ "root2", "child1" ],
[ "root2", "child2", "grandchild1", "greatgrandchild1" ]
]
edit
Getting further but still having issues walking through the tree recursively
var $text = ''
+ 'root1\n'
+ ' r1 child1\n'
+ ' r1 child2\n'
+ ' r1 grandchild1\n'
+ ' r1 grandchild2\n'
+ ' r1 child3\n'
+ 'root2\n'
+ ' r2 child1\n'
+ ' r2 c1\n'
+ ' r2 c1 g1\n'
+ ' r2 child2\n'
+ ' r2 grandchild1\n'
+ ' r2 greatgrandchild1\n'
+ 'test!\n'
+ 'root3\n'
+ ' r3 child1\n'
+ ' r3 c1\n'
+ ' r3 c1 g1\n'
+ ' r3 child3\n'
+ ' r3 grandchild1\n'
+ ' r3 greatgrandchild1';
var dirGen = (function(trees) {
"use strict";
var indent = /[\s\t]/g;
var lTrim = /[\s\t]*/;
var $trees = decompose(trees);
var $root = [];
function init() {
var paths = $trees.map(treeMap)
$test(paths);
}
function treeMap(tree, n, arr) {
var base = new LinkedList();
return bfs(-1, tree, base);
}
function bfs(n, tree, base) {
var l, t;
n++;
//base case
if (n === tree.length) return trails(base);
l = tree.length;
t = tree[n];
var cur = { label: t.replace(lTrim, ""), depth: depth(t), children: [] };
//set root
if (n === 0) {
base.tree = cur;
return bfs(n, tree, base);
}
base.push(cur);
return bfs(n, tree, base);
}
function depth(str) {
var d = str.match(indent);
if (d === null) return 0;
return d.length;
}
function trails(arr) {
return arr;
}
function LinkedList() {}
LinkedList.prototype.push = function(node) {
var l = this.tree.children.length;
var j = l - 1;
if (l === 0) {
this.tree.children.push(node);
return;
}
//walk through children array in reverse to get parent
while (j > -1) {
var d = this.tree.children[j].depth;
//child
if (node.depth > d) {
console.log(this.tree.children[j], node)
return this.tree.children[j].children.push(node);
}
//sibling
if (node.depth === d) {
}
j--;
}
}
function decompose(arr) {
var treeBreak = /[\r\n](?=\w)/gm;
var lines = /[\r\n]+(?!\s)*/g;
return arr.split(treeBreak).map(function(str) {
return str.split(lines)
});
}
function $test(str) {
var json = JSON.stringify(str, null, 2);
var wtf = "<pre>" + json + "</pre>";
document.write(wtf);
}
return init;
})($text);
dirGen();
The code so far gets me this json array:

I'm too lazy to read your algorithm :-|
function populateTree (tree, text) {
var rTab, rChunks, rChunk;
var chunks, chunk;
var i, l, node;
if (!text) return;
rTab = /^\s{4}/gm;
rChunks = /[\r\n]+(?!\s{4})/g;
rChunk = /^(.+)(?:[\r\n]+((?:\r|\n|.)+))?$/;
chunks = text.split(rChunks);
l = chunks.length;
for (i = 0; i < l; i++) {
chunk = chunks[i].match(rChunk);
node = { label: chunk[1], children: [] };
tree.children.push(node);
populateTree(node, chunk[2] && chunk[2].replace(rTab, ''));
}
}
function printTree(tree, prefix) {
var i, l = tree.children.length;
for (i = 0; i < l; i++) {
console.log(prefix + tree.children[i].label);
printTree(tree.children[i], prefix + ' ');
}
}
Usage:
var tree = { children: [] };
populateTree(tree, text);
printTree(tree, '');
I'm not familiar with Nodejs, I can only tell that it works in Chrome with this string:
var text = ''
+ 'root1\n'
+ ' child1\n'
+ ' child2\n'
+ ' grandchild1\n'
+ ' grandchild2\n'
+ ' child3\n'
+ 'root2\n'
+ ' child1\n'
+ ' child2\n'
+ ' grandchild1\n'
+ ' greatgrandchild1';

(Answering my own question)
Ok so the implementation actually has three parts: (1) converting the text file into a tree structure and then (2) using dfs on the tree to find the unique paths, and finally (3) merging all the paths into a single array.
First, the text to tree converter. You still need to find the depth (level of indentation) of each item because that's what determines if it is a child or sibling:
var treeGrapher = (function() {
"use strict";
var find = require("lodash.find");
var indent = /[\s\t]/g;
var lTrim = /[\s\t]*/;
var treeBreak = /[\r\n](?=\w)/gm;
var lines = /[^\r\n]+/g
function init(text) {
return decompose(text).map(function(tree) {
return populate(-1, tree, {})
});
}
function depth(str) {
var d = str.match(indent);
if (d === null) return 0;
return d.length;
}
function decompose(txt) {
return txt.split(treeBreak).map(function(str) {
return str.match(lines);
});
}
function populate(n, tree, root, cache, breadCrumbs) {
var branch, leaf, crumb;
//set index
n++;
//root case
if (n === tree.length) return root.tree;
branch = tree[n];
leaf = { label: branch.replace(lTrim, ""), index: n, depth: depth(branch), children: [] };
breadCrumbs = breadCrumbs || [];
crumb = cache ? { label: cache.label, index: cache.index, depth: cache.depth, node: cache } : undefined;
//set root
if (n === 0) {
root.tree = leaf;
return populate(n, tree, root, leaf, breadCrumbs);
}
//push child to cached parent from previous iteration
if (leaf.depth > cache.depth) {
cache.children.push(leaf);
root.parent = cache;
breadCrumbs.push(crumb)
return populate(n, tree, root, leaf, breadCrumbs);
}
//push child to distant parent via breadcrumb search
if (leaf.depth <= cache.depth) {
var rev = breadCrumbs.slice(0).reverse();
var parentNode = find(rev, function(obj){ return obj.depth < leaf.depth }).node;
parentNode.children.push(leaf);
return populate(n, tree, root, leaf, breadCrumbs);
}
}
return init;
})();
module.exports = treeGrapher;
Then, the dfs. This algorithm only searches one tree at a time so if your directory structure has multiple roots you need to put it in a loop.
var uniquePaths = (function() {
"use strict";
function init(tree) {
return walk(tree, [], []);
}
function walk(branch, path, basket) {
var fork = path.slice(0);
var i = 0;
var chld = branch.children;
var len = chld.length;
fork.push(branch.label);
if (len === 0) {
basket.push(fork);
return basket;
}
for (i; i < len; i++) walk(chld[i], fork, basket);
return basket;
}
return init;
})();
module.exports = uniquePaths;
Putting them together would look like this:
directory.tmpl.txt
root1
child1
child2
gc1
root2
root3
root3-child1
main.js
var fs = require("fs");
var treeGrapher = require("./lib/treeGrapher.js");
var uniquePaths = require("./lib/uniquePaths.js");
var tmpl = fs.readFileSync("./director.tmpl.txt", "utf8");
var graphs = treeGrapher(tmpl); //returns an array of trees
var paths = arrange(graphs);
/**
[
[ "root1", "rootchild1" ],
[ "root1", "child2", "gc1" ],
[ "root2" ],
[ "root3", "root3-child1" ]
]
*/
function arrange(trees) {
var bucket = [];
trees.forEach(function(list) {
uniquePaths(list).forEach(function(arr) {
bucket.push(arr);
});
});
return bucket;
}

Related

Problems with ReactJS and ExtJS3

I have page with ExtJS3 and ReactJS.
In order for the React to work in IE11 i use #babel/polifyll
In turn, Babel uses core-js which has this method:
"use strict";
var LIBRARY = __webpack_require__(/*! ./_library */ "./node_modules/core-js/library/modules/_library.js");
var $export = __webpack_require__(/*! ./_export */ "./node_modules/core-js/library/modules/_export.js");
var redefine = __webpack_require__(/*! ./_redefine */ "./node_modules/core-js/library/modules/_redefine.js");
var hide = __webpack_require__(/*! ./_hide */ "./node_modules/core-js/library/modules/_hide.js");
var Iterators = __webpack_require__(/*! ./_iterators */ "./node_modules/core-js/library/modules/_iterators.js");
var $iterCreate = __webpack_require__(/*! ./_iter-create */ "./node_modules/core-js/library/modules/_iter-create.js");
var setToStringTag = __webpack_require__(/*! ./_set-to-string-tag */ "./node_modules/core-js/library/modules/_set-to-string-tag.js");
var getPrototypeOf = __webpack_require__(/*! ./_object-gpo */ "./node_modules/core-js/library/modules/_object-gpo.js");
var ITERATOR = __webpack_require__(/*! ./_wks */ "./node_modules/core-js/library/modules/_wks.js")('iterator');
var BUGGY = !([].keys && 'next' in [].keys()); // Safari has buggy iterators w/o `next`
var FF_ITERATOR = '##iterator';
var KEYS = 'keys';
var VALUES = 'values';
module.exports = function (Base, NAME, Constructor, next, DEFAULT, IS_SET, FORCED) {
$iterCreate(Constructor, NAME, next);
var getMethod = function (kind) {
if (!BUGGY && kind in proto) return proto[kind];
switch (kind) {
case KEYS: return function keys() { return new Constructor(this, kind); };
case VALUES: return function values() { return new Constructor(this, kind); }; //AT THIS POINT
} return function entries() { return new Constructor(this, kind); };
}
//SOME PARTS OF THIS METHOD
};
in case VALUES: return function values() { return new Constructor(this, kind); }; i have something which make something and this part of code broke ExtJS3 methods like this:
Ext.XTemplate = function(){
Ext.XTemplate.superclass.constructor.apply(this, arguments);
var me = this,
s = me.html,
re = /<tpl\b[^>]*>((?:(?=([^<]+))\2|<(?!tpl\b[^>]*>))*?)<\/tpl>/,
nameRe = /^<tpl\b[^>]*?for="(.*?)"/,
ifRe = /^<tpl\b[^>]*?if="(.*?)"/,
execRe = /^<tpl\b[^>]*?exec="(.*?)"/,
m,
id = 0,
tpls = [],
VALUES = 'values',
PARENT = 'parent',
XINDEX = 'xindex',
XCOUNT = 'xcount',
RETURN = 'return ',
WITHVALUES = 'with(values){ ';
s = ['<tpl>', s, '</tpl>'].join('');
while((m = s.match(re))){
var m2 = m[0].match(nameRe),
m3 = m[0].match(ifRe),
m4 = m[0].match(execRe),
exp = null,
fn = null,
exec = null,
name = m2 && m2[1] ? m2[1] : '';
if (m3) {
exp = m3 && m3[1] ? m3[1] : null;
if(exp){
fn = new Function(VALUES, PARENT, XINDEX, XCOUNT, WITHVALUES + RETURN +(Ext.util.Format.htmlDecode(exp))+'; }');
}
}
if (m4) {
exp = m4 && m4[1] ? m4[1] : null;
if(exp){
exec = new Function(VALUES, PARENT, XINDEX, XCOUNT, WITHVALUES +(Ext.util.Format.htmlDecode(exp))+'; }');
}
}
if(name){
switch(name){
case '.': name = new Function(VALUES, PARENT, WITHVALUES + RETURN + VALUES + '; }'); break;
case '..': name = new Function(VALUES, PARENT, WITHVALUES + RETURN + PARENT + '; }'); break;
default: name = new Function(VALUES, PARENT, WITHVALUES + RETURN + name + '; }');
}
}
tpls.push({
id: id,
target: name,
exec: exec,
test: fn,
body: m[1]||''
});
s = s.replace(m[0], '{xtpl'+ id + '}');
++id;
}
Ext.each(tpls, function(t) {
me.compileTpl(t);
});
me.master = tpls[tpls.length-1];
me.tpls = tpls;
};
Ext.extend(Ext.XTemplate, Ext.Template, {
// private
re : /\{([\w-\.\#]+)(?:\:([\w\.]*)(?:\((.*?)?\))?)?(\s?[\+\-\*\\]\s?[\d\.\+\-\*\\\(\)]+)?\}/g,
// private
codeRe : /\{\[((?:\\\]|.|\n)*?)\]\}/g,
// private
applySubTemplate : function(id, values, parent, xindex, xcount){
var me = this,
len,
t = me.tpls[id],
vs,
buf = [];
if ((t.test && !t.test.call(me, values, parent, xindex, xcount)) ||
(t.exec && t.exec.call(me, values, parent, xindex, xcount))) {
return '';
}
vs = t.target ? t.target.call(me, values, parent) : values;
len = vs.length;
parent = t.target ? values : parent;
if(t.target && Ext.isArray(vs)){
Ext.each(vs, function(v, i) {
buf[buf.length] = t.compiled.call(me, v, parent, i+1, len);
});
return buf.join('');
}
return t.compiled.call(me, vs, parent, xindex, xcount);
},
// private
compileTpl : function(tpl){
var fm = Ext.util.Format,
useF = this.disableFormats !== true,
sep = Ext.isGecko ? "+" : ",",
body;
function fn(m, name, format, args, math){
if(name.substr(0, 4) == 'xtpl'){
return "'"+ sep +'this.applySubTemplate('+name.substr(4)+', values, parent, xindex, xcount)'+sep+"'";
}
var v;
if(name === '.'){
v = 'values';
}else if(name === '#'){
v = 'xindex';
}else if(name.indexOf('.') != -1){
v = name;
}else{
v = "values['" + name + "']";
}
if(math){
v = '(' + v + math + ')';
}
if (format && useF) {
args = args ? ',' + args : "";
if(format.substr(0, 5) != "this."){
format = "fm." + format + '(';
}else{
format = 'this.call("'+ format.substr(5) + '", ';
args = ", values";
}
} else {
args= ''; format = "("+v+" === undefined ? '' : ";
}
return "'"+ sep + format + v + args + ")"+sep+"'";
}
function codeFn(m, code){
return "'"+ sep +'('+code+')'+sep+"'";
}
// branched to use + in gecko and [].join() in others
if(Ext.isGecko){
body = "tpl.compiled = function(values, parent, xindex, xcount){ return '" +
tpl.body.replace(/(\r\n|\n)/g, '\\n').replace(/'/g, "\\'").replace(this.re, fn).replace(this.codeRe, codeFn) +
"';};";
}else{
body = ["tpl.compiled = function(values, parent, xindex, xcount){ return ['"];
body.push(tpl.body.replace(/(\r\n|\n)/g, '\\n').replace(/'/g, "\\'").replace(this.re, fn).replace(this.codeRe, codeFn));
body.push("'].join('');};");
body = body.join('');
}
eval(body);
return this;
},
applyTemplate : function(values){
return this.master.compiled.call(this, values, {}, 1, 1);
},
compile : function(){return this;}
});
Ext.XTemplate.prototype.apply = Ext.XTemplate.prototype.applyTemplate;
Ext.XTemplate.from = function(el){
el = Ext.getDom(el);
return new Ext.XTemplate(el.value || el.innerHTML);
};/*!
On line 92 in vs variable i have this method function values() { return new Constructor(this, kind); }; from core-js.
I don't know what i need to do, because we need a several months (maybe year) to stay with ExtJS3 but many things in ExtJS3 not work.
EDIT
Also no one have access to ExtJS3 for make software crutch
Hard fix:
just create fix.sh file, put it in a root of project along with webpack config and put this source to fix.sh:
sed -i 's/getMethod/_getMethod/g' target/application/static/js/*.js && sed -i 's/KEYS/_KEYS/g' target/application/static/js/*.js &&
sed -i 's/VALUES/_VALUES/g' target/application/static/js/*.js &&
sed -i 's/keys/_keys/g' target/application/static/js/*.js &&
sed -i 's/values/_values/g' target/application/static/js/*.js
also replace target/application/static/js path to yours.
Add WebpackShellPlugin and in webpack add this:
plugins: [
new WebpackShellPlugin({
onBuildEnd: ['bash ./fix.sh']
})
]

Create Array of Objects and count number of occurrence

I have an array of objects and want to create another array of objects based on.
I want to check if an object is repeated just want to show the count, otherwise show the object itself with count = 1.
<!-- I have an array-->
var arr =[{name:"coke",price:20},{name:"coke",price:20},{name:"coke",price:20},{name:"kabab",price:250}];
// I want to create another array based on "arr" like the one below
var test =[{name:"coke",price:20,count:3},{name:"kabab",price:20,count:1}];
//Any hint please
This may help you. This answer considers name or some identifier will be unique for each object.
counter = {}
var arr = [{
name: "coke",
price: 20
}, {
name: "coke",
price: 20
}, {
name: "coke",
price: 20
}, {
name: "kabab",
price: 250
}];
var obj = {};
var counter = {}
for (var i = 0, len = arr.length; i < len; i++) {
obj[arr[i]['name']] = arr[i];
counter[arr[i]['name']] = (counter[arr[i]['name']] || 0) + 1
}
newArr = new Array();
for (var key in obj){
newArr.push(extend( obj[key], {count:counter[key]}));
}
function extend(a, b){
for(var key in b)
if(b.hasOwnProperty(key))
a[key] = b[key];
return a;
}
console.log(newArr)
var arr =[{name:"coke",price:20},{name:"coke",price:20},{name:"coke",price:20},{name:"kabab",price:250}];
var countNameMapping = {}, finalArr = [];
var arrLength = arr.length;
for(i=0; i<arrLength; i++){
var tempObj = {name:arr[i], price:arr[i].price, occurance:1};
var productName = arr[i].name;
if(countNameMapping[productName] === undefined){
countNameMapping[productName] = tempObj;
}else{
countNameMapping[productName].occurance += 1;
}
}
for(var k in countNameMapping){
finalArr.push(countNameMapping[k])
}
console.log(finalArr );
You can try this one:
var arr =[{name:"coke",price:20},{name:"coke",price:20},{name:"coke",price:20},{name:"kabab",price:250}];
var result = [];
arr.map(function(arrObject) {
if (result.length > 0) {
result.map(function(resultObject) {
if (resultObject.name != arrObject.name) {
arrObject.count = 1;
result.push(arrObject);
} else {
resultObject.count++;
}
})
} else {
arrObject.count = 1;
result.push(arrObject);
}
})
console.log(result);
This will provide the result you are looking for:
var arr =[{name:"coke",price:20},{name:"coke",price:20},{name:"coke",price:20},{name:"kabab",price:250}];
var map = arr.reduce((accum, item) => {
var obj = accum.get(item.name) || Object.assign({}, item, {count:0});
obj.count++;
return accum.set(item.name, obj);
}, new Map());
var res = [...map.values()];
More or less...
var arr = [{
name: "coke",
price: 20
}, {
name: "coke",
price: 20
}, {
name: "coke",
price: 20
}, {
name: "kabab",
price: 250
}];
// I want to create another array based on "arr" like the one below
// var test =[{name:"coke",price:20,count:3},{name:"kabab",price:20,count:1}];
var count = {};
var test = [];
for (var i = 0, len = arr.length; i < len; i++) {
var id = JSON.stringify(arr[i]);
if (count.hasOwnProperty(id)) {
count[id].count++;
} else {
test.push(arr[i]); // Data contamination. Too lazy to copy object
count[id] = test[test.length - 1]; // Could be better.
count[id].count = 1;
}
}
console.log(test);
This is probably what are you looking for:
How does it work?
First, your array arr will use a forEach loop to find each object and if if new you will add it to the results array. The method isNew() will return true if the object is new.
For each new object founded you will count the number of occurrences using findOccurrences() To reduce the number of "loops" you will slice the array according to the index. So you don't need to search again over the already processed data.
So now you can build an new object, using the name, price and count.
Finally, you can push() the new object to the results array.
var arr =[{name:"coke",price:20},{price:20,name:"coke"},{name:"coke",price:20},{name:"kabab",price:250}];
var results = [];
var index = 0;
var originalDiv = document.getElementById('original');
var resultsDiv = document.getElementById('results');
arr.forEach(function(obj) {
if (isNew(obj)) {
var counter = findOccurrences(obj, arr.slice(index, arr.length));
var newObj = {
name: obj.name,
price: obj.price,
count: counter
}
results.push(newObj);
}
index++;
});
printArray(arr, originalDiv);
printArray(results, resultsDiv);
function isNew(newObj) {
var wasFound = true;
if (typeof results != "undefined" && results != null && results.length > 0) {
results.forEach(function(obj) {
if (newObj.name === obj.name && newObj.price === obj.price) {
return false;
} else {
wasFound = false;
}
});
return !wasFound;
} else {
return true;
}
}
function findOccurrences(newObj, objects) {
var count = 0;
if (typeof objects != "undefined" && objects != null && objects.length > 0) {
objects.forEach(function(obj) {
if (newObj.name === obj.name && newObj.price === obj.price) {
count++;
}
});
}
return count;
}
function printArray(objects, div) {
var count = 0;
if (typeof objects != "undefined" && objects != null && objects.length > 0) {
objects.forEach(function(obj) {
var newElement = document.createElement('p');
newElement.innerHTML = 'item ' + count + ': ';
Object.keys(obj).forEach(function(key) {
newElement.innerHTML += key + ': ' + obj[key] + ', ';
});
newElement.innerHTML = newElement.innerHTML.slice(0, -2);
div.appendChild(newElement);
count++;
});
}
}
<div id="original"><p>Original Array</p></div>
<div id="results"><p>Results Array</p></div>
Update:
More optimization.
var arr =[{name:"coke",price:20},{name:"coke",price:20},{name:"coke",price:20},{name:"kabab",price:250},{name:"coke",price:20},{name:"coke",price:20},{name:"kabab",price:250}];
var accumulator = {};
var results = [];
var index = 0;
var originalDiv = document.getElementById('original');
var resultsDiv = document.getElementById('results');
String.prototype.hashCode = function() {
var hash = 0;
if (this.length == 0) return hash;
for (i = 0; i < this.length; i++) {
var char = this.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash |= 0; // Convert to 32bit integer
}
var c = (hash & 0x0FFFFFFF)
.toString(16)
.toUpperCase();
return '0000000'.substring(0, 7 - c.length) + c;
};
arr.forEach(function(obj) {
var id = JSON.stringify(obj).hashCode();
console.log(id);
if (accumulator.hasOwnProperty(id)) {
accumulator[id].count++;
} else {
results.push(obj);
accumulator[id] = results[results.length - 1];
accumulator[id].count = 1;
}
});
printArray(arr, originalDiv);
printArray(results, resultsDiv);
function printArray(objects, div) {
var count = 0;
if (typeof objects != "undefined" && objects != null && objects.length > 0) {
objects.forEach(function(obj) {
var newElement = document.createElement('p');
newElement.innerHTML = 'item ' + count + ': ';
Object.keys(obj).forEach(function(key) {
newElement.innerHTML += key + ': ' + obj[key] + ', ';
});
newElement.innerHTML = newElement.innerHTML.slice(0, -2);
div.appendChild(newElement);
count++;
});
}
}
<div id="original">
<p>Original Array</p>
</div>
<div id="results">
<p>Results Array</p>
</div>

Convert to CSV in a downloadable format

I want to convert an array of objects into CSV.
Please have a look at the code. What I am doing wrong? I am getting an array when I console.log("csv",array) which is passed from printAccountsGa. Moreover, I have declared JSONGA = [] globally but still I get array with every object on consoling. But when I pass that to csvConverter function it displays undefined.
function printAccountsGa(results) {
if (results && !results.error) {
var accounts = results.items;
for (var i = 0, account; account = accounts[i]; i++) {
//console.log('Account Id: ' + account.id);
listProperties(account.id);
//console.log('Account Name: ' + account.name);
}
console.log(JSONGA);
csvConverter(JSONGA);
}
}
function printAccountsGtm(results) {
console.log("result is",results);
if (results && !results.error) {
var accounts = results.accounts;
console.log(accounts);
for (var i = 0, account; account = accounts[i]; i++) {
//console.log('Account Id: ' + account.accountId);
listContainers(account.accountId);
//console.log('Account Name: ' + account.name);
}
}
}
function listProperties(accountID){
var request = gapi.client.analytics.management.webproperties.list({
'accountId': accountID
});
request.execute(printProperties);
}
function printProperties(results){
if (results && !results.error) {
var properties = results.items;
//console.log(properties[0].accountId);
$.each(properties,function(element){
//console.log(properties[element].accountId);
var tempObj={};
tempObj={
'AccountID':properties[element].accountId,
'PropertyID':properties[element].id,
'name':properties[element].name,
}
JSONGA.push(tempObj);
})
}
}
function csvConverter(array){
console.log("csv",array);
var result = array.map(function(d){
return JSON.stringify(values(d));
})
.join('\n')
.replace(/(^\[)|(\]$)/mg, '');
window.open('data:text/csv;charset=utf-8,' + escape(result));
}
window.onload = function(){
var el = document.getElementById('auth-button-ga');
el.addEventListener('click', authorizeGa);
var elt = document.getElementById("auth-button-gtm");
elt.addEventListener('click', authorizeGtm);
}

Code Challenge: Rename filenames if duplicates are present

I'm working through a coding challenge I found online. I have the first test case passing but failing the second. I'm trying to decide if the second test case I'm failing is a typo or not.
Here is the question:
You are given an array of desired filenames in the order of their
creation. Since two files cannot have equal names, the one which comes
later will have an addition to its name in a form of (k), where k is
the smallest positive integer such that the obtained name is not used
yet.
Return an array of names that will be given to the files.
Test Cases:
1 - Passing:
INPUT: ["doc", "doc", "image", "doc(1)", "doc"]
OUTPUT: ["doc", "doc(1)", "image", "doc(1)(1)", "doc(2)"]
2 - Failing:
INPUT: ["a(1)","a(6)","a","a","a","a","a","a","a","a","a","a"]
OUTPUT: ["a(1)","a(6)","a","a(2)","a(3)","a(4)","a(5)","a(7)","a(8)","a(9)","a(10)","a(11)"]
Here is my code that passes the first Spec:
function fileNaming(names) {
var finalArr = [],
obj = {};
names.forEach(function(val){
if(obj[val] === undefined){
if(finalArr.indexOf(val) === -1){
finalArr.push(val);
obj[val] = 0;
} else {
obj[val] = 1;
finalArr.push(val + "(" + obj[val] + ")" );
}
} else {
finalArr.push( val + "(" + (++obj[val]) + ")");
}
});
return finalArr;
}
Question:
In the second test spec, why isn't there an "a(1)(1)" like there is a "doc(1)(1)" Is this a typo?
If anyone has suggestions on improvining my approach or alternative approaches I would greatly appreciate your feedback.
Here's a simpler approach. The idea is to store both the original and the generated name in the hashtable:
f = function(xs) {
var c = {}, t = (x, n) => x + "(" + n + ")";
return xs.map(function(x) {
var n = c[x] || 0;
c[x] = n + 1;
if(!n)
return x;
while(c[t(x, n)])
n++;
c[t(x, n)] = 1;
return t(x, n);
});
};
q = ["doc", "doc", "image", "doc(1)", "doc", "doc"];
document.write('<pre>'+JSON.stringify(f(q)));
q = ["a(1)","a(6)","a","a","a","a","a","a","a","a","a","a"]
document.write('<pre>'+JSON.stringify(f(q)));
Here's my approach :
def fileNaming(names):
uniq = []
for i in range(len(names)):
if names[i] not in uniq:
uniq.append(names[i])
else:
k = 1
while True:
if (names[i] + "(" + str(k) + ")") in uniq:
k += 1
else:
uniq.append(names[i] + "(" + str(k) + ")")
break
return uniq
with arrray approch
//var arr=["doc", "doc", "image", "doc(1)", "doc"];
var arr=["a(1)","a(6)","a","a","a","a","a","a","a","a","a","a"];
var arr1=new Array();
for (var r in arr)
{
if(arr1.indexOf(arr[r])>-1)
{
var ind=1;
while(arr1.indexOf(arr[r]+'('+ind+')')>-1)
{
ind++;
}
var str=arr[r]+'('+ind+')';
arr1.push(str);
}
else
{
arr1.push(arr[r]);
}
}
document.write("INPUT:"+arr+"</br>");
document.write("OUTPUT:"+arr1);
This was my beginner approach:
const renameFiles = arr => {
const fileObj = {};
let count = 0;
const renamed = arr.map(currentFile => {
if (!Object.keys(fileObj).includes(currentFile)) {
fileObj[currentFile] = count;
return currentFile;
} else {
count++;
if (Object.keys(fileObj).includes(`${currentFile}(${count})`)) {
count++;
return `${currentFile}(${count})`;
} else return `${currentFile}(${count})`;
}
});
return renamed;
};
Here is a working c++ impl.
#include <map>
#include <string>
using namespace std;
string makeName(string n, int i)
{
string ret = n + "(";
ret += std::to_string(i);
ret += ")";
return ret;
}
std::vector<std::string> fileNaming(std::vector<std::string> names)
{
map<string, int> lookup;
vector<string> outNames;
for (auto name : names)
{
auto f = lookup.find(name);
if (f != lookup.end())
{
int index = 1;
while (lookup.find(makeName(name, index)) != lookup.end())
{
index++;
}
name = makeName(name, index); // reassign
}
lookup[name] = 1;
outNames.push_back(name);
}
return outNames;
}
Here is my approach in Javascript:
function fileNaming(names) {
for (i in names) {
if (names.slice(0,i).includes(names[i])) {
j = 1
while (names.slice(0,i).includes(names[i]+"("+j.toString()+")")) {j++}
names[i] += "(" + j.toString() + ")"}}
return names
}

Javascript: Determine unknown array length and map dynamically

Going to do my best at explaining what I am trying to do.
I have two models, mine and an api response I am receiving. When the items api response comes in, I need to map it to my model and inserts all the items. This is simple of course. Heres the issue, I need to do so without really knowing what I am dealing with. My code will be passed in two strings, one of my models mapping path and one of the api response mapping path.
Here are the two paths
var myPath = "outputModel.items[].uniqueName"
var apiPath = "items[].name"
Basically FOR all items in apiPath, push into items in myPath and set to uniqueName
What it comes down to is that my code has NO idea when two items need to be mapped, or even if they contain an array or simple field to field paths. They could even contain multiple arrays, like this:
******************** EXAMPLE *************************
var items = [
{
name: "Hammer",
skus:[
{num:"12345qwert"}
]
},
{
name: "Bike",
skus:[
{num:"asdfghhj"},
{num:"zxcvbn"}
]
},
{
name: "Fork",
skus:[
{num:"0987dfgh"}
]
}
]
var outputModel = {
storeName: "",
items: [
{
name: "",
sku:""
}
]
};
outputModel.items[].name = items[].name;
outputModel.items[].sku = items[].skus[].num;
************************ Here is the expected result of above
var result = {
storeName: "",
items: [
{
name: "Hammer",
sku:"12345qwert"
},
{
name: "Bike",
sku:"asdfghhj"
},
{
name: "Bike",
sku:"zxcvbn"
},
{
name: "Fork",
sku:"0987dfgh" }
]
};
I will be given a set of paths for EACH value to be mapped. In the case above, I was handed two sets of paths because I am mapping two values. It would have to traverse both sets of arrays to create the single array in my model.
Question - How can I dynamically detect arrays and move the data around properly no matter what the two model paths look like? Possible?
So you have defined a little language to define some data addressing and manipulation rules. Let's think about an approach which will allow you to say
access(apiPath, function(value) { insert(myPath, value); }
The access function finds all the required items in apiPath, then calls back to insert, which inserts them into myPath. Our job is to write functions which create the access and insert functions; or, you could say, "compile" your little language into functions we can execute.
We will write "compilers" called make_accessor and make_inserter, as follows:
function make_accessor(program) {
return function(obj, callback) {
return function do_segment(obj, segments) {
var start = segments.shift() // Get first segment
var pieces = start.match(/(\w+)(\[\])?/); // Get name and [] pieces
var property = pieces[1];
var isArray = pieces[2]; // [] on end
obj = obj[property]; // drill down
if (!segments.length) { // last segment; callback
if (isArray) {
return obj.forEach(callback);
} else {
return callback(obj);
}
} else { // more segments; recurse
if (isArray) { // array--loop over elts
obj.forEach(function(elt) { do_segment(elt, segments.slice()); });
} else {
do_segment(obj, segments.slice()); // scalar--continue
}
}
}(obj, program.split('.'));
};
}
We can now make an accessor by calling make_accessor('items[].name').
Next, let's write the inserter:
function make_inserter(program) {
return function(obj, value) {
return function do_segment(obj, segments) {
var start = segments.shift() // Get first segment
var pieces = start.match(/(\w+)(\[\])?/); // Get name and [] pieces
var property = pieces[1];
var isArray = pieces[2]; // [] on end
if (segments.length) { // more segments
if (!obj[property]) {
obj[property] = isArray ? [] : {};
}
do_segment(obj, segments.slice());
} else { // last segment
obj[property] = value;
}
}(obj, program.split('.'));
};
}
Now, you can express your whole logic as
access = make_accessor('items[].name');
insert = make_inserter('outputModel.items[].uniqueName');
access(apiPath, function(val) { insert(myPath, val); });
As mentioned in the comments, there is no strict definition of the input format, it is hard to do it with perfect error handling and handle all corner cases.
Here is my lengthy implementation that works on your sample, but might fail for some other cases:
function merge_objects(a, b) {
var c = {}, attr;
for (attr in a) { c[attr] = a[attr]; }
for (attr in b) { c[attr] = b[attr]; }
return c;
}
var id = {
inner: null,
name: "id",
repr: "id",
type: "map",
exec: function (input) { return input; }
};
// set output field
function f(outp, mapper) {
mapper = typeof mapper !== "undefined" ? mapper : id;
var repr = "f("+outp+","+mapper.repr+")";
var name = "f("+outp;
return {
inner: mapper,
name: name,
repr: repr,
type: "map",
clone: function(mapper) { return f(outp, mapper); },
exec:
function (input) {
var out = {};
out[outp] = mapper.exec(input);
return out;
}
};
}
// set input field
function p(inp, mapper) {
var repr = "p("+inp+","+mapper.repr+")";
var name = "p("+inp;
return {
inner: mapper,
name: name,
repr: repr,
type: mapper.type,
clone: function(mapper) { return p(inp, mapper); },
exec: function (input) {
return mapper.exec(input[inp]);
}
};
}
// process array
function arr(mapper) {
var repr = "arr("+mapper.repr+")";
return {
inner: mapper,
name: "arr",
repr: repr,
type: mapper.type,
clone: function(mapper) { return arr(mapper); },
exec: function (input) {
var out = [];
for (var i=0; i<input.length; i++) {
out.push(mapper.exec(input[i]));
}
return out;
}
};
}
function combine(m1, m2) {
var type = (m1.type == "flatmap" || m2.type == "flatmap") ? "flatmap" : "map";
var repr = "combine("+m1.repr+","+m2.repr+")";
return {
inner: null,
repr: repr,
type: type,
name: "combine",
exec:
function (input) {
var out1 = m1.exec(input);
var out2 = m2.exec(input);
var out, i, j;
if (m1.type == "flatmap" && m2.type == "flatmap") {
out = [];
for (i=0; i<out1.length; i++) {
for (j=0; j<out2.length; j++) {
out.push(merge_objects(out1[i], out2[j]));
}
}
return out;
}
if (m1.type == "flatmap" && m2.type != "flatmap") {
out = [];
for (i=0; i<out1.length; i++) {
out.push(merge_objects(out1[i], out2));
}
return out;
}
if (m1.type != "flatmap" && m2.type == "flatmap") {
out = [];
for (i=0; i<out2.length; i++) {
out.push(merge_objects(out2[i], out1));
}
return out;
}
return merge_objects(out1, out2);
}
};
}
function flatmap(mapper) {
var repr = "flatmap("+mapper.repr+")";
return {
inner: mapper,
repr: repr,
type: "flatmap",
name: "flatmap",
clone: function(mapper) { return flatmap(mapper); },
exec:
function (input) {
var out = [];
for (var i=0; i<input.length; i++) {
out.push(mapper.exec(input[i]));
}
return out;
}
};
}
function split(s, t) {
var i = s.indexOf(t);
if (i == -1) return null;
else {
return [s.slice(0, i), s.slice(i+2, s.length)];
}
}
function compile_one(inr, outr) {
inr = (inr.charAt(0) == ".") ? inr.slice(1, inr.length) : inr;
outr = (outr.charAt(0) == ".") ? outr.slice(1, outr.length) : outr;
var box = split(inr, "[]");
var box2 = split(outr, "[]");
var m, ps, fs, i, j;
if (box == null && box2 == null) { // no array!
m = id;
ps = inr.split(".");
fs = outr.split(".");
for (i=0; i<fs.length; i++) { m = f(fs[i], m); }
for (j=0; j<ps.length; j++) { m = p(ps[j], m); }
return m;
}
if (box != null && box2 != null) { // array on both sides
m = arr(compile_one(box[1], box2[1]));
ps = box[0].split(".");
fs = box[0].split(".");
for (i=0; i<fs.length; i++) { m = f(fs[i], m); }
for (j=0; j<ps.length; j++) { m = p(ps[j], m); }
return m;
}
if (box != null && box2 == null) { // flatmap
m = flatmap(compile_one(box[1], outr));
ps = box[0].split(".");
for (j=0; j<ps.length; j++) { m = p(ps[j], m); }
return m;
}
return null;
}
function merge_rules(m1, m2) {
if (m1 == null) return m2;
if (m2 == null) return m1;
if (m1.name == m2.name && m1.inner != null) {
return m1.clone(merge_rules(m1.inner, m2.inner));
} else {
return combine(m1, m2);
}
}
var input = {
store: "myStore",
items: [
{name: "Hammer", skus:[{num:"12345qwert"}]},
{name: "Bike", skus:[{num:"asdfghhj"}, {num:"zxcvbn"}]},
{name: "Fork", skus:[{num:"0987dfgh"}]}
]
};
var m1 = compile_one("items[].name", "items[].name");
var m2 = compile_one("items[].skus[].num", "items[].sku");
var m3 = compile_one("store", "storeName");
var m4 = merge_rules(m3,merge_rules(m1, m2));
var out = m4.exec(input);
alert(JSON.stringify(out));
I have borrowed earlier answer and made improvements so as to solve both your examples and this should be generic. Though if you plan to run this sequencially with 2 sets of inputs, then the behavior will be as I have outlined in my comments to your original question.
var apiObj = {
items: [{
name: "Hammer",
skus: [{
num: "12345qwert"
}]
}, {
name: "Bike",
skus: [{
num: "asdfghhj"
}, {
num: "zxcvbn"
}]
}, {
name: "Fork",
skus: [{
num: "0987dfgh"
}]
}]
};
var myObj = { //Previously has values
storeName: "",
items: [{
uniqueName: ""
}],
outputModel: {
items: [{
name: "Hammer"
}]
}
};
/** Also works with this **
var myPath = "outputModel.items[].uniqueName";
var apiPath = "items[].name";
*/
var myPath = "outputModel.items[].sku";
var apiPath = "items[].skus[].num";
function make_accessor(program) {
return function (obj, callback) {
(function do_segment(obj, segments) {
var start = segments.shift() // Get first segment
var pieces = start.match(/(\w+)(\[\])?/); // Get name and [] pieces
var property = pieces[1];
var isArray = pieces[2]; // [] on end
obj = obj[property]; // drill down
if (!segments.length) { // last segment; callback
if (isArray) {
return obj.forEach(callback);
} else {
return callback(obj);
}
} else { // more segments; recurse
if (isArray) { // array--loop over elts
obj.forEach(function (elt) {
do_segment(elt, segments.slice());
});
} else {
do_segment(obj, segments.slice()); // scalar--continue
}
}
})(obj, program.split('.'));
};
}
function make_inserter(program) {
return function (obj, value) {
(function do_segment(obj, segments) {
var start = segments.shift() // Get first segment
var pieces = start.match(/(\w+)(\[\])?/); // Get name and [] pieces
var property = pieces[1];
var isArray = pieces[2]; // [] on end
if (segments.length) { // more segments
if (!obj[property]) {
obj[property] = isArray ? [] : {};
}
do_segment(obj[property], segments.slice());
} else { // last segment
if (Array.isArray(obj)) {
var addedInFor = false;
for (var i = 0; i < obj.length; i++) {
if (!(property in obj[i])) {
obj[i][property] = value;
addedInFor = true;
break;
}
}
if (!addedInFor) {
var entry = {};
entry[property] = value;
obj.push(entry);
}
} else obj[property] = value;
}
})(obj, program.split('.'));
};
}
access = make_accessor(apiPath);
insert = make_inserter(myPath);
access(apiObj, function (val) {
insert(myObj, val);
});
console.log(myObj);
(old solution: https://jsfiddle.net/d7by0ywy/):
Here is my new generalized solution when you know the two objects to process in advance (called inp and out here). If you don't know them in advance you can use the trick in the old solution to assign the objects on both sides of = to inp and out (https://jsfiddle.net/uxdney3L/3/).
Restrictions: There has to be the same amount of arrays on both sides and an array has to contain objects. Othewise it would be ambiguous, you would have to come up with a better grammar to express rules (or why don't you have functions instead of rules?) if you want it to be more sophisticated.
Example of ambiguity: out.items[].sku=inp[].skus[].num Do you assign an array of the values of num to sku or do you assign an array of objects with the num property?
Data:
rules = [
'out.items[].name=inp[].name',
'out.items[].sku[].num=inp[].skus[].num'
];
inp = [{
'name': 'Hammer',
'skus':[{'num':'12345qwert','test':'ignore'}]
},{
'name': 'Bike',
'skus':[{'num':'asdfghhj'},{'num':'zxcvbn'}]
},{
'name': 'Fork',
'skus':[{'num':'0987dfgh'}]
}];
Program:
function process() {
if (typeof out == 'undefined') {
out = {};
}
var j, r;
for (j = 0; j < rules.length; j++) {
r = rules[j].split('=');
if (r.length != 2) {
console.log('invalid rule: symbol "=" is expected exactly once');
} else if (r[0].substr(0, 3) != 'out' || r[1].substr(0, 3) != 'inp') {
console.log('invalid rule: expected "inp...=out..."');
} else {
processRule(r[0].substr(3).split('[]'), r[1].substr(3).split('[]'), 0, inp, out);
}
}
}
function processRule(l, r, n, i, o) { // left, right, index, in, out
var t = r[n].split('.');
for (var j = 0; j < t.length; j++) {
if (t[j] != '') {
i = i[t[j]];
}
}
t = l[n].split('.');
if (n < l.length - 1) {
for (j = 0; j < t.length - 1; j++) {
if (t[j] != '') {
if (typeof o[t[j]] == 'undefined') {
o[t[j]] = {};
}
o = o[t[j]];
}
}
if (typeof o[t[j]] == 'undefined') {
o[t[j]] = [];
}
o = o[t[j]];
for (j = 0; j < i.length; j++) {
if (typeof o[j] == 'undefined') {
o[j] = {};
}
processRule(l, r, n + 1, i[j], o[j]);
}
} else {
for (j = 0; j < t.length - 1; j++) {
if (t[j] != '') {
if (typeof o[t[j]] == 'undefined') {
o[t[j]] = {};
}
o = o[t[j]];
}
}
o[t[j]] = i;
}
}
process();
console.log(out);
Well, an interesting problem. Programmatically constructing nested objects from a property accessor string (or the reverse) isn't much of a problem, even doing so with multiple descriptors in parallel. Where it does get complicated are arrays, which require iteration; and that isn't as funny any more when it gets to different levels on setter and getter sides and multiple descriptor strings in parallel.
So first we need to distinguish the array levels of each accessor description in the script, and parse the text:
function parse(script) {
return script.split(/\s*[;\r\n]+\s*/g).map(function(line) {
var assignment = line.split(/\s*=\s*/);
return assignment.length == 2 ? assignment : null; // console.warn ???
}).filter(Boolean).map(function(as) {
as = as.map(function(accessor) {
var parts = accessor.split("[]").map(function(part) {
return part.split(".");
});
for (var i=1; i<parts.length; i++) {
// assert(parts[i][0] == "")
var prev = parts[i-1][parts[i-1].length-1];
parts[i][0] = prev.replace(/s$/, ""); // singular :-)
}
return parts;
});
if (as[0].length == 1 && as[1].length > 1) // getter contains array but setter does not
as[0].unshift(["output"]); // implicitly return array (but better throw an error)
return {setter:as[0], getter:as[1]};
});
}
With that, the textual input can be made into a usable data structure, and now looks like this:
[{"setter":[["outputModel","items"],["item","name"]],
"getter":[["items"],["item","name"]]},
{"setter":[["outputModel","items"],["item","sku"]],
"getter":[["items"],["item","skus"],["sku","num"]]}]
The getters already transform nicely into nested loops like
for (item of items)
for (sku of item.skus)
… sku.num …;
and that's exactly where we are going to. Each of those rules is relatively easy to process, copying properties on objects and iterating array for array, but here comes our most crucial issue: We have multiple rules. The basic solution when we deal with iterating multiple arrays is to create their cartesian product and this is indeed what we will need. However, we want to restrict this a lot - instead of creating every combination of all names and all nums in the input, we want to group them by the item that they come from.
To do so, we'll build some kind of prefix tree for our output structure that'll contain generators of objects, each of those recursivley being a tree for the respective output substructure again.
function multiGroupBy(arr, by) {
return arr.reduce(function(res, x) {
var p = by(x);
(res[p] || (res[p] = [])).push(x);
return res;
}, {});
}
function group(rules) {
var paths = multiGroupBy(rules, function(rule) {
return rule.setter[0].slice(1).join(".");
});
var res = [];
for (var path in paths) {
var pathrules = paths[path],
array = [];
for (var i=0; i<pathrules.length; i++) {
var rule = pathrules[i];
var comb = 1 + rule.getter.length - rule.setter.length;
if (rule.setter.length > 1) // its an array
array.push({
generator: rule.getter.slice(0, comb),
next: {
setter: rule.setter.slice(1),
getter: rule.getter.slice(comb)
}
})
else if (rule.getter.length == 1 && i==0)
res.push({
set: rule.setter[0],
get: rule.getter[0]
});
else
console.error("invalid:", rule);
}
if (array.length)
res.push({
set: pathrules[0].setter[0],
cross: product(array)
});
}
return res;
}
function product(pathsetters) {
var groups = multiGroupBy(pathsetters, function(pathsetter) {
return pathsetter.generator[0].slice(1).join(".");
});
var res = [];
for (var genstart in groups) {
var creators = groups[genstart],
nexts = [],
nests = [];
for (var i=0; i<creators.length; i++) {
if (creators[i].generator.length == 1)
nexts.push(creators[i].next);
else
nests.push({path:creators[i].path, generator: creators[i].generator.slice(1), next:creators[i].next});
}
res.push({
get: creators[0].generator[0],
cross: group(nexts).concat(product(nests))
});
}
return res;
}
Now, our ruleset group(parse(script)) looks like this:
[{
"set": ["outputModel","items"],
"cross": [{
"get": ["items"],
"cross": [{
"set": ["item","name"],
"get": ["item","name"]
}, {
"get": ["item","skus"],
"cross": [{
"set": ["item","sku"],
"get": ["sku","num"]
}]
}]
}]
}]
and that is a structure we can actually work with, as it now clearly conveys the intention on how to match together all those nested arrays and the objects within them.
Let's dynamically interpret this, building an output for a given input:
function transform(structure, input, output) {
for (var i=0; i<structure.length; i++) {
output = assign(output, structure[i].set.slice(1), getValue(structure[i], input));
}
return output;
}
function retrieve(val, props) {
return props.reduce(function(o, p) { return o[p]; }, val);
}
function assign(obj, props, val) {
if (!obj)
if (!props.length) return val;
else obj = {};
for (var j=0, o=obj; j<props.length-1 && o!=null && o[props[j]]; o=o[props[j++]]);
obj[props[j]] = props.slice(j+1).reduceRight(function(val, p) {
var o = {};
o[p] = val;
return o;
}, val);
return obj;
}
function getValue(descriptor, input) {
if (descriptor.get) // && !cross
return retrieve(input, descriptor.get.slice(1));
var arr = [];
descriptor.cross.reduce(function horror(next, d) {
if (descriptor.set)
return function (inp, cb) {
next(inp, function(res){
cb(assign(res, d.set.slice(1), getValue(d, inp)));
});
};
else // its a crosser
return function(inp, cb) {
var g = retrieve(inp, d.get.slice(1)),
e = d.cross.reduce(horror, next)
for (var i=0; i<g.length; i++)
e(g[i], cb);
};
}, function innermost(inp, cb) {
cb(); // start to create an item
})(input, function(res) {
arr.push(res); // store the item
});
return arr;
}
And this does indeed work with
var result = transform(group(parse(script)), items); // your expected result
But we can do better, and much more performant:
function compile(structure) {
function make(descriptor) {
if (descriptor.get)
return {inputName: descriptor.get[0], output: descriptor.get.join(".") };
var outputName = descriptor.set[descriptor.set.length-1];
var loops = descriptor.cross.reduce(function horror(next, descriptor) {
if (descriptor.set)
return function(it, cb) {
return next(it, function(res){
res.push(descriptor)
return cb(res);
});
};
else // its a crosser
return function(it, cb) {
var arrName = descriptor.get[descriptor.get.length-1],
itName = String.fromCharCode(it);
var inner = descriptor.cross.reduce(horror, next)(it+1, cb);
return {
inputName: descriptor.get[0],
statement: (descriptor.get.length>1 ? "var "+arrName+" = "+descriptor.get.join(".")+";\n" : "")+
"for (var "+itName+" = 0; "+itName+" < "+arrName+".length; "+itName+"++) {\n"+
"var "+inner.inputName+" = "+arrName+"["+itName+"];\n"+
inner.statement+
"}\n"
};
};
}, function(_, cb) {
return cb([]);
})(105, function(res) {
var item = joinSetters(res);
return {
inputName: item.inputName,
statement: (item.statement||"")+outputName+".push("+item.output+");\n"
};
});
return {
statement: "var "+outputName+" = [];\n"+loops.statement,
output: outputName,
inputName: loops.inputName
};
}
function joinSetters(descriptors) {
if (descriptors.length == 1 && descriptors[0].set.length == 1)
return make(descriptors[0]);
var paths = multiGroupBy(descriptors, function(d){ return d.set[1] || console.error("multiple assignments on "+d.set[0], d); });
var statements = [],
inputName;
var props = Object.keys(paths).map(function(p) {
var d = joinSetters(paths[p].map(function(d) {
var names = d.set.slice(1);
names[0] = d.set[0]+"_"+names[0];
return {set:names, get:d.get, cross:d.cross};
}));
inputName = d.inputName;
if (d.statement)
statements.push(d.statement)
return JSON.stringify(p) + ": " + d.output;
});
return {
inputName: inputName,
statement: statements.join(""),
output: "{"+props.join(",")+"}"
};
}
var code = joinSetters(structure);
return new Function(code.inputName, code.statement+"return "+code.output+";");
}
So here is what you will get in the end:
> var example = compile(group(parse("outputModel.items[].name = items[].name;outputModel.items[].sku = items[].skus[].num;")))
function(items) {
var outputModel_items = [];
for (var i = 0; i < items.length; i++) {
var item = items[i];
var skus = item.skus;
for (var j = 0; j < skus.length; j++) {
var sku = skus[j];
outputModel_items.push({"name": item.name,"sku": sku.num});
}
}
return {"items": outputModel_items};
}
> var flatten = compile(group(parse("as[]=bss[][]")))
function(bss) {
var as = [];
for (var i = 0; i < bss.length; i++) {
var bs = bss[i];
for (var j = 0; j < bs.length; j++) {
var b = bs[j];
as.push(b);
}
}
return as;
}
> var parallelRecords = compile(group(parse("x.as[]=y[].a; x.bs[]=y[].b")))
function(y) {
var x_as = [];
for (var i = 0; i < y.length; i++) {
var y = y[i];
x_as.push(y.a);
}
var x_bs = [];
for (var i = 0; i < y.length; i++) {
var y = y[i];
x_bs.push(y.b);
}
return {"as": x_as,"bs": x_bs};
}
And now you can easily pass your input data to that dynamically created function and it will be transformed quite fast :-)

Categories

Resources