Remove item from nested structure [duplicate] - javascript

This question already has answers here:
remove object from nested array
(4 answers)
Closed 4 years ago.
I have the following object:
{
id: 1,
children: [
{id: 2},
{id: 3, children: [
{id: 4 }
]}
]
}
I want to be able to remove a specific object by using the id property (e.g. remove the object with an id of 4). I can obviously find the object by using arr.findIndex but how do I remove it from the enclosing array? I'm struggling with finding how to get the parent array so I can remove it. Again I know how to remove from an array using splice but the problem I'm having is how to do this programmatically so I can remove any item from anywhere in a nested structure as above.

Try following
let obj = {id: 1,children: [{id: 2},{id: 3, children: [{id: 4 }]}]};
/* o is object or sub-object
* k is id to be deleted
* p is parent of object, same as o for first time
* index is the index of item in the children array */
function removeKey(o, k, p=o, index) {
if(o.id === k) { // if this is the object that needs to be removed
// For first level object, make the object empty
if(o.id === p.id) {delete o.children; delete o.id}
// For others remove it from the children array
else p.children.splice(index,1);
} else if(o.children) { // if the object is not a match and has children
// iterate over the children and check and remove key
for (let i = 0; i < o.children.length; i++) {
if(removeKey(o.children[i], k, o, i)) break;
}
}
}
removeKey(obj, 4);
console.log(obj);

Related

javascript how to avoid numbered object keys to be sorted automatically [duplicate]

Why I met this problem:
I tried to solve an algorithm problem and I need to return the number which appeared most of the times in an array. Like [5,4,3,2,1,1] should return 1.
And also when two number appear same time as the maximum appearance return the one came first. Like [5,5,2,2,1] return 5 because 5 appear first. I use an object to store the appearance of each number. The key is the number itself.
So When the input is [5,5,2,2,1] my object should be
Object {5: 2, 2: 2, 1: 1} but actually I got Object {1: 1, 2: 2, 5: 2}
So When I use for..in to iterate the object I got 2 returned instead of 5 . So that's why I asked this question.
This problem occurs in Chrome console and I'm not sure if this is a common issue:
When I run the following code
var a = {};
a[0]=1;
a[1]=2;
a[2]=3;
a is: Object {0: 1, 1: 2, 2: 3}
But when I reverse the order of assignment like:
var a = {};
a[2]=3;
a[1]=2;
a[0]=1;
a is also:Object {0: 1, 1: 2, 2: 3}
The numeric property automatic sorted in ascending order.
I tried prefix or postfix the numeric property like
var a = {};
a['p'+0]=1;
a['p'+1]=2;
a['p'+2]=3;
console.log(a);//Object {p0: 1, p1: 2, p2: 3}
And this keep the property order. Is this the best way to solve the problem? And is there anyway to prevent this auto sort behavior? Is this only happen in Chrome V8 JavaScript engine? Thank you in advance!
target = {}
target[' ' + key] = value // numeric key
This can prevent automatic sort of Object numeric property.
You really can't rely on order of an object fields in JavaScript, but I can suggest to use Map (ES6/ES2015 standard) if you need to preserve order of your key, value pair object. See the snippet below:
let myObject = new Map();
myObject.set('z', 33);
myObject.set('1', 100);
myObject.set('b', 3);
for (let [key, value] of myObject) {
console.log(key, value);
}
// z 33
// 1 100
// b 3
You are using a JS object, that by definition does not keep order. Think of it as a key => value map.
You should be using an array, that will keep whatever you insert on the index you inserted it into. Think of it as a list.
Also notice that you did not in fact "reverse the order of the assignment", because you inserted elements on the same index every time.
This is an old topic but it is still worth mentioning as it is hard to find a straight explanation in one-minute googling.
I recently had a coding exercise that finding the first occurrence of the least/most frequent integer in an array, it is pretty much the same as your case.
I encountered the same problem as you, having the numeric keys sorted by ASC in JavaScript object, which is not preserving the original order of elements, which is the default behavior in js.
A better way to solve this in ES6 is to use a new data type called: Map
Map can preserve the original order of elements(pairs), and also have the unique key benefit from object.
let map = new Map()
map.set(4, "first") // Map(1) {4 => "first"}
map.set(1, "second") // Map(2) {4 => "first", 1 => "second"}
map.set(2, "third") // Map(3) {4 => "first", 1 => "second", 2 => "third"}
for(let [key, value] of map) {
console.log(key, value)
}
// 4 "first"
// 1 "second"
// 2 "third"
However, using the object data type can also solve the problem, but we need the help of the input array to get back the original order of elements:
function findMostAndLeast(arr) {
let countsMap = {};
let mostFreq = 0;
let leastFreq = arr.length;
let mostFreqEl, leastFreqEl;
for (let i = 0; i < arr.length; i++) {
let el = arr[i];
// Count each occurrence
if (countsMap[el] === undefined) {
countsMap[el] = 1;
} else {
countsMap[el] += 1;
}
}
// Since the object is sorted by keys by default in JS, have to loop again the original array
for (let i = 0; i < arr.length; i++) {
const el = arr[i];
// find the least frequent
if (leastFreq > countsMap[el]) {
leastFreqEl = Number(el);
leastFreq = countsMap[el];
}
// find the most frequent
if (countsMap[el] > mostFreq) {
mostFreqEl = Number(el);
mostFreq = countsMap[el];
}
}
return {
most_frequent: mostFreqEl,
least_frequent: leastFreqEl
}
}
const testData = [6, 1, 3, 2, 4, 7, 8, 9, 10, 4, 4, 4, 10, 1, 1, 1, 1, 6, 6, 6, 6];
console.log(findMostAndLeast(testData)); // { most_frequent: 6, least_frequent: 3 }, it gets 6, 3 instead of 1, 2
To prevent the automatic sort of numeric keys of Object in Javascript, the best way is to tweak the Object keys a little bit.
We can insert an "e" in front of every key name to avoid lexicographical sorting of keys and to get the proper output slice the "e", by using the following code;
object_1 = {
"3": 11,
"2": 12,
"1": 13
}
let automaticSortedKeys = Object.keys(object_1);
console.log(automaticSortedKeys) //["1", "2", "3"]
object_2 = {
"e3": 11,
"e2": 12,
"e1": 13
}
let rawObjectKeys = Object.keys(object_2);
console.log(rawObjectKeys) //["e3", "e2", "e1"]
let properKeys = rawObjectKeys.map(function(element){
return element.slice(1)
});
console.log(properKeys) //["3", "2", "1"]
instead of generating an object like {5: 2, 2: 2, 1: 1}
generate an array to the effect of
[
{key: 5, val: 2},
{key: 2, val: 2},
{key: 1, val: 1}
]
or... keep track of the sort order in a separate value or key
I've stumbled with this issue with our normalised array which keyed with Ids> After did my research, I found out there's no way to fix using the object keys because by default the Javascript is sorting any object key with number when you iterate it.
The solution I've done and it worked for me is to put a 'sortIndex' field and used that to sort the list.
The simplest and the best way to preserve the order of the keys in the array obtained by Object.keys() is to manipulate the Object keys a little bit.
insert a "_" in front of every key name. then run the following code!
myObject = {
_a: 1,
_1: 2,
_2: 3
}
const myObjectRawKeysArray = Object.keys(myObject);
console.log(myObjectRawKeysArray)
//["_a", "_1", "_2"]
const myDesiredKeysArray = myObjectRawKeysArray.map(rawKey => {return rawKey.slice(1)});
console.log(myDesiredKeysArray)
//["a", "1", "2"]
You get the desired order in the array with just a few lines of code. hApPy CoDiNg :)
I came across this same problem, and after search a lot about that, i found out that the solution to prevent this behavior is make key as string.
Like that:
{"a": 2, "b": 2}
you can use Map() in javascript ES6 which will keep the order of the keys insertion.
just trying to solve your problem in an alternative solution, recently like to practise leetcode-like question
function solution(arr) {
const obj = {};
const record = {
value: null,
count: 0
};
for (let i = 0; i < arr.length; i++) {
let current = arr[i];
if (!obj[current]) {
obj[current] = 0;
}
obj[current]++;
if (obj[current] > record.count) {
record.value = current;
record.count = obj[current];
}
}
console.log("mode number: ", record.value);
console.log("mode number count: ", record.count);
}
simply do that while you're working with a numeric array index
data = {}
data[key] = value

Is it possible to find an index of a randomly-chosen JavaScript Object property (if said property is an array/object)? [duplicate]

This question already has answers here:
Accessing an object property with a dynamically-computed name
(19 answers)
Closed 4 years ago.
var geartable = {
shittyboots: {
name: "Shitty Boots",
cost: "500"
},
shittyarmor: {
name: "Shitty Armor",
cost: "1000"
},
shittyhelmet: {
name: "Shitty Helmet",
cost: "750"
}
};
var shops = {
positions: [
[2, 3],
[0, 1],
[2, 4]
],
inventory: [
[geartable.shittyboots.name, geartable.shittyboots.cost, "Available"],
[geartable.shittyarmor.name, geartable.shittyarmor.cost, "Available"],
[geartable.shittyhelmet.name, geartable.shittyhelmet.cost, "Available"]
]
};
function pickRandomItem(gearlist) {
var result;
var count = 0;
for (var item in gearlist) {
if (Math.random() < 1 / ++count) {
result = item;
}
}
console.log(geartable.result.cost);
return result;
}
Hi there. So, my problem, put simply, is that I'm trying to access an index/a property of a parent object property, but when I run the random selector function (pickRandomItem) on geartable and try to access a property of the result, it tells me that geartable.result.cost is undefined. I assume this is because, for some god forsaken reason, JavaScript is trying to find a property 'result' inside of geartable instead of looking for a property with the value of result inside of geartable.
Is there any way around this? I'm at the end of my rope and I can't imagine there is, due to the fact that object nesting is already pretty shifty as-is. I've tried this with arrays in the place of nested objects, but geartable.result[0]... etc still returns undefined.
This is the error in the JavaScript console, if you're curious:
pickRandomItem(geartable);
TypeError: geartable.result is undefined; can't access its "cost" property[Learn More]
One problem is:
for (var item in gearlist) {
// ...
result = item;
// ...
console.log(geartable.result.cost);
for..in loops iterate over property names (not values), and result never becomes a property of geartable. If you wanted to put a result property on the object, you would have to explicitly assign it:
for (var item in gearlist) {
if (Math.random() < 1 / ++count) {
gearlist.result = gearlist[item];
}
}
And then you could access gearlist.result in the future. Or, if you don't want to mutate the input object, just assign to the result variable, and to access the result later, access the result variable name later, not the geartable.result property.
Another problem is that the if (Math.random() < 1 / ++count) test is not balanced - objects occurring later will be more likely to be chosen than objects occurring earlier. A balanced random result can be produced by choosing a random item from an array of the object values, by using Math.random() only once:
const values = Object.values(gearlist);
const randomValue = values[Math.floor(Math.random() * values.length)];
console.log(randomValue.cost);
return randomValue;
var geartable = {
shittyboots: {
name: "Shitty Boots",
cost: "500"
},
shittyarmor: {
name: "Shitty Armor",
cost: "1000"
},
shittyhelmet: {
name: "Shitty Helmet",
cost: "750"
}
};
var shops = {
positions: [
[2, 3],
[0, 1],
[2, 4]
],
inventory: [
[geartable.shittyboots.name, geartable.shittyboots.cost, "Available"],
[geartable.shittyarmor.name, geartable.shittyarmor.cost, "Available"],
[geartable.shittyhelmet.name, geartable.shittyhelmet.cost, "Available"]
]
};
function pickRandomItem(gearlist) {
const values = Object.values(gearlist);
const randomValue = values[Math.floor(Math.random() * values.length)];
console.log(randomValue.cost);
return randomValue;
}
pickRandomItem(geartable);

Get difference between arrays with same objects

I have two arrays, like this:
var a = [
{id: 1}, {id: 2}, {id: 1}, {id: 3}
];
var b = [
{id: 1}, {id: 3}
];
I want to get the elements that array a has and array b doesn't. The expected outcome is:
[
{id: 1}, {id: 2}
]
I tried this:
a.filter(x => b.indexOf(x) == -1);
And this:
a.filter(x => new Set(b).has(x) == false);
The problem with those two is that it treats {id: 2} from array A and {id: 2} from array B as different objects, so those two lines of code simply return the full array A.
Another difficulty, I need {id: 1} and {id: 1} to be treated as two different objects, even if they have the exact same properties and values inside.
In my actual code, I have objects which are more complex and have more properties, but the situation is the same.
You could take a set and return the filtered array without the values of the set's id.
var a = [{ id: 1 }, { id: 2 }, { id: 1 }, { id: 3 }],
b = [{ id: 2 }, { id: 3 }],
s = new Set(b.map(({ id }) => id)),
result = a.filter(({ id }) => !s.has(id));
console.log(result);
I eventually got this working:
function differenceOf(arr1, arr2) {
var differences = $.extend(true, [], arr1); // creates clone
var arr2Duplicate = $.extend(true, [], arr2);
arr2Loop:
for(var i = 0; i < arr2Duplicate.length; i++) {
var obj2 = arr2Duplicate[i];
if(obj2 == null) continue;
differencesLoop:
for(var j = 0; j < differences.length; j++) {
var obj1 = differences[j];
if(obj1 == null) continue;
if(obj1.id == obj2.id) {
differences.splice(j, 1);
arr2Duplicate.splice(i, 1);
i = -1;
j = -1;
break differencesLoop;
}
}
}
return differences;
}
I cloned the two arrays for future manipulation, so references would be removed and the original arrays wouldn't be affected. I set the first array to be the differences array, so I can delete the elements that appear in the other array.
I iterate through the second array and then inside that loop I iterate through the first array. Then, I check for equal ID's; if so, then I found an element that is in both arrays, so I simply remove it from the first array. I also remove the element from the second array to prevent duplicate comparison, and then I break out of the loop to prevent more deletion of elements with the same ID.
When I remove the elements, the loop is still going, and eventually it'll reach that empty slot where the element used to be, so I check if it's null; if so, skip and keep going.
After both loops finish, I'm left with an array that has the elements that are different, regardless of elements that have the same properties.
EDIT: I changed the jQuery each loops to standard for loops because when I tried to break out of the inner loop, it broke out of the outer loop as well. I fixed this by adding those GOTO labels, which fixed the breaking problem.
When I detected a duplicate, I also reset the indices back to -1, because when the loop continues, the index will increment and skip over objects, leading to incorrect data. I reset it to -1 so that when the code block finishes, it'll increment back to 0 and it'll scan the arrays over again.

How do I delete the object? [duplicate]

This question already has answers here:
Remove Object from Array using JavaScript
(32 answers)
Closed 5 years ago.
Why is there an undefined in the array? How do I delete the object?
arr = [
{id:1,name:'aaa'},
{id:2,name:'bbb'},
{id:3,name:'ccc'}
];
for(var item in arr){
if(arr.hasOwnProperty(item)){
if(arr[item].id === 2){
delete(arr[item]);
continue;
}
}
}
console.log(arr);
Hope this is what you are trying to do:-
var arr = [
{id:1,name:'aaa'},
{id:2,name:'bbb'},
{id:3,name:'ccc'}
];
arr = arr.filter(function(item){
return item.id != 2;
});
console.log(arr)
Because delete don't arranges the indexes. From the documentation
When you delete an array element, the array length is not affected.
This holds even if you delete the last element of the array
For removing you need to use Array#splice function, which removes by index. Find first the index using Array#findIndex and then pass to the splice function.
arr = [
{id:1,name:'aaa'},
{id:2,name:'bbb'},
{id:3,name:'ccc'}
];
const index = arr.findIndex(item => item.id === 2);
arr.splice(index, 1);
console.log(arr);
You need to modify the arr which is array of objects.The delete operator removes a given property from an object, in your case you need to shift the elements after removing the object
var arr = [{
id: 1,
name: 'aaa'
},
{
id: 2,
name: 'bbb'
},
{
id: 3,
name: 'ccc'
}
];
// iterating the object
arr.forEach(function(item, index) {
//checking if id === 2, if it is 2 using splice
//method to remove element from that index, & shift by one element
if (item.id === 2) {
arr.splice(index, 1)
}
})
console.log(arr);

Why doesn't the splice method remove all the items from an array with matching property values?

I'm trying to remove items from orders[] array where the tableNumber provided in the function parameter matches table_id.
orders = [
{food_id: 5, table_id: 1},
{food_id: 5, table_id: 2},
{food_id: 5, table_id: 1},
{food_id: 5, table_id: 1},
{food_id: 5, table_id: 2},
{food_id: 5, table_id: 3},
];
removeAllOrdersForTable(tableNumber: Table): void
{
for (let order of this.orders) {
let match = (order.table_id == tableNumber);
match ? this.orders.splice(this.orders.indexOf(order), 1) : null;
}
}
If I execute removeAllOrdersForTable(1), it still leaves some items in orders[] array with table_id of 1. When I console.log(orders) after the function execution, I still get something like the following:
Array[1]
0: Object {food_id: 5, table_id: 1},
1: Object {food_id: 3, table_id: 1},
Is this the appropriate way to remove multiple objects from an array that match an object property values?
As you remove items while you have a loop on the same array, there will be items that get skipped in the loop.
Why not use filter? It does what you need, and you can assign the result back to this.items:
removeAllOrdersForTable(tableNumber: Table): void
{
this.orders = this.orders.filter(order => order.table_id == tableNumber);
}
The filter method creates a new array with the matches. By assigning that result back to this.orders you replace the original array, by the array of matches.
Mutating the array
In case you need this.orders array to keep its original reference, then you can use splice just once, namely by removing all original elements and inserting the filter matches instead. But only do this if the previous method does not work because of other dependencies in your code:
removeAllOrdersForTable(tableNumber: Table): void
{
this.orders.splice(0, this.orders.length,
...this.orders.filter(order => order.table_id == tableNumber);
}
Try using this
var _orders = this.orders.slice();
for (let order of _orders) {
if(order.table_id == tableNumber){
var index = this.orders.indexOf(order);
this.orders.splice(index, 1);
}
}
var newArray = this.orders.slice();
for (let order of newArray) {
let match = (order.table_id == tableNumber);
match ? this.orders.splice(this.orders.indexOf(order), 1) : null;
}

Categories