I've got this data:
const items = [
{
_id: 0,
content: 'Item 1 something',
note: 'Some note for item 1'
},
{
_id: 5,
content: 'Item 1.1 something',
note: 'Some note for item 1.1'
},
{
_id: 1,
content: 'Item 2 something',
note: 'Some note for item 2',
subItems: [
{
_id: 2,
parent_id: 1,
content: 'Sub Item 1 something',
subItems: [{
_id: 3,
parent_id: 2,
content: 'Sub Sub Item 4'
}]
}
]
}
];
Using Javascript, how can I navigate/insert into the tree, provided at any point I have the _id of one item in the tree.
For example, some case scenarios:
I am at _id 3 and want to insert another sibling to _id 3
I am at _id 2 and want to go up to _id 1 - how do I get _id 1?
I am at _id 5 and want to go to _id 1
How do I navigate the tree using only an _id?
You could iterate the array and test if _id property has the wanted value. Then save either the node, the parent or the next item of the array.
For getting the parent node, the actual parent is saved as a closure and returned if the wanted _id is found.
All functions test for subItems as array and if, it performs an iteration over subItems.
function getNode(array, id) {
var node;
array.some(function iter(a) {
if (a._id === id) {
node = a;
return true;
}
return Array.isArray(a.subItems) && a.subItems.some(iter);
});
return node;
}
function getParent(array, id) {
var parent ;
array.some(function iter(p) {
return function (a) {
if (a._id === id) {
parent = p;
return true;
}
return Array.isArray(a.subItems) && a.subItems.some(iter(a));
};
}(undefined));
return parent;
}
function getNextNode(array, id) {
var node;
array.some(function iter(a, i, aa) {
if (a._id === id) {
node = aa[i + 1];
return true;
}
return Array.isArray(a.subItems) && a.subItems.some(iter);
});
return node;
}
var items = [{ _id: 0, content: 'Item 1 something', note: 'Some note for item 1' }, { _id: 5, content: 'Item 1.1 something', note: 'Some note for item 1.1' }, { _id: 1, content: 'Item 2 something', note: 'Some note for item 2', subItems: [{ _id: 2, parent_id: 1, content: 'Sub Item 1 something', subItems: [{ _id: 3, parent_id: 2, content: 'Sub Sub Item 4' }] }] }];
console.log(getNode(items, 3));
console.log(getParent(items, 2));
console.log(getNextNode(items, 5));
.as-console-wrapper { max-height: 100% !important; top: 0; }
Using Javascript, how can I navigate/insert into the tree, provided at any point I have the _id of one item in the tree.
You have to recursively loop over the tree and keep track of where you are and where you have been.
JavaScript references point in one direction. Given nothing but a reference to the object with _id 1, you have no connect to the array it is in at all. (It could even exist in multiple arrays or multiple places in the same array).
In general, you need to search the tree (recursion is your friend) and track the indexes of the members you care about.
Once you know the indexes you are dealing with, you can use splice.
I've developed solution for issues like that. I called it backlooker. My function looks:
var backlooker = function(obj) {
for (key in obj) {
if (obj[key]._) {
break;
}
if (obj[key] instanceof Object) {
obj[key]._ = obj;
backlooker(obj[key])
}
}
return obj;
}
You must improve your object first:
items = backlooker(items);
And now you are able to do sth like this:
a = items[2].subItems[0].subItems[0];
c = a._._._._._._._;
c == items; //true
Only one issue: code will not work properly if you already have keys named _ in your object (I think that's very rare situation, but possible).
it's not as easy as "navigating".. the following function will loop through the object and provide things that you can use to achieve what you want.. like name, values, types, number of children and depths.
also look below for specific examples for your case
Main function
you simply call it like this: loopThrough(items)and watch your console for details.
function loopThrough(obj, depth) {
if (typeof(depth) === "undefined") {
depth = 0; // depth 0 means the "root" of your object
} else {
depth++ // increase depth if exist... depth 1 means a property of an object on depth 0
}
for (keyName in obj) {
let thisObj = obj[keyName] //value of this object
let type = thisObj.constructor.name // type: Array, Object, String, Number or Function...
if (type === "Object" || type === "Array") { // to check if this object "have children" to loop through
let childCount = type === "Object" ? Object.keys(thisObj).length : thisObj.length
console.group(depth + " (" + type + ") " + keyName + " : " + childCount) // starts a collapsable group called: depth, type and key
loopThrough(thisObj, depth) //loop through the child object
console.groupEnd() // closes the group
} else { // doesn't have children (a String, Number or Function)
console.log(depth + " (" + type + ") " + keyName + " : " + thisObj) // types: depth, type key and value
}
}
}
Example
here's an example targeting _id:3 in this example I added a sibling to the wanted key.
loopThrough(items, "_id", 3)
function loopThrough(obj, wantedKey = "", wantedValue = "", depth) {
if (typeof(depth) === "undefined") {
depth = 0;
} else {
depth++
}
for (keyName in obj) {
let thisObj = obj[keyName]
let type = thisObj.constructor.name
if (type === "Object" || type === "Array") {
let childCount = type === "Object" ? Object.keys(thisObj).length : thisObj.length
loopThrough(thisObj, wantedKey, wantedValue, depth)
}
if (keyName === wantedKey && thisObj === wantedValue){
siblings = Object.keys(obj)
console.log('%c Hello!, I am ' + wantedKey +":"+ wantedValue, 'color: green');
console.log('%c I have '+ siblings.length + " siblings: " + siblings.toString(), 'color: green');
console.log("%c adding a new sibling...", 'color: grey')
obj["new_sibling"] = "new_sibling_value" // add a sibling to _id 3
siblings = Object.keys(obj)
console.log('%c now I have '+ siblings.length + " siblings: " + siblings.toString(), 'color: green');
console.log('%c I am at depth ' + depth, 'color: blue');
console.log('%c it should be simple to find a way to get my parent _id at depth ' + (depth - 1) , 'color: blue');ParentID
console.log(JSON.stringify(items, null, 4));
}
}
}
for your 2nd request you'll have to tweak the function to store the depth of the wanted key and look for its parent _id at depth - 1 by recalling the function or creating another one
for the third request you can count++ the keys and once you find the wantedKey you store the count and loop through again and look for the count - 1 aka previous sibling or count + 1 aka next sibling
as you can see, it's not a simple task, but it's totally possible with some creativity, best of luck.
Related
I would like to declare a set of fixed vars to be used as indicies to a large array
e.g. var age = 1, height = 2, weight = 3, name = 4, address = 5, education = 6.
I know very well that I if I used an object to store this data I would not have to declare each index, but assume for a few minutes that I need to use an array to store the data.
My problem is in in many cases I have such lists of indicies that exceed 30 or 40 in length and quite often I want to insert a new index into the middle of the list and I have to edit the list and slowly increment each existing defined value by one (or more if I insert more than one new index at a time).
How can I cleanly declare and initialise this set of vars to avoid this problem.
Could I define an object to contain them all and pass the object to an initialiser function that assigns start values to each?
What if I later wanted to minify the code and replace the variables with actual values.
You can go with typescript way.
In typescript:
enum Person {
age = 1,
height,
weight
}
is equivalent of
var Person;
(function (Person) {
Person[Person["age"] = 1] = "age";
Person[Person["height"] = 2] = "height";
Person[Person["weight"] = 3] = "weight";
})(Person || (Person = {}));
The rough implementation using es6 syntax is given below. A util class Enum.
Using Proxy:
class Enum {
constructor(args, startIndex = 0, defaultValue) {
const em = args.split("|").reduce((m, key, index) => {
m[(m[key] = index + startIndex)] = key;
return m;
}, {});
em.value = em.key = (k) => em[k];
return new Proxy(em, {
get: (obj, prop) => (prop in obj ? obj[prop] : defaultValue),
});
}
}
const Person = new Enum("AGE|HEIGHT|WEIGHT", 2, -1);
console.log(Person); // { '2': 'AGE', '3': 'HEIGHT', '4': 'WEIGHT', AGE: 2, HEIGHT: 3, WEIGHT: 4 }
// How to use it:
let { AGE, HEIGHT, X } = Person;
console.log(AGE, HEIGHT, X); // 2, 3, -1
// To get key
if ("HEIGHT" === Person.key(3)) {
console.log("You are right!"); // You are right
}
// To get value
if (2 === Person.value("AGE")) {
console.log("You are right, Again!"); // You are right
}
Using Basic Javascript:
const enumVars = (args, startIndex = 1) => {
return args.split("|").reduce((m, key, index) => {
m[(m[key] = index + startIndex)] = key;
return m;
}, {});
};
const Person = enumVars("AGE|HEIGHT|WEIGHT", 2);
console.log(Person); // { '2': 'AGE', '3': 'HEIGHT', '4': 'WEIGHT', AGE: 2, HEIGHT: 3, WEIGHT: 4 }
// How to use it:
let { AGE, HEIGHT } = Person;
console.log(AGE, HEIGHT); // 2, 3
// To get key
if ("HEIGHT" === Person[3]) {
console.log("You are right!");
}
I have an array of objects. These objects have a property id. I need a function which returns the next available id (which is not used by an object).
array = [
{
id: 1
},
{
id: 2
},
{
id: 5
},
{
id: 3
}
]
I would like to have a function which takes an array as an input and returns a number (which is the next free id).
In the example case:
findFreeId(array){
magic happens
}
result --> 4
How about something like this?
function findFreeId (array) {
const sortedArray = array
.slice() // Make a copy of the array.
.sort(function (a, b) {return a.id - b.id}); // Sort it.
let previousId = 0;
for (let element of sortedArray) {
if (element.id != (previousId + 1)) {
// Found a gap.
return previousId + 1;
}
previousId = element.id;
}
// Found no gaps.
return previousId + 1;
}
// Tests.
let withGap = [{id: 1}, {id: 2}, {id: 5}, {id: 3}];
let noGap = [{id: 1}, {id: 2}];
let empty = [];
console.log(findFreeId(withGap)); // 4
console.log(findFreeId(noGap)); // 3
console.log(findFreeId(empty)); // 1
A simple approach is to get all the ID values, sort them, then starting at 0 look for the first missing number in the sequence. That may be OK where efficiency doesn't matter, but a more efficient method is to:
Get the IDs
Sort them
Step through the values to get the next available number
Insert the value in the list of IDs
Store the value so next time it starts at #3 from the previous value + 1
E.g.
class IDStore {
constructor(dataArray) {
if (!Array.isArray(dataArray)) {
return null;
}
this.previousIndex = 0;
this.indexes = dataArray.map(obj => obj.id).sort();
}
get nextIndex() {
while (this.indexes[this.previousIndex] == this.previousIndex) {
this.previousIndex++;
}
return this.previousIndex;
}
addIndex(index) {
if (!Number.isInteger(index) || this.indexes.find[index]) {
return null;
}
this.indexes.push(index);
this.indexes.sort();
return index;
}
}
var data = [ { id: 1 }, { id: 2 }, { id: 5 }, { id: 3 } ];
// Create an ID store
var idStore = new IDStore(data);
// Insert more objects in the array with unique IDs
for (var i=0, next; i<4; i++) {
// Current list of indexes
console.log('Indexes: ' + idStore.indexes);
// Get the next available index
next = idStore.nextIndex;
console.log('Next available: ' + next);
// Calling nextIndex doesn't affect next index
next = idStore.nextIndex;
console.log('Next available: ' + next);
// Use next index
data.push({id: next});
// Adding next index is manual
idStore.addIndex(next);
console.log('Added: ' + next);
}
// Data structure is independent
console.log('End: ' + JSON.stringify(data));
This is somewhat simplistic in that it assumes the IDs are sequential integers starting at 0 and doesn't have much validation or error handling.
Maintaining the id is separate from adding new members to the data array. It would be much better to combine the operations, so an "add object" method gets the next available ID, adds it to the object, adds the object to the array, updates the index and returns the new ID.
const findFreeId = (ids) => {
let id = 0;
for (id; ; id++) {
let isFree = true;
for (let i = 0; i < array.length; i++) {
const e = ids[i];
if (e === id) {
isFree = false;
break;
}
}
if (isFree) break;
}
return id;
}
I know we can match array values with indexOf in JavaScript. If it matches it wont return -1.
var test = [
1, 2, 3
]
// Returns 2
test.indexOf(3);
Is there a way to match objects? For example?
var test = [
{
name: 'Josh'
}
]
// Would ideally return 0, but of course it's -1.
test.indexOf({ name: 'Josh' });
Since the two objects are distinct (though perhaps equivalent), you can't use indexOf.
You can use findIndex with a callback, and handle the matching based on the properties you want. For instance, to match on all enumerable props:
var target = {name: 'Josh'};
var targetKeys = Object.keys(target);
var index = test.findIndex(function(entry) {
var keys = Object.keys(entry);
return keys.length == targetKeys.length && keys.every(function(key) {
return target.hasOwnProperty(key) && entry[key] === target[key];
});
});
Example:
var test = [
{
name: 'Josh'
}
];
var target = {name: 'Josh'};
var targetKeys = Object.keys(target);
var index = test.findIndex(function(entry) {
var keys = Object.keys(entry);
return keys.length == targetKeys.length && keys.every(function(key) {
return target.hasOwnProperty(key) && entry[key] === target[key];
});
});
console.log(index);
Note that findIndex was added in ES2015, but is fully polyfillable.
Nope, you can't and the explanation is simple. Despite you use the same object literal, two different objects are created. So test would have another reference for the mentioned object if you compare it with the reference you are looking for in indexOf.
This is kind of custom indexOf function. The code just iterates through the items in the object's array and finds the name property of each and then tests for the name you're looking for. Testing for 'Josh' returns 0 and testing for 'Kate' returns 1. Testing for 'Jim' returns -1.
var test = [
{
name: 'Josh'
},
{
name: 'Kate'
}
]
myIndexOf('Kate')
function myIndexOf(name) {
testName = name;
for (var i = 0; i < test.length; i++) {
if(test[i].hasOwnProperty('name')) {
if(test[i].name === testName) {
console.log('name: ' + test[i].name + ' index: ' + i);
return i;
}
}
}
return -1;
}
You can loop on array and then look for what you want
var test = [{ name: 'Josh' }]
const Myname = test.map((item) => { return item.name; }).indexOf("Josh")
I have an array containing objects with this structure:
var results = [{
AuthorId: 2,
Id: 89,
caseId: 33 //some key
},...];
Now, I want to check if the objects in this array exist 2 or more times and log them in the console.
My approach:
$.each(results, function (i, result) {
var stringRes = result.AuthorId + ";" + result.caseId;
$.each(results, function (j, toTest) {
if (j <= results.length - 2) {
var stringToTest = results[j + 1].AuthorId + ";" + results[j + 1].caseId;
if (stringToTest == stringRes) {
console.log(result.Id);
//some function to do something with duplicates
}
}
});
});
Firstly, I know making strings and comparing them isn't really good. Secondly, this will log every item at least once, because every item compares to each other (= the item compares itself to itself).
Is this fixable with a (more or less) fast and reliable way?
You could use a hash table or a map for counting. If the count is 2 or greater make something. As key, I suggest to use the stringified object, if the object has always the same structure.
var results = [{ AuthorId: 2, Id: 89, caseId: 33 }, { AuthorId: 2, Id: 89, caseId: 33 }],
hash = Object.create(null);
results.forEach(function (a) {
var key = JSON.stringify(a);
hash[key] = (hash[key] || 0) + 1;
if (hash[key] >= 2) {
console.log('count of 2 or more of ' + key);
}
});
I have an array currently only with names because I cannot figure out how to add more information but not make the script sort that data. For every entry in the array I wish to add a number between 1-20 for each, and also a count of how many is named that name. So it would something like 1. Nielsen (100,000). It's only a problem with my second function because I need to sort it by length.
<script>
var arr = []
arr[0] = " Nielsen"
arr[1] = " Jensen"
arr[2] = " Hansen"
arr[3] = " Pedersen"
arr[4] = " Andersen"
arr[5] = " Christensen"
arr[6] = " Larsen"
arr[7] = " Sørensen"
arr[8] = " Rasmussen"
arr[9] = " Jørgensen"
arr[10] = " Petersen"
arr[11] = " Madsen"
arr[12] = " Kristensen"
arr[13] = " Olsen"
arr[14] = " Thomsen"
arr[15] = " Christiansen"
arr[16] = " Poulsen"
arr[17] = " Johansen"
arr[18] = " Møller"
arr[19] = " Mortensen"
document.getElementById("liste").innerHTML = arr; // Skriver den oprindelige rækkefølge
function Sorter1() {
arr.sort(); // Sorter efter aflabetisk rækkefølge
document.getElementById("liste").innerHTML = arr; // Skriver rækkefølgen
}
function Sorter2() {
arr.sort(function (a, b) {
return b.length - a.length || // sorter efter længde
a.localeCompare(b); // Sorter efter aflabetisk rækkefølge
});
document.getElementById("liste").innerHTML = arr; // Skriver rækkefølgen
}
</script>
If I understand you correct you would like to create a multidimensional array and then sort it on the name alphabetically and on character count. If that is correct I would suggest you to create an multidimensional object with the data needed. Then you will be able to sort on the name key and preserve the other information correctly.
Check this out, it may get you in the right direction
var arr = [
{
name: 'Nielsen',
num: 1,
count: 100
},
{
name: 'Jensenlongest',
num: 15,
count: 230
},
{
name: 'Jensenlong',
num: 13,
count: 500
},
{
name: 'Jensen',
num: 2,
count: 300
},
{
name: 'Hansen',
num: 5,
count: 400
}
]
// Just adds the unsorted arr to the list for demo purpose
updateList(arr)
// On "Sort by length" button click
document.getElementById('sort-by-length').addEventListener('click', function (event) {
arr.sort(sortNameByLength);
updateList(arr);
})
// On "Sort alphabetically" button click
document.getElementById('sort-alphabetically').addEventListener('click', function (event) {
arr.sort(sortNameAlphabetically);
updateList(arr);
})
// Sort by name alphabetically
function sortNameAlphabetically(a, b) {
return a.name > b.name
}
// Sort by name length
function sortNameByLength(a, b) {
return a.name.length - b.name.length
}
// Updates the list according to the current sorting of the arr
function updateList(names) {
var listHtml = ''
names.forEach(function (item, index) {
listHtml += item.name + ', ' + item.num + ' (' + item.count + ')<br>'
})
document.getElementById("liste").innerHTML = listHtml
}
https://jsfiddle.net/sbe8yzv0/4/
This will result in a list like this.
Hansen, 5 (400)
Jensen, 2 (300)
Jensenlong, 13 (500)
Jensenlongest, 15 (230)
Nielsen, 1 (100)
You can use an array of complex objects with the data structure you like (just be consistent). Then define your own sort() method that will compare only the name parameter of your objects. Here's a simple example:
var arr = [];
arr[0] = {ID: 1, Name: "Nielsen", Value: "100"};
arr[0] = {ID: 2, Name: "Jensen", Value: "200"};
// Sort based on the second column, 'Name'.
function sortByName(){
arr.sort(
function(x, y){
return x.Name > y.Name; // Compare and sort based on the 'Name' column only.
}
);
console.log(arr[0]); // If results are correct this is 'Jensen'.
console.log(arr[1]); // If results are correct this is 'Nielsen'.
}
Adapt this to your needs (add the proper columns and data, add the proper variables, make it so that it shows in your page's HTML) and it will do what you want.