I have a string example
"abc|pqr[abc,xyz[abc,def]]"
Now i want to output into array
{
abc : true,
pqr : ['abc', xyz : [abc, def]]
}
the code i wrote is this but it give me
"message": "Maximum call stack size exceeded"
var x = 'p[a,b,c,d]|q[small,large]|r[small,large]|s|t[w[x,y],z[a,b,c]]';
y = x.split("|");
function foo(query) {
if (typeof query == "string") query = [query]
var i = {}
_(query).forEach(function(v) {
regexQuery = v.match(/\[(.*)\]/);
if (regexQuery != null) {
index = regexQuery['index']
if (regexQuery[1].match(/\[(.*)\]/) != null) {
i[regexQuery['input'].substr(0, index)] = foo(regexQuery[0])
} else {
i[regexQuery['input'].substr(0, index)] = regexQuery[1].split(",");
}
} else {
i[v] = true;
}
})
return i;
}
console.log(foo(y));
i know regex is not got for this but is there any other solution?
You could use the function below. For the input given in the question:
p[a,b,c,d]|q[small,large]|r[small,large]|s|t[w[x,y],z[a,b,c]]
...it produces this object:
{
"p": [
"a",
"b",
"c",
"d"
],
"q": [
"small",
"large"
],
"r": [
"small",
"large"
],
"s": true,
"t": {
"w": [
"x",
"y"
],
"z": [
"a",
"b",
"c"
]
}
}
function toObject(x) {
// Turn custom format into JSON text format, and then parse it.
// In that object, find nested objects that could be turned into array.
return (function flagsToArray(obj) {
// Collect keys with nested objects.
var nested = Object.keys(obj).filter(key => obj[key] !== true);
// For those, call this function recursively
nested.forEach(key => obj[key] = flagsToArray(obj[key]));
// If no nesting, then turn this into an array
return nested.length ? obj : Object.keys(obj);
})(JSON.parse('{' +
x.replace(/\|/g, ',') // treat '|' as ','
.replace(/"/g, '\"') // escape any double quotes
.replace(/([^,|\[\]]+)/g, '"$1"') // wrap terms in double quotes
.replace(/"\[/g, '":[') // insert colon for assignment of arrays
.replace(/"([,\]])/g, '":true$1') // insert `true` assignment for atomic term
.replace(/\[/g, "{").replace(/\]/g, "}") // replace array notation with object notation
+ '}'));
}
// Sample input
var x = 'p[a,b,c,d]|q[small,large]|r[small,large]|s|t[w[x,y],z[a,b,c]]';
// Convert
var obj = toObject(x);
// Output
console.log(obj);
The function makes several replacements to convert the custom format into a JSON text format, turning everything into nested objects (no arrays). Then in a second process, a recursive one, objects are identified that have no nested objects, i.e. they only consist of members with true as value. Those objects are then replaced by their array "equivalent", i.e. the array with the object's keys.
Your query string is essentially a flat representation of a tree whose nodes are defined by:
either a name alone
or a name and a list of child nodes
Note that I don't see any obvious difference between , and |, so I'm going to assume that they actually have the same meaning.
You can't easily store this structure by using only arrays, and it would also be unnecessarily complicated to use a mix of arrays and objects.
Therefore, I'd suggest to use only objects with the following conventions:
key = name of node
value = either true1 or a child object
1 This is a placeholder. You may also consider using an empty object.
With these assumptions, your example string "abc|pqr[abc,xyz[abc,def]]" would be decoded as:
tree = {
"abc": true,
"pqr": {
"abc": true,
"xyz": {
"abc": true,
"def": true
}
}
}
Such a structure is quite easy to manipulate.
For instance, if you'd like to get the child nodes of root > pqr > xyz, you could do:
Object.keys(tree.pqr.xyz)
which will return:
["abc", "def"]
Implementation
Below is a possible implementation:
function parse(query) {
var n, tree = {}, node = tree, stk = [],
sym = '', sz = (query += ',').length;
for(n = 0; n < sz; n++) {
switch(query[n]) {
case '|':
case ',':
sym && (node[sym] = true);
break;
case '[':
stk.push(node);
node = node[sym] = {};
break;
case ']':
sym && (node[sym] = true);
node = stk.pop();
break;
default:
sym += query[n];
continue;
}
sym = '';
}
return tree;
}
console.log(parse("abc|pqr[abc,xyz[abc,def]]"));
Related
The Setup
I have a string, something like:
Sample Strings
val4[3].sub1[ 1 ][2].smth.esl
// or
val4.sub1
// or
val4.sub1[2].smth
// and so on...
The string will always abide by the following rules:
The string always starts with characters belonging to A-Za-z0-9_-
Every period must be followed by at least one of A-Za-z0-9_-
Every opening bracket [ has a matching closing bracket ]
Every set of brackets contains an integer, with optional leading and trailing spaces
Think of the string in the same way you'd access items in an associative array. For example, the first string listed above might access an array like:
Sample Array Structure
val4 = [
0,
'some string',
2,
{
sub1: [
[1, 2, 3],
[
'val',
1,
{
smth: {
esl: 'final value'
}
}
],
[4, 5, 6]
],
sub2: 'another str'
},
4
];
and the value at val4[3].sub1[ 1 ][2].smth.esl is final value.
Albeit the array is a nightmare, but just to help understand the acceptable patterns in the input strings.
Desired Behavior
What I am trying to do is split the string by the following:
Periods and any following text belonging to A-Za-z0-9_-
/\.([A-Za-z0-9_-]+)/g
Bracket pairs with integer contents and optional pre/aft spaces
/\[\s?[\d]+\s?\]/g
So, using the first string as an example again:
val4[3].sub1[ 1 ][2].smth.esl
would split into
Desired Output
val4
[3]
.sub1
[ 1 ]
[2]
.smth
.esl
You can see at this RegExr what the desired groupings should be.
The Trials
JSFiddle of code
When I run the following:
Code Block #1
var str = 'val4[3].sub1[ 1 ][2].smth.esl';
var re = /(\[\s?[\d]+\s?\])|(?=\.([A-Za-z0-9_-]+))/g;
var splits = str.split(re);
Note: In the regex expression, I've made the second pattern a positive lookahead (?=), so that the periods remain with the chars that follow
I end up with the following:
Output #1
splits = [
"val4",
"[3]",
null,
".sub1",
"[ 1 ]",
null,
"",
"[2]",
null,
".smth",
null,
"esl",
".esl"
];
The result is pretty close, with a few notable exceptions:
splits[2], splits[5], splits[8] and splits[10] are null
splits[6] is an empty string
splits[11] is missing the preceding period (and is really just an incorrect duplicate of splits[12])
Running this with only the brackets part
Code Block #2
var re = /(\[\s?[\d]+\s?\])/g;
returns:
Output #2
splits = [
"val4",
"[3]",
".sub1",
"[ 1 ]",
"",
"[2]",
".smth.esl"
];
Running this with only the period/chars part
Code Block #3
var re = /(?=\.([A-Za-z0-9_-]+))/g;
returns:
Output #3
splits = [
"val4[3]",
".sub1[ 1 ][2]",
"smth",
".smth",
"esl",
".esl"
];
But both of the preceding have their own flaws (empty values, strings without their accompanying periods).
I've said "I'm so close" with regex stuff before, and have been miles off. But, I feel like I'm right on the cusp.
So, how can I mod the regex/split to accomplish what I'm after?
Basically, no null or empty values returned, and only strings with their preceding periods.
Hopefully I've been clear, please let me know if any ambiguity.
Important!
For proposed solutions, it needs to be...
Browser-Independent
This means likely nothing from ES6, since browser support for the new content is very inconsistent. People cannot be forced to adopt a browser just to run this.
Easily ported over to PHP (Preferred, but not absolutely critical)
This has a sister script written for server-side operations. Keeping away from JavaScript-Specific technologies will help implement it in both languages.
Additional (Not Essential)
(For those interested in the context and purpose)
Spoiler alert! There really is an array. Not identical to val4 defined above, but not dissimilar, and never the same twice.
val4 = [... and so on...];
A string is provided (must be a string type)
str = 'val4[3].sub1[ 1 ][2].smth.esl';
And the return must be the value in val4, at the address in str.
var val4 = [... and so on...];
var str = 'val4[3].sub1[ 1 ][2].smth.esl';
var result = getItem(val4, str);
Would literally return
return val4[3].sub1[1][2].smth.esl;
If you've got a bangin' way to do what I'm trying to achieve, without all the regex, I'm happy to hear it. But still interested in the primary issue laid-out in the question.
I assume in the end it's this you want to build.
I slightly extended the Regex to also parse things like ["lorem ipsum"]
//parses the path into an Array of keys
var parsePath = (function(){
var fetchParts = /\[\s*(\d+|"(?:\\[\s\S]|[^\\"])+")\s*\]|(?:\.|^)([A-Za-z_$][A-Za-z_$0-9]*)/g;
var isValid = new RegExp("^(?:" + fetchParts.source.replace(/\((?:\?\:)?/g, "(?:") + ")+$", "")
return function(str){
var s = str == null? "": String(str).trim();
if(!isValid.test(s))
throw new Error("invalid path: "+JSON.stringify(str));
for(var m, keys = []; m = fetchParts.exec(s);)
keys.push(m[1]? JSON.parse(m[1]): m[2]);
return keys;
}
})();
//takes a path or an Array of keys and returns a function that resolves the path from the passed object.
//returns undefined if path can't be resolved;
//v1
var resolvePath = function(path){
var keys = Array.isArray(path)? path: parsePath(path);
return keys.reduceRight(
(nextFn, key) => obj => obj != null && key in obj? nextFn(obj[key]): void 0,
v => v
);
}
//v2
var resolvePath = function(path){
var keys = Array.isArray(path)? path: parsePath(path);
return function(obj){
for(var i=0; i<keys.length; ++i){
if(obj == null) return void 0;
obj = obj[keys[i]]
}
return obj;
}
}
//usage:
var getSmthEsl = resolvePath('val4[3].sub1[ 1 ][2]["lorem\\nipsum"].smth.esl');
console.log("a", getSmthEsl({
val4: [
null, //0
null, //1
null, //2
{ //3
sub1: [
null, //0
[ //1
null, //0
null, //1
{ //2
"lorem\nipsum": {
smth: {
esl: {
sucess: true
}
}
}
}
]
]
}
]
}))
console.log("b", getSmthEsl({ val4: [] }))
Much simpler than you think:
window.val4 = [
0,
'some string',
2,
{
sub1: [
[1, 2, 3],
[
'val',
1,
{
smth: {
esl: 'final value'
}
}
],
[4, 5, 6]
],
sub2: 'another str'
},
4
];
let ref = 'val4[3].sub1[ 1 ][2].smth.esl'
let result = window; // or 'global' in node
ref.replace(/(\w+)|\[\s*(\d+)\s*\]/g, (_, $1, $2) => result = result[$1 || $2]);
console.log(result)
OK for this job you need to be able to set the object properties dynamically. Previously i had done a code for that purpose. Object.prototype.setNestedValue().
setNestedValue([prop1[, prop2[, prop3...]]],value) works exactly like it's twin getNestedValue() but the last argument is the value to set. If the property does not exist it will create an object or array depenging on the type of the provided argument. Again, a string type argument will result an object while a number type argument will result an Array of that size.
Lets see the code.
Object.prototype.setNestedValue = function(...a) {
a.length > 2 ? typeof this[a[0]] === "object" && this[a[0]] !== null ? this[a[0]].setNestedValue(...a.slice(1))
: (this[a[0]] = typeof a[1] === "string" ? {} : new Array(a[1]),
this[a[0]].setNestedValue(...a.slice(1)))
: this[a[0]] = a[1];
return this;
};
var str = "val4[3].sub1[ 1 ][2].smth.esl",
arr = str.split(/\[\s*(\d+)\s*\]|\./)
.filter(prop => prop)
.map(prop => Number.isNaN(+prop) ? prop : +prop)
.concat("final value");
result = {}.setNestedValue(...arr);
console.log(JSON.stringify(arr));
console.log(JSON.stringify(result,null,4));
I first show how the resulting arguments array to invoke setNestedValue(...arr) and then the resulting object.
Oh my bad...! I thought you wanted to construct the object dynamically however you like to get the nested value dynamically from an already constructed object.. Object.prototype.getNestedValue() is ideal for this job. We already have our object constructed anyways so lets use the above code again and get the nested value dynamically.
Object.prototype.getNestedValue = function(...a) {
return a.length > 1 ? (this[a[0]] !== void 0 && this[a[0]].getNestedValue(...a.slice(1))) : this[a[0]];
};
Object.prototype.setNestedValue = function(...a) {
a.length > 2 ? typeof this[a[0]] === "object" && this[a[0]] !== null ? this[a[0]].setNestedValue(...a.slice(1))
: (this[a[0]] = typeof a[1] === "string" ? {} : new Array(a[1]),
this[a[0]].setNestedValue(...a.slice(1)))
: this[a[0]] = a[1];
return this;
};
var str = "val4[3].sub1[ 1 ][2].smth.esl",
arr = str.split(/\[\s*(\d+)\s*\]|\./)
.filter(prop => prop)
.map(prop => Number.isNaN(+prop) ? prop : +prop)
.concat("final value"),
myObj = {}.setNestedValue(...arr),
value;
arr.pop(); // lets remove the "final value" since now we will get it.
value = myObj.getNestedValue(...arr);
console.log(value);
I would like to remove the matching elements {}, and {} from a JSON string.
Input : "test": [{},{},{},{},{},{},{}],
Output : "test": [],
To do so, I tried :
var jsonConfig = JSON.stringify(jsonObj);
var jsonFinal = jsonConfig.replace(/[{},]/g, ''); // Remove global
var jsonFinal = jsonConfig.replace(/[{},]/, ''); // Remove brackets
console.log(jsonFinal);
and many more.
How can I remove only those set of elements from my JSON without impacting the other brackets and comma?
Do NOT attempt to modify JSON with string manipulation functions.
ALWAYS parse the JSON, transform the data, and re-stringify to JSON.
EDIT: this answer addresses your comment that the input data object will contain other potential keys that should be present in the output.
// a couple of procedures to help us transform the data
const isEmptyObject = x => Object.keys(x).length === 0;
const not = x => ! x;
const comp = f => g => x => f (g (x));
const remove = f => xs => xs.filter (comp (not) (f));
// your input json
let json = '{"test": [{},{},{"x": 1}], "test2": [{},{}], "a": 1, "b": 2}';
// parsed json
let data = JSON.parse(json);
// transform data
let output = JSON.stringify(Object.assign({}, data, {
// remove all empty objects from `test`
test: remove (isEmptyObject) (data.test),
// remove all empty objects from `test2`
test2: remove (isEmptyObject) (data.test2),
}));
// display output
console.log(output); // '{"test":[{"x":1}],"test2":[],"a":1,"b":2}'
I like the ES2015 answer of #naomik.
This is another alternative:
/**
* Remove empty objects or arrays
* #param {Object, Array} obj: the object to which remove empty objects or arrays
* #return {Any}
*/
const removeEmptyObject = (function() {
const isNotObject = v => v === null || typeof v !== "object";
const isEmpty = o => Object.keys(o).length === 0;
return function(obj) {
if (isNotObject(obj)) return obj;
if (obj instanceof Array) {
for (let i = 0; i < obj.length; i += 1) {
if (isNotObject(obj[i])) continue;
if (isEmpty(obj[i])) obj.splice(i--, 1);
else obj[i] = removeEmptyObject(obj[i]);
}
}
else {
for (let p in obj) {
if (isNotObject(obj[p])) continue;
if (!isEmpty(obj[p])) obj[p] = removeEmptyObject(obj[p]);
if (isEmpty(obj[p])) delete obj[p];
}
}
return obj;
}
}());
Now lets test the code:
var json = '{"test": [{},{},{"x": 1}], "test2": [{},{}], "test3":[[],[1,2,3],[]], "a": 1, "b": 2}';
var data = JSON.parse(json); //Object
var output = removeEmptyObject(data);
console.log(output);
console.log(removeEmptyObject(9));
console.log(removeEmptyObject(null));
console.log(removeEmptyObject({}));
You should work on the actual object not the string.
If you do, you can loop through the object and check if it has any properties. If it doesn't have any, you can remove it.
for(var prop in obj) {
if (obj.hasOwnProperty(prop)) {
//remove here
}
}
Setting aside the question of whether string manipulation is the best way to tidy up JSON data, your earlier attempts would remove all braces and commas, because [] in a regexp indicates "match any of the characters contained inside these brackets". If you were trying to treat those as literal characters, they'd need to be escaped: \[ or \]
You want something like .replace(/{},?/g,"") (which means "match all instances of the string {} or the string {}, -- the question mark makes the preceding character an optional match).
(This would, of course, remove all empty objects from the string, and has the potential to create invalid JSON given input like "foo: {}, bar: {}" -- so only use this if you're certain that your data will never include intentionally empty objects.)
THIS IS NOT A QUESTION OF SORTING BY A PROPERTY!!
Assume I have an array of object instances. Many instances are in the array more than once.
var array = [
opaqueObjectA,
opaqueObjectB,
opaqueObjectA,
opaqueObjectC,
opaqueObjectB,
opaqueObjectC,
opaqueObjectA,
];
I don't care about the order, I care that the objects that are the same instance end up next to each other. In other words after sorting, one possible result would be
var array = [
opaqueObjectB,
opaqueObjectB,
opaqueObjectC,
opaqueObjectC,
opaqueObjectA,
opaqueObjectA,
opaqueObjectA,
];
I don't care if the A's or the B's or the C's come first, I only care that objects of the same instance are next to each other after sorting.
So the questions are
is JavaScript sort guaranteed to handle this case?
If not how can I do it? The sort function requires me to return -1 if a < b, 1 of a > b and 0 if a === b but given the objects are opaque, and since I don't have access to pointer values or something else, I have nothing to compare them with to get a less than or greater than result, only an equal result.
I can go add some sortId to each opaque object but that seems kind of bad to add properties to objects, I'd have no idea if I'm cobbering a property. I could make another set of objects, each with an id and a reference to one instance, sort those, then collect their instances into a new array. That also seems rather lame to have to go build an entire array of objects to sort.
Actually I'd also like to be able to sort by multiple instances which is a property but still not comparable. Example:
var array = [
{ thing: opaqueThingA, stuff: opaqueStuffG, },
{ thing: opaqueThingA, stuff: opaqueStuffH, },
{ thing: opaqueThingB, stuff: opaqueStuffG, },
{ thing: opaqueThingC, stuff: opaqueStuffG, },
{ thing: opaqueThingB, stuff: opaqueStuffH, },
{ thing: opaqueThingA, stuff: opaqueStuffG, },
{ thing: opaqueThingA, stuff: opaqueStuffH, },
{ thing: opaqueThingC, stuff: opaqueStuffG, },
];
I'd like to be able to sort them first by thing, then by stuff. So one possible result would be
var array = [
{ thing: opaqueThingB, stuff: opaqueStuffG, },
{ thing: opaqueThingB, stuff: opaqueStuffH, },
{ thing: opaqueThingA, stuff: opaqueStuffG, }, // Note the G' s
{ thing: opaqueThingA, stuff: opaqueStuffG, }, // Are next to
{ thing: opaqueThingA, stuff: opaqueStuffH, }, // each other
{ thing: opaqueThingA, stuff: opaqueStuffH, },
{ thing: opaqueThingC, stuff: opaqueStuffG, },
{ thing: opaqueThingC, stuff: opaqueStuffG, },
];
This would be trivial in C/C++ because I could just compare the addresses
of the instances. Is there a way to do this in JavaScript without dirtying the objects with hacked on properties and without making temporary arrays just for sorting?
You cannot do it using Array.prototype.sort because the arguments sent to the comparator function are only objects and you need to keep track of the objects yourself somewhere.
var objectA = {name: 'objectA'}, objectB = {name: 'objectB'}, objectC = {name: 'objectC'};
var original = [objectA, objectB, objectA, objectC, objectB, objectC, objectA];
var instanceSort = function (original) {
var seen = [], comparator = function (a, b) {
if (seen.indexOf(a) === -1) seen.push(a);
if (seen.indexOf(b) === -1) seen.push(b);
return seen.indexOf(a) - seen.indexOf(b);
}
return original.sort(comparator);
}
var sorted = instanceSort(original);
console.log(sorted);
If you need to call this function multiple times, you could add it to Array.prototype like so, instead of polluting the scope:
Array.prototype.instanceSort = function (original) { ... }
and then call it on your array like so: var sorted = original.instanceSort()
#steady rain complained that this is inefficient, so here's an improved version:
var instanceSort = function (original) {
var i, o, comparator, sorted;
for (i = original.length - 1; i >= 0; i--) {
o = original[i];
if (!o.hasOwnProperty('__instanceSortIndex')) o.__instanceSortIndex = i;
}
comparator = function (a, b) {
return a.__instanceSortIndex - b.__instanceSortIndex;
}
sorted = original.sort(comparator);
for (i = original.length - 1; i >= 0; i--) {
delete original[i].__instanceSortIndex;
}
return sorted;
}
this assumes you'll never ever need to use a property called __instanceSortIndex on any object that might ever end up being sorted by this function. It's a bit dirty in theory, but it's safe to use in practice.
Here's another one, but it will only work if you're targeting modern browsers which support WeakMap and you like the idea of sending the function as argument to .sort():
var objectA = {name: 'objectA'}, objectB = {name: 'objectB'}, objectC = {name: 'objectC'};
var original = [objectA, objectB, objectA, objectC, objectB, objectC, objectA];
var instanceSort = function (a, b) {
if (!instanceSort.history) {
instanceSort.history = new WeakMap();
instanceSort.uid = 0;
}
var h = instanceSort.history, aIndex, bIndex;
if (h.has(a)) aIndex = h.get(a);
else h.set(a, aIndex = ++instanceSort.uid);
if (h.has(b)) bIndex = h.get(b);
else h.set(b, bIndex = ++instanceSort.uid);
return aIndex - bIndex;
}
var sorted = original.sort(instanceSort);
A WeakMap can hold existing object as keys but it will not add to the object reference count, so basically you can use it to store hidden properties without worrying that you're also holding references and creating memory leaks. In this case I am using a WeakMap inside the instanceSort comparator function to assign a unique integer identifier to each object it receives as argument and use the difference between identifiers as the "difference" between objects.
The downside is that you cannot use it for browsers older than IE11, Firefox 31 ESR, Safari 7.1, iOS 7, Konqueror (all versions) and Opera (all versions). See the link above for detailed information regarding browsers which support WeakMap.
My interpretation of your question is that you wanna sort by type so here is what I came up with :
function Test() {} // Dummy object
var test = [a = 1, b = 2, c = 3, a, "titi", b, c, new Test(), a, b, c, new Test(), "toto"]; // Test Array
function sortByType(cpy) {
var arr = cpy.slice();
var arrs = [];
var ret = [];
for (var i = 0; i < arr.length; i++) {
for (var j = 0; j < arrs.length; j++) {
if (arrs[j][0].__proto__ === arr[i].__proto__) {
arrs[j].push(arr[i]);
arr[i] = null;
break;
}
}
if (arr[i] !== null) {
arrs.push([arr[i]]);
arr[i] = null;
}
}
ret = Array.prototype.concat.apply([], arrs);
return (ret);
}
test = sortByType(test); // [1, 2, 3, 1, 2, 3, 1, 2, 3, "titi", "toto", Test, Test]
I basically compare x.__proto__ to check whether object are from the same parent or not.
Edit : Fixed a typo thanks to #Travis J
Edit : Changed clone method
It seems to me you can use the native Array.prototype.sort() for primary/secondary grouping since the objects in the array are the same object used multiple times. The approach I've come up with does not modify the objects, but does use a temporary array and create a new, grouped array based on both the primary and secondary "sort" items. I'm not sure how true sorting can be accomplished when the object is opaque since you have to have some criteria by which you can sort by.
//object setup
//inner objects, note that "name" is a convention only used to identify sort order (secondary grouping)
var innerObjA = { "name": "a" };
var innerObjB = { "name": "b" };
var innerObj1 = { "name": "1" };
var innerObj2 = { "name": "2" };
var innerObj3 = { "name": "3" };
//parenting objects (primary grouping)
var obj1 = { "first": innerObjA, "second": innerObj1 };
var obj2 = { "first": innerObjA, "second": innerObj2 };
var obj3 = { "first": innerObjA, "second": innerObj1 };
var obj3 = { "first": innerObjB, "second": innerObj1 };
var obj4 = { "first": innerObjB, "second": innerObj1 };
var obj5 = { "first": innerObjB, "second": innerObj2 };
var obj6 = { "first": innerObjB, "second": innerObj3 };
//out of order array
var original = [obj6, obj2, obj4, obj1, obj5, obj3];
//helper to show the order of these objects
function showOrder(myArray){
myArray.forEach(function(index) { console.log(index.first.name, index.second.name); });
console.log('-----------');
}
//helper to handle the native sort
function doNativeSort(myArray, sortBy) {
myArray.sort(function(a, b) {
return a[sortBy]===b[sortBy]?0:1;
});
return myArray; //returns so we can use in-line
}
showOrder(original);
//primary sort is done by the property "first" within the objects
// in our array, since the OP states that many instances of an
// object are in the array more than once, the native JS sort will
// work to GROUP items which is what we do here.
doNativeSort(original, "first");
showOrder(original);
//secondary sort is more challenging and requires temp arrays
// to group the like items so that we can again use the native
// sort, we'll use the reduce method to compare the array that
// already has the first grouping/sorting applied to it.
function myReduce(original) {
var newArr = [], subGroup = []; //new stuff
original.reduce(function(previousValue, currentValue) {
if (previousValue.first === currentValue.first) {
subGroup.push(currentValue);
} else {
if (subGroup.length > 0) {
//concat the sorted sub-group onto the new array
newArr = newArr.concat(doNativeSort(subGroup, "second"));
}
//starting the next subgroup
subGroup = [currentValue];
}
//becomes the previous value in the next invocation
return currentValue;
}, original[0]);
//sort the final subGroup and add to the new array
newArr = newArr.concat(doNativeSort(subGroup, "second"));
return newArr;
}
var groupedArray = myReduce(original);
showOrder(groupedArray);
http://jsbin.com/yeboyosome/4/edit?js,console
I'd watch performance in general for this problem. This approach could be inefficient for large arrays since we're defaulting to 1 if the objects are not equal in the native Array.prototype.sort() method (potentially forcing more invocations of the compareFunction).
With an array of objects in a form like this:
[
{
1429={
{
8766={...},
8483={...},
7345={...}
}
}
},
{
9041={...}
}
]
how could i get back an array like this?:
[1429, 9041]
If the array of objects would be in another structure this code would work:
var obj = {
"5": "some",
"8": "thing"
};
var keys = $.map(obj, function (value, key) {
return key;
});
console.log(keys);
That would return [5, 8]. But in my example it just would return the indexes [0,1]
Even if I wouldn't know the depth of the object - is it possible to get the values on that level? I dont need the indexes, I need those values. I couldn't find anything about it so far. Any tips for me maybe?
P.S.: I know that i could work out something with these keys and a loop, but I'm just asking for a simplier way to do it.
Regards
you are looking for the keys in a json object, you can get them this way:
Object.keys(obj);
for the object example:
var obj = {
"5": "some",
"8": "thing"
};
you will get:
["5","8"]
for an array of object of this type:
var arrayObject = [{},{},{}];
you can use a map and get the keys:
var keys = arrayObject.map(function(k){
return Object.keys(k);
});
keys is an array of arrays of keys. Example, for the following object (similar to your data structure):
var l= [
{
1429:{
8766: "test",
8483:"test",
7345: "test"
}
},
{
9041: "test"
}
];
you will get:
[["1429"],["9041"]]
apply concat and you will get what you are looking for. Here how to apply concat in the case of multiple arrays.
var arrayOfKeys = [].concat.apply([], keys);
now you will get:
["1429","9041"];
In your specific case you could use
var keys = [];
root.forEach(function(v) { keys = keys.concat(Object.keys(v)); });
If instead you have a tree of arrays and you want the keys of all other objects instead (but not recursing into objects) then a simple recursive function would do it:
function topKeys(x) {
if (x && x.constructor === Array) {
var result = [];
x.forEach(function(item) {
result = result.concat(topKeys(item));
});
return result;
} else if (typeof x === "object") {
return Object.keys(x);
} else {
return [];
}
}
I am trying to implement a Trie in Javascript, which is easy enough but I seem to have hit a road block with my object.
The nodes are structured as follows:
var node = {
children: []
}
Children is an array of nodes that is mapped by a letter in a string. So the string "Test" would look like this:
root = {
children: [
't' => {
children: [
'e' => {
children: [
's' => {
children: [
't' => {
children: []
}
]
}
]
}
]
}
]
};
So each children array should have a length of 1, but if do something like alert(this._root.children.length); I get zero. Any thoughts on why this is happening?
Here is the rest of my implementation:
function Trie() {
this._root = {
children: []
};
}
Trie.prototype = {
//restore constructor
constructor: Trie,
add: function (str){
var curr = this._root,
prev,
currchar;
// For each character in the string
for(var i = 0, j = str.length; i < j; i++) {
// Insert only lowercase letters for efficiency
currchar = str.toLowerCase().charAt(i);
prev = curr;
curr = prev.children[currchar];
// Traverse until we hit a non-existant node
if(typeof(curr) == "undefined") {
// Make a new node
prev.children[currchar] = {
children: []
};
curr = prev.children[currchar];
}
}
}
You are adding properties to the array instance object, not elements to the array. The length property only includes array elements, not properties on the array instance object.
var a = [23, 42];
console.log(a.length); // 2
a['foo'] = 'bar';
console.log(a.length); // 2
a[2] = 1337;
console.log(a.length); // 3
EDITED:
You could instead structure the nodes like this:
var node = {
children: {},
length: function () {
var i = 0;
var k;
for (k in this.children) {
if (this.children.hasOwnProperty(k)) {
i++;
}
}
return i;
}
};
This is inefficient, of course. You should instead define a Node class with the length method on its prototype. Alternatively, define an add method that updates the length property.
I think that the problem is that you use a javasrcipt array as an associative array (as found in other languages). In javascript "associative" arrays are objects that don't have a length property. Normal arrays have numeric indices.
Irrelevant to the question but you might find this useful.
Maybe you want
str.toLowerCase().charCodeAt(i)
instead of
str.toLowerCase().charAt(i)
If str is "f1", the properties you're adding to the children array are "f" and "1" which should cause an array with property named f and length 0, and another child array with length 2 and property 1.
To get only numeric properties, you should make sure your property names are valid array indices -- positive integers representable in 31 bits.
By using charCodeAt instead of charCode, you would get the property names 102 and 49 instead of "f" and 1.