I have an array of ojects which all have a path and a name property.
Like
[
{
"id": "1",
"path": "1",
"name": "root"
},
{
"id": "857",
"path": "1/857",
"name": "Animals"
},
{
"id": "1194",
"path": "1/857/1194",
"name": "Dinasours"
},
...and so on
]
Here are some path examples
1/1279/1282
1/1279/1281
1/1279/1280
1/857
1/857/1194
1/857/1194/1277
1/857/1194/1277/1278
I want to turn this into a multidimensional array like:
const data = {
id: "1",
name: "Root",
children: [
{
id: "1279",
name: "Toys",
},
{
id: "857",
name: "Animals",
children: [
{
id: "1194",
name: "Dinasours",
children: [
{
id: "1277",
name: "T-Rex",
children: [
{
id: "1278",
name: "Superbig T-Rex",
},
],
},
],
},
],
},
],
};
As you can understand the amount of data is much larger.
Is there a neat way to transform this data?
I wonder if this would be sufficient for your needs?
I'll refer to the objects as nodes (just because I'm a graph theory person, and that's how I roll).
Build an index mapping each id to the object itself using a Map. (Purely for efficiency. You could technically find each node from scratch by id each time you need it.)
Split the path to obtain the second last path fragment which should be the id of the direct parent of the node. (Assuming there's only one and that there is guaranteed to be a node corresponding to that id?)
Add the child to the parent's list of children. We'll be careful not to add it multiple times.
This will result in nodes that have no children literally having no children property (as opposed to having a children property that is just []). I also did not remove/delete the path property from the objects.
As a note of caution, if there are path fragments that do not have corresponding objects, this will not work.
const nodes = [
{ id: '1', path: '1', name: 'root' },
{ id: '857', path: '1/857', name: 'Animals' },
{ id: '1194', path: '1/857/1194', name: 'Dinasours' }
//...and so on
];
const index = new Map();
for (let node of nodes) {
index.set(node.id, node)
}
for (let node of nodes) {
const fragments = node.path.split('/');
const parentId = fragments[fragments.length - 2];
const parent = index.get(parentId);
if (parent !== undefined) {
parent.children = parent.children || [];
if (!parent.children.includes(node)) {
parent.children.push(node);
}
}
}
// TODO: Decide which node is the root.
// Here's one way to get the first (possibly only) root.
const root = index.get(nodes[0].path.split('/')[0]);
console.dir(root, { depth: null });
Assuming that the root is always the same I came up with this code, it took me some time but it was fun to think about it.
var data = {};
list.forEach(item => {
var path = item.path.split("/");
let parent = data;
path.forEach((id) => {
if (!parent.id) {
parent.id = id;
parent.children = [];
if (id != item.id) {
let next = {}
parent.children.push(next);
parent = next;
}
} else if (parent.id != id) {
let next = parent.children.find(child => child.id == id);
if (!next) {
next = { id: id, children: [] }
parent.children.push(next);
}
parent = next;
}
});
parent.id = item.id;
parent.name = item.name
});
output:
{
"id": "1",
"children": [
{
"id": "857",
"children": [
{
"id": "1194",
"children": [
{
"id": "1277",
"children": [
{ "id": "1278", "children": [], "name": "Superbig T-Rex" }
],
"name": "T-Rex"
}
],
"name": "Dinasours"
}
],
"name": "Animals"
},
{ "id": "1279", "children": [], "name": "Toys" }
],
"name": "Root"
}
I think that having more roots here may need some fixing. Although I think the problem would be different if we were talking about multiple roots since your data variable is an object
Also, if you think in a recursive way it can be more understandable, but no comments on performance.
Related
I have a multilevel nested document (its dynamic and some levels can be missing but maximum 3 levels). I want to update all the children and subchildren routes if any. The scenario is same as in any Windows explorer, where all subfolders' route need to change when a parent folder route is changed. For eg. In the below example, If I am at route=="l1/l2a" and it's name needs to be edited to "l2c", then I will update it's route as route="l1/l2c and I will update all childrens' route to say "l1/l2c/l3a".
{
"name":"l1",
"route": "l1",
"children":
[
{
"name": "l2a",
"route": "l1/l2a",
"children":
[
{
"name": "l3a",
"route": "l1/l2a/l3a"
}]
},
{
"name": "l2b",
"route": "l1/l2b",
"children":
[
{
"name": "l3b",
"route": "l1/l2b/l3b"
}]
}
]
}
Currently I am able to go to a point and I am able to change its name and ONLY its route in the following manner:
router.put('/navlist',(req,res,next)=>{
newname=req.body.newName //suppose l2c
oldname=req.body.name //suppose l2a
route=req.body.route // existing route is l1/l2a
id=req.body._id
newroute=route.replace(oldname,newname); // l1/l2a has to be changed to l1/l2c
let segments = route.split('/');
let query = { route: segments[0]};
let update, options = {};
let updatePath = "";
options.arrayFilters = [];
for(let i = 0; i < segments.length -1; i++){
updatePath += `children.$[child${i}].`;
options.arrayFilters.push({ [`child${i}.route`]: segments.slice(0, i + 2).join('/') });
} //this is basically for the nested children
updateName=updatePath+'name'
updateRoute=updatePath+'route';
update = { $setOnInsert: { [updateName]:newDisplayName,[updateRoute]:newroute } };
NavItems.updateOne(query,update, options)
})
The problem is I am not able to edit the routes of it's children if any i.e it's subfolder route as l1/l2c/l3a. Although I tried using the $[] operator as follows.
updateChild = updatePath+'.children.$[].route'
updateChild2 = updatePath+'.children.$[].children.$[].route'
//update = { $set: { [updateChild]:'abc',[updateChild2]:'abc' } };
Its important that levels are customizable and thus I don't know whether there is "l3A" or not. Like there can be "l3A" but there may not be "l3B". But my code simply requires every correct path else it gives an error
code 500 MongoError: The path 'children.1.children' must exist in the document in order to apply array updates.
So the question is how can I apply changes using $set to a path that actually exists and how can I edit the existing route part. If the path exists, it's well and good and if the path does not exist, I am getting the ERROR.
Update
You could simplify updates when you use references.Updates/Inserts are straightforward as you can only the update target level or insert new level without worrying about updating all levels. Let the aggregation takes care of populating all levels and generating route field.
Working example - https://mongoplayground.net/p/TKMsvpkbBMn
Structure
[
{
"_id": 1,
"name": "l1",
"children": [
2,
3
]
},
{
"_id": 2,
"name": "l2a",
"children": [
4
]
},
{
"_id": 3,
"name": "l2b",
"children": [
5
]
},
{
"_id": 4,
"name": "l3a",
"children": []
},
{
"_id": 5,
"name": "l3b",
"children": []
}
]
Insert query
db.collection.insert({"_id": 4, "name": "l3a", "children": []}); // Inserting empty array simplifies aggregation query
Update query
db.collection.update({"_id": 4}, {"$set": "name": "l3c"});
Aggregation
db.collection.aggregate([
{"$match":{"_id":1}},
{"$lookup":{
"from":"collection",
"let":{"name":"$name","children":"$children"},
"pipeline":[
{"$match":{"$expr":{"$in":["$_id","$$children"]}}},
{"$addFields":{"route":{"$concat":["$$name","/","$name"]}}},
{"$lookup":{
"from":"collection",
"let":{"route":"$route","children":"$children"},
"pipeline":[
{"$match":{"$expr":{"$in":["$_id","$$children"]}}},
{"$addFields":{"route":{"$concat":["$$route","/","$name"]}}}
],
"as":"children"
}}
],
"as":"children"
}}
])
Original
You could make route as array type and format before presenting it to user. It will greatly simplify updates for you. You have to break queries into multiple updates when nested levels don’t exist ( ex level 2 update ). May be use transactions to perform multiple updates in atomic way.
Something like
[
{
"_id": 1,
"name": "l1",
"route": "l1",
"children": [
{
"name": "l2a",
"route": [
"l1",
"l2a"
],
"children": [
{
"name": "l3a",
"route": [
"l1",
"l2a",
"l3a"
]
}
]
}
]
}
]
level 1 update
db.collection.update({
"_id": 1
},
{
"$set": {
"name": "m1",
"route": "m1"
},
"$set": {
"children.$[].route.0": "m1",
"children.$[].children.$[].route.0": "m1"
}
})
level 2 update
db.collection.update({
"_id": 1
},
{
"$set": {
"children.$[child].route.1": "m2a",
"children.$[child].name": "m2a"
}
},
{
"arrayFilters":[{"child.name": "l2a" }]
})
db.collection.update({
"_id": 1
},
{
"$set": {
"children.$[child].children.$[].route.1": "m2a"
}
},
{
"arrayFilters":[{"child.name": "l2a"}]
})
level 3 update
db.collection.update({
"_id": 1
},
{
"$set": {
"children.$[].children.$[child].name": "m3a"
"children.$[].children.$[child].route.2": "m3a"
}
},
{
"arrayFilters":[{"child.name": "l3a"}]
})
I don't think its possible with arrayFilted for first level and second level update, but yes its possible only for third level update,
The possible way is you can use update with aggregation pipeline starting from MongoDB 4.2,
I am just suggesting a method, you can simplify more on this and reduce query as per your understanding!
Use $map to iterate the loop of children array and check condition using $cond, and merge objects using $mergeObjects,
let id = req.body._id;
let oldname = req.body.name;
let route = req.body.route;
let newname = req.body.newName;
let segments = route.split('/');
LEVEL 1 UPDATE: Playground
// LEVEL 1: Example Values in variables
// let oldname = "l1";
// let route = "l1";
// let newname = "l4";
if(segments.length === 1) {
let result = await NavItems.updateOne(
{ _id: id },
[{
$set: {
name: newname,
route: newname,
children: {
$map: {
input: "$children",
as: "a2",
in: {
$mergeObjects: [
"$$a2",
{
route: { $concat: [newname, "/", "$$a2.name"] },
children: {
$map: {
input: "$$a2.children",
as: "a3",
in: {
$mergeObjects: [
"$$a3",
{ route: { $concat: [newname, "/", "$$a2.name", "/", "$$a3.name"] } }
]
}
}
}
}
]
}
}
}
}
}]
);
}
LEVEL 2 UPDATE: Playground
// LEVEL 2: Example Values in variables
// let oldname = "l2a";
// let route = "l1/l2a";
// let newname = "l2g";
else if (segments.length === 2) {
let result = await NavItems.updateOne(
{ _id: id },
[{
$set: {
children: {
$map: {
input: "$children",
as: "a2",
in: {
$mergeObjects: [
"$$a2",
{
$cond: [
{ $eq: ["$$a2.name", oldname] },
{
name: newname,
route: { $concat: ["$name", "/", newname] },
children: {
$map: {
input: "$$a2.children",
as: "a3",
in: {
$mergeObjects: [
"$$a3",
{ route: { $concat: ["$name", "/", newname, "/", "$$a3.name"] } }
]
}
}
}
},
{}
]
}
]
}
}
}
}
}]
);
}
LEVEL 3 UPDATE: Playground
// LEVEL 3 Example Values in variables
// let oldname = "l3a";
// let route = "l1/l2a/l3a";
// let newname = "l3g";
else if (segments.length === 3) {
let result = await NavItems.updateOne(
{ _id: id },
[{
$set: {
children: {
$map: {
input: "$children",
as: "a2",
in: {
$mergeObjects: [
"$$a2",
{
$cond: [
{ $eq: ["$$a2.name", segments[1]] },
{
children: {
$map: {
input: "$$a2.children",
as: "a3",
in: {
$mergeObjects: [
"$$a3",
{
$cond: [
{ $eq: ["$$a3.name", oldname] },
{
name: newname,
route: { $concat: ["$name", "/", "$$a2.name", "/", newname] }
},
{}
]
}
]
}
}
}
},
{}
]
}
]
}
}
}
}
}]
);
}
Why separate query for each level?
You could do single query but it will update all level's data whenever you just need to update single level data or particular level's data, I know this is lengthy code and queries but i can say this is optimized version for query operation.
you can't do as you want. Because mongo does not support it. I can offer you to fetch needed item from mongo. Update him with your custom recursive function help. And do db.collection.updateOne(_id, { $set: data })
function updateRouteRecursive(item) {
// case when need to stop our recursive function
if (!item.children) {
// do update item route and return modified item
return item;
}
// case what happen when we have children on each children array
}
How do you write a function that will perform an unflattening transformation on a nodes array of objects which represents a directory structure or routing path tree.
Here's an example nodes array:
const nodes = [
{
"id": "1",
"path": "/home",
},
{
"id": "2",
"path": "/users",
},
{
"id": "3",
"path": "/users/alice",
},
{
"id": "4",
"path": "/users/bob",
},
{
"id": "5",
"path": "/users/alice/posts",
}
];
To transforming an array into the following nested object:
{
"path": "/",
"children": [
{
"id": "1",
"path": "/home",
"children": []
},
{
"id": "2",
"path": "/users",
"children": [
{
"id": "3",
"path": "/users/alice",
"children": [
{
"children": [],
"id": "5",
"path": "/users/alice/posts"
}
]
},
{
"id": "4",
"path": "/users/bob",
"children": []
}
]
}
]
}
Please take a look here:
const nodes = [ { "id": "1", "path": "/home",},{"id": "2","path": "/users",},{"id": "3","path": "/users/alice",},{"id": "4","path": "/users/bob",},{"id": "5","path": "/users/alice/posts",}];
const tree = {path: "/", children: []};
nodes.map(t => ({id: t.id, path: t.path.split('/').slice(1)}))
.sort((a, b) => a.path.length - b.path.length)
.forEach(t => {
t.path.slice(0, -1).reduce((a, b) => a.children.find(el => el.path === b), tree).children.push({
id: t.id,
path: t.path[t.path.length - 1],
children: []
});
});
console.log(tree);
Since previous answers do not provide full path for each nested node (and I believe this is what OP wants) here is my take on it:
sort objects by path length and their id
for each object split its path
for each dir of a split path try to find an object within the tree that matches partial path
.reduce() will return last object that matched (or our initial value which is a tree base)
push initial object into that
const nodes = [
{"id": "4","path": "/users/bob"},
{"id": "5","path": "/users/alice/posts"},
{"id":"6", "path": "/home/somedir/etc"},
{"id": "2","path": "/users"},
{"id": "1", "path": "/home"},
{"id": "3","path": "/users/alice"}
];
const tree = {path: "/", children: []};
nodes.sort((a, b) => (a.path.split('/').length - b.path.split('/').length) || parseInt(a.id) - parseInt(b.id))
.forEach(node => node.path.split('/').slice(1)
.reduce(((a, b) => (a.children.find(el => el.path.slice(el.path.lastIndexOf('/') + 1) === b) || a)), tree)
.children.push({id: node.id, path: node.path, children: []}));
console.log(tree);
It's worth noting that this code does not create intermediate nodes/paths if they weren't provided, i.e /home/somedir/etc will be nested under /home because /home/somedir doesn't exist in the initial array.
This is my solution. What I did were:
First create a tree with only the root directory (i.e. path: '/')
Then, for each node, I will "traverse" it according to its path
I will traverse each directory separated by / in each node. Then, I will check if there is already a subtree (i.e. children) within the said directory's children array. If there isn't, create a new subtree and push it to the array.
For each directory traversed in node, update the variable traversed to store the currently being traversed subtree
When I reach the final directory of each node, update the directory id to the node id
Here's a working example (the code below works when paths are not sorted, i.e. some longer paths with directories that don't exist create said directories):
const nodes = [{ "id": "1", "path": "/home" }, { "id": "2", "path": "/users" }, { "id": "3", "path": "/users/alice" }, { "id": "4", "path": "/users/bob" }, { "id": "5", "path": "/users/alice/posts" }]
const tree = { path: '/', children:[] }
nodes.forEach(node => {
const dirs = node.path.split('/').slice(1)
let traversed = tree // Hold the reference of tree
let totalPath = ``
dirs.forEach((dir, index) => {
const foundChild = traversed.children.find(child => child.path === `${totalPath}/${dir}`)
const currSubtree = foundChild ? foundChild : { path: `${totalPath}/${dir}`, children: [] }
if (!foundChild) traversed.children.push(currSubtree)
if (index === dirs.length - 1) currSubtree.id = node.id
traversed = currSubtree
totalPath += `/${dir}`
})
})
console.log(tree)
I have a hierarchy of objects that contain the parent ID on them. I am adding the parentId to the child object as I parse the json object like this.
public static fromJson(json: any): Ancestry | Ancestry[] {
if (Array.isArray(json)) {
return json.map(Ancestry.fromJson) as Ancestry[];
}
const result = new Ancestry();
const { parents } = json;
parents.forEach(parent => {
parent.parentId = json.id;
});
json.parents = Parent.fromJson(parents);
Object.assign(result, json);
return result;
}
Any thoughts on how to pull out the ancestors if I have a grandchild.id?
The data is on mockaroo curl (Ancestries.json)
As an example, with the following json and a grandchild.id = 5, I would create and array with the follow IDs
['5', '0723', '133', '1']
[{
"id": "1",
"name": "Deer, spotted",
"parents": [
{
"id": "133",
"name": "Jaime Coldrick",
"children": [
{
"id": "0723",
"name": "Ardys Kurten",
"grandchildren": [
{
"id": "384",
"name": "Madelle Bauman"
},
{
"id": "0576",
"name": "Pincas Maas"
},
{
"id": "5",
"name": "Corrie Beacock"
}
]
},
There is perhaps very many ways to solve this, but in my opinion the easiest way is to simply do a search in the data structure and store the IDs in inverse order of when you find them. This way the output is what you are after.
You could also just reverse the ordering of a different approach.
I would like to note that the json-structure is a bit weird. I would have expected it to simply have nested children arrays, and not have them renamed parent, children, and grandchildren.
let data = [{
"id": "1",
"name": "Deer, spotted",
"parents": [
{
"id": "133",
"name": "Jaime Coldrick",
"children": [
{
"id": "0723",
"name": "Ardys Kurten",
"grandchildren": [
{
"id": "384",
"name": "Madelle Bauman"
},
{
"id": "0576",
"name": "Pincas Maas"
},
{
"id": "5",
"name": "Corrie Beacock"
}
]
}]
}]
}]
const expectedResults = ['5', '0723', '133', '1']
function traverseInverseResults(inputId, childArray) {
if(!childArray){ return }
for (const parent of childArray) {
if(parent.id === inputId){
return [parent.id]
} else {
let res = traverseInverseResults(inputId, parent.parents || parent.children || parent.grandchildren) // This part is a bit hacky, simply to accommodate the strange JSON structure.
if(res) {
res.push(parent.id)
return res
}
}
}
return
}
let result = traverseInverseResults('5', data)
console.log('results', result)
console.log('Got expected results?', expectedResults.length === result.length && expectedResults.every(function(value, index) { return value === result[index]}))
I am currently with some JSON, which has to be structured in a tree-like hierarchy. The depth of the hierarchy varies a lot, and is therefor unknown.
As it is right now, I have achieved to get an array of objects. Example is below.
[
{
"name": "level1",
"collapsed": true,
"children": [
{
"name": "Level 1 item here",
"id": 360082134191
}
]
},
{
"name": "level1",
"collapsed": true,
"children": [
{
"name": "level2",
"collapsed": true,
"children": [
{
"name": "Level 2 item here",
"id": 360082134751
}
]
}
]
},
{
"name": "level1",
"collapsed": true,
"children": [
{
"name": "Another level 1 item",
"id": 360082262772
}
]
}
]
What I want to achieve is these objects to be merged, without overwriting or replacing anything. Listed below is an example of how I want the data formatted:
[
{
"name": "level1",
"collapsed": true,
"children": [
{
"name": "level2",
"collapsed": true,
"children": [
{
"name": "Level 2 item here",
"id": 360082134751
}
]
},
{
"name": "Level 1 item here",
"id": 360082134191
},
{
"name": "Another level 1 item",
"id": 360082262772
}
]
}
]
How would I achieve this with JavaScript? No libraries is preferred, ES6 can be used though.
Edit:
It is important that the output is an array, since items without children can appear at the root.
I am assuming you need a little help on working with the data. There could be multiple ways to achieve this, here is how would I do.
// data => supplied data
const result = data.reduce ((acc, item) => {
// if acc array already contains an object with same name,
// as current element [item], merfe the children
let existingItem;
// Using a for loop here to create a reference to the
// existing item, so it'd update this item when childrens
// will be merged.
for (let index = 0; index < acc.length; index ++) {
if (acc[index].name === item.name) {
existingItem = acc[index];
break;
}
}
// if existingItem exists, merge children of
// existing item and current item.
// else push it into the accumulator
if (existingItem) {
existingItem.children = existingItem.children.concat(item.children);
} else {
acc.push (item);
}
return acc;
}, []);
I'm assuming you want to group based on the name property in the level 1 object. You could do a simple reduce and Object.values like this:
const input = [{"name":"level1","collapsed":true,"children":[{"name":"Level 1 item here","id":360082134191}]},{"name":"level1","collapsed":true,"children":[{"name":"level2","collapsed":true,"children":[{"name":"Level 2 item here","id":360082134751}]}]},{"name":"level1","collapsed":true,"children":[{"name":"Another level 1 item","id":360082262772}]}]
const merged = input.reduce((r,{name, collapsed, children}) =>{
r[name] = r[name] || {name, collapsed, children:[]};
r[name]["children"].push(...children)
return r;
}, {})
const final = Object.values(merged);
console.log(final)
You could do the whole thing in one line:
const input = [{"name":"level1","collapsed":true,"children":[{"name":"Level 1 item here","id":360082134191}]},{"name":"level1","collapsed":true,"children":[{"name":"level2","collapsed":true,"children":[{"name":"Level 2 item here","id":360082134751}]}]},{"name":"level1","collapsed":true,"children":[{"name":"Another level 1 item","id":360082262772}]}]
const output = Object.values(input.reduce((r,{name,collapsed,children}) => (
(r[name] = r[name] || {name,collapsed,children: []})["children"].push(...children), r), {}))
console.log(output)
I'm looking for the best way to convert multiple string paths to a nested object with javascript. I'm using lodash if that could help in any way.
I got the following paths:
/root/library/Folder 1
/root/library/Folder 2
/root/library/Folder 1/Document.docx
/root/library/Folder 1/Document 2.docx
/root/library/Folder 2/Document 3.docx
/root/library/Document 4.docx
and I would like to create the following array of object:
var objectArray =
[
{
"name": "root", "children": [
{
"name": "library", "children": [
{
"name": "Folder 1", "children": [
{ "name": "Document.docx", "children": [] },
{ "name": "Document 2.docx", "children": [] }
]
},
{
"name": "Folder 2", "children": [
{ "name": "Document 3.docx", "children": [] }
]
},
{
"name": "Document 4.docx", "children": []
}
]
}
]
}
];
I suggest implementing a tree insertion function whose arguments are an array of children and a path. It traverses the children according to the given path and inserts new children as necessary, avoiding duplicates:
// Insert path into directory tree structure:
function insert(children = [], [head, ...tail]) {
let child = children.find(child => child.name === head);
if (!child) children.push(child = {name: head, children: []});
if (tail.length > 0) insert(child.children, tail);
return children;
}
// Example:
let paths = [
'/root/library/Folder 1',
'/root/library/Folder 2',
'/root/library/Folder 1/Document.docx',
'/root/library/Folder 1/Document 2.docx',
'/root/library/Folder 2/Document 3.docx',
'/root/library/Document 4.docx'
];
let objectArray = paths
.map(path => path.split('/').slice(1))
.reduce((children, path) => insert(children, path), []);
console.log(objectArray);
Iterate over each string and resolve it to an object:
var glob={name:undefined,children:[]};
["/root/library/Folder 1","/root/library/Folder 2","/root/library/Folder 1/Document.docx","/root/library/Folder 1/Document 2.docx","/root/library/Folder 2/Document 3.docx","/root/library/Document 4.docx"]
.forEach(function(path){
path.split("/").slice(1).reduce(function(dir,sub){
var children;
if(children=dir.children.find(el=>el.name===sub)){
return children;
}
children={name:sub,children:[]};
dir.children.push(children);
return children;
},glob);
});
console.log(glob);
http://jsbin.com/yusopiguci/edit?console
Improved version:
var glob={name:undefined,children:[]};
var symbol="/" /* or Symbol("lookup") in modern browsers */ ;
var lookup={[symbol]:glob};
["/root/library/Folder 1","/root/library/Folder 2","/root/library/Folder 1/Document.docx","/root/library/Folder 1/Document 2.docx","/root/library/Folder 2/Document 3.docx","/root/library/Document 4.docx"]
.forEach(function(path){
path.split("/").slice(1).reduce(function(dir,sub){
if(!dir[sub]){
let subObj={name:sub,children:[]};
dir[symbol].children.push(subObj);
return dir[sub]={[symbol]:subObj};
}
return dir[sub];
},lookup);
});
console.log(glob);
It creates the same result but it is may much faster ( up to O(n) vs. O(n+n!))
http://jsbin.com/xumazinesa/edit?console