Javascript Array.Splice() doesn't remove entry - javascript

I'm trying to remove an entry from an array if a certain condition is true, but when I console.log the array of objects, the object has not been removed and I'm confused why. Am I using the Splice() function correctly?
var itemsProcessed;
people.forEach(async function(peep, index, object){
var d = new Date();
var currenttime = d.getTime();
if (peep.endtime < currenttime){
var rolesub = guild.roles.find(r => r.name == roleNameSub);
var user2 = await client.fetchUser(peep.id);
var member = await guild.fetchMember(user2);
member.removeRole(rolesub);
object.splice(index, 1); //This seems to go wrong...
console.log(peep.id + " Lost subscription!");
user2.send("Your subscription ended!");
}
itemsProcessed++;
if (itemsProcessed === object.length){
SaveJson(people, "users.json");
}
});

Your problem is the fact you're splicing the same array you're iterating hence why the indexes will not be correct.
You should create a copy of the array before iterating it and remove elements from the original array by retrieving the index of the element you want to remove, take a look below.
arr.slice(0).forEach(function(item) {
arr.splice(arr.indexOf(item), 1);
});
var arr = [{0:0},{i:1},{i:"test"},{i:"Something else"},{i:"Test"},5];
arr.slice(0).forEach(function(item) {
if(item != 5)
arr.splice(arr.indexOf(item), 1);
});
console.log(arr);

One thing you can consider and change is that when you are iterating the array, don't delete from it while iterating. Just mark the entry for deletion. Then, when you are done, filter out the ones that need to be removed.
For example:
people.forEach( (person,i) => {
if( /* person needs to be removed from people */ ) {
people[i] = null; // use use array from 3rd parameter if you want to pass it in
}
});
// then remove unwanted people
people = people.filter( person => person != null );
Or, if you don't want to set the person to null, then you can set a flag in the object instead to mark it for deletion.
Example:
people.forEach( (person,i) => {
if( /* person needs to be removed from people */ ) {
person.toBeDeleted = true;
}
});
people = people.filter( person => person.toBeDeleted !== true );
What is probably an even better or cleaner approach is to not use forEach but just use filter only.
Example:
people = people.filter( p_person => {
if( /* person stays */ ) {
return true;
} else {
return false;
}
});

Related

Removing an object from an array without knowing the index

I need to remove an element from an array used the splice method, but I don't know the index of the object that I wish to remove. I've tried adding an ID that mimicks the index to remove the item but this doesn't appear to be working.
RandomiseNextQuestion: function(player1qs) {
// Pick a random question
this.Questions = player1qs;
var rand = Math.floor(Math.random() * player1qs.length);
function findQ(q) {
return q.qId === rand;
}
this.CurrentQuestion = player1qs.find(findQ);
if(this.CurrentQuestion) {
// Process question
}
// Remove that question so it can't be used again
this.Questions.splice(this.CurrentQuestion.qId, 1);
}
I've also tried using the 'rand' value to remove the item but that doesn't work either.
You can map to find the index of your element
var yourArray = ['bla','bloe','blie'];
var elementPos = yourArray.indexOf('bloe');
console.log(elementPos); // this will show the index of the element you want
yourArray.splice(elementPos,1); // this wil remove the element
console.log(yourArray);
you can do it like this I suppose
getRandomQuestion: function(playerQuestions) {
// Pick a random question and return question and index of question
this.questions = playerQuestions;
var rand = Math.floor(Math.random() * playerQuestions.length);
return this.questions[rand];
}
removeQuestion: function(question, playerQuestions){
playerQuestions.splice(playerQuestions.indexOf(question), 1);
return playerQuestions; // removes question and gives back the remaining questions
}
processQuestion: function(question){
//do something with the question
}
// you would call it like this
var questionsPlayer1 = ["the meaning of life", "the answer to everything"]
var question = getRandomQuestion(questionsPlayer1);
processQuestion(question);
removeQuestion(question, questionsPlayer1);
All you can do with a single command is a filter
arr.filter((value) => value !== removeValue)
Otherwise, if you want to keep using your array (aka mutable), you will have to use something like:
const i = arr.indexOf('value')
arr.splice(i, 1)

element being removes from all arrays in aurelia class

I'm writing a simple filter "search" functionality for members. I've got 2 array properties one is members which is a list of all the members the other is filteredMembers which will be the filtered list of members:
getMembers() {
return this.userService.getMembers(this.authUser.selectedCompany).then(data => {
if (data.statusCode === 200) {
this.members = data.content.users.sort(function (a, b) {
if (a.firstName < b.firstName) return -1;
if (a.firstName > b.firstName) return 1;
return 0;
});
this.filteredMembers = this.members;
}
}).catch(err => {
console.log('error getting members:', err.response);
});
}
Since there hasn't been any filtering yet I just set the filteredMembers to equal members. So far so good.
Now I can an input search with a keyup event that filters the members depending on what I type in the search box:
<input type="search" value.bind="searchQuery" keyup.delegate="filterMembers()">
and the function:
filterMembers() {
if(!this.searchQuery) {
this.filteredMembers = this.members;
return;
}
let filteredCount = this.filteredMembers.length;
for (var i = 0; i < filteredCount; i++) {
if (this.filteredMembers[i].lastName.toLowerCase().indexOf(this.searchQuery.toLowerCase()) == -1) {
this.filteredMembers.splice(i, 1);
}
}
}
So you can see in the function I just check if what ever the user types in the search box is not found within a member's last name then I remove the member from the filteredMembers array. If the search query is empty the again I make the filteredMembers equal to members.
Here is the issue I'm running into for some reason may be a bug in Aurelia or may be human error but when I remove the element from filteredMembers it is also removing it from the members array. I've tried all sort of variations like not setting filteredMembers to equal members in that initial getMembers() function.
If filteredMembers and members are the same array instance, element removal will be reflected in both properties because both properties are references to the same array instance.
Shallow-clone the members array before assigning it to filteredMembers.
Change this:
if(!this.searchQuery) {
this.filteredMembers = this.members;
return;
}
To this:
if(!this.searchQuery) {
this.filteredMembers = this.members.slice(0);
return;
}
So I was not able to figure out what was causing the issue but I updated my filterMembers function to work around the problem.
filterMembers() {
let members: User[] = [];
if(!this.searchQuery) {
this.filteredMembers = this.members;
return;
}
let membersCount = this.members.length;
for (var i = 0; i < membersCount; i++) {
if (this.members[i].lastName.toLowerCase().indexOf(this.searchQuery.toLowerCase()) > -1) {
members.push(this.members[i]);
}
}
this.filteredMembers = members;
}
Try the opposite approach: rather than removing elements that don't match, create a new empty array and add the elements that do match. Then set filteredMembers to that new array.
Or use the native filter method (which returns a new array with the results) and write filteredMembers = members.filter(...)
https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/filter

Simplest way to remove duplicates from an array, whilst still keeping the last one

What would be the most simple way to remove duplicates from an array that have a specific value the same, whilst still keeping the most recent one pushed in?
Let's say I have this function.
bucket.bucketList =[];
bucket.addItem = function(item) {
bucket.bucketList.push(item);
}
The function pushes an object called foo this into the array on every mouse-scroll:
Some foo's also have a property ,
foo.name = "something";
The question is, what is the best way to delete a duplicate based on their property name whilst keeping the most recents one pushed in?
So the concept now is to remove all duplicates, except the last one, that have a foo.name = "example".
PS: I am using jQuery already in my project, so if jQuery has a more elegant way of doing this than vanilla JS i'd be more than happy to use it.
Edit: This is my exact code snippet:
bucket.addItem = function(item) {
bucket.bucketList.push(item);
var dict = {}, item;
for (var i = bucket.bucketList.length - 1; i >= 0 ; i--) {
item = bucket.bucketList[i];
if (item.name) {
// if already in the dict, remove this array entry
if (dict[item.name] === true) {
bucket.bucketList.splice(i, 1);
} else {
// add it to the dict
dict[item.name] = true;
}
}
}
}
Unfortunately, the above function removes all duplicates from the array that have the same name property. What I want is just to remove duplicates that have a SPECIFIC property name. e.g : item.name ="example"
Try this:
bucket.addItem = function(item) {
bucket.bucketList = bucket.bucketList.filter(function(e){
return item.name !== "example" || e.name !== "example"
});
bucket.bucketList.push(item);
}
This basically removes all items that have the same name as the current item, then pushes the current item onto the list, but only if the name is "example"

javascript not removing undefined objects from array

I've got an in page text search using JS, which is here:
$.fn.eoTextSearch = function(pat) {
var out = []
var textNodes = function(n) {
if (!window['Node']) {
window.Node = new Object();
Node.ELEMENT_NODE = 1;
Node.ATTRIBUTE_NODE = 2;
Node.TEXT_NODE = 3;
Node.CDATA_SECTION_NODE = 4;
Node.ENTITY_REFERENCE_NODE = 5;
Node.ENTITY_NODE = 6;
Node.PROCESSING_INSTRUCTION_NODE = 7;
Node.COMMENT_NODE = 8;
Node.DOCUMENT_NODE = 9;
Node.DOCUMENT_TYPE_NODE = 10;
Node.DOCUMENT_FRAGMENT_NODE = 11;
Node.NOTATION_NODE = 12;
}
if (n.nodeType == Node.TEXT_NODE) {
var t = typeof pat == 'string' ?
n.nodeValue.indexOf(pat) != -1 :
pat.test(n.nodeValue);
if (t) {
out.push(n.parentNode)
}
}
else {
$.each(n.childNodes, function(a, b) {
textNodes(b)
})
}
}
this.each(function() {
textNodes(this)
})
return out
};
And I've got the ability to hide columns and rows in a table. When I submit a search and get the highlighted results, there would be in this case, the array length of the text nodes found would be 6, but there would only be 3 highlighted on the page. When you output the array to the console you get this:
So you get the 3 tags which I was expecting, but you see that the array is actually consisting of a [span,undefined,span,undefined,undefined,span]. Thus giving me the length of 6.
<span>
<span>
<span>
[span, undefined, span, undefined, undefined, span]
I don't know why it's not stripping out all of the undefined text nodes when I do the check for them. Here's what I've got for the function.
performTextSearch = function(currentObj){
if($.trim(currentObj.val()).length > 0){
var n = $("body").eoTextSearch($.trim(currentObj.val())),
recordTitle = "matches",
arrayRecheck = new Array(),
genericElemArray = new Array()
if(n.length == 1){
recordTitle = "match"
}
//check to see if we need to do a recount on the array length.
//if it's more than 0, then they're doing a compare and we need to strip out all of the text nodes that don't have a visible parent.
if($(".rows:checked").length > 0){
$.each(n,function(i,currElem){
if($(currElem).length != 0 && typeof currElem != 'undefined'){
if($(currElem).closest("tr").is(":visible") || $(currElem).is(":visible")){
//remove the element from the array
console.log(currElem)
arrayRecheck[i] = currElem
}
}
})
}
if(arrayRecheck.length > 0){
genericElemArray.push(arrayRecheck)
console.log(arrayRecheck)
}
else{
genericElemArray.push(n)
}
genericElemArray = genericElemArray[0]
$("#recordCount").text(genericElemArray.length + " " +recordTitle)
$(".searchResults").show()
for(var i = 0; i < genericElemArray.length; ++i){
void($(genericElemArray[i]).addClass("yellowBkgd").addClass("highLighted"))
}
}
else{
$(".highLighted").css("background","none")
}
}
If you look at the code below "//check to see if we need to do a recount on the array length. ", you'll see where I'm stripping out the text nodes based off of the display and whether or not the object is defined. I'm checking the length instead of undefined because the typeof == undefined wasn't working at all for some reason. Apparently, things are still slipping by though.
Any idea why I'm still getting undefined objects in the array?
My apologies for such a big post!
Thanks in advance
I've modified your eoTextSearch() function to remove dependencies on global variables in exchange for closures:
$.fn.extend({
// helper function
// recurses into a DOM object and calls a custom function for every descendant
eachDescendant: function (callback) {
for (var i=0, j=this.length; i<j; i++) {
callback.call(this[i]);
$.fn.eachDescendant.call(this[i].childNodes, callback);
}
return this;
},
// your text search function, revised
eoTextSearch: function () {
var text = document.createTextNode("test").textContent
? "textContent" : "innerText";
// the "matches" function uses an out param instead of a return value
var matches = function (pat, outArray) {
var isRe = typeof pat.test == "function";
return function() {
if (this.nodeType != 3) return; // ...text nodes only
if (isRe && pat.test(this[text]) || this[text].indexOf(pat) > -1) {
outArray.push(this.parentNode);
}
}
};
// this is the function that will *actually* become eoTextSearch()
return function (stringOrPattern) {
var result = $(); // start with an empty jQuery object
this.eachDescendant( matches(stringOrPattern, result) );
return result;
}
}() // <- instant calling is important here
});
And then you can do something like this:
$("body").eoTextSearch("foo").filter(function () {
return $(this).closest("tr").is(":visible");
});
To remove unwanted elements from the search result. No "recounting the array length" necessary. Or you use each() directly and decide within what to do.
I cannot entirely get my head around your code, but the most likely issue is that you are removing items from the array, but not shrinking the array afterwards. Simply removing items will return you "undefined", and will not collapse the array.
I would suggest that you do one of the following:
Copy the array to a new array, but only copying those items that are not undefined
Only use those array items that are not undefined.
I hope this is something of a help.
Found the answer in another post.
Remove empty elements from an array in Javascript
Ended up using the answer's second option and it worked alright.

How to re-label Object in array?

Extending a previous question about JavaScript and jQuery, I'm trying to make an array and then look into it, but the array contains dimensions and I can't get it right..
var markers = [];
$.getJSON('GetList', "id"= + data.id,
function(data){
$.each(data.list, function(index, data) {
}
markers.push( {
category:data.category,
id:data.id,
location:{
latitude:data.location.latitude,
longitude:data.location.longitude
}
});
});
});
return markers;
}
The first thing that strikes me is that every item will now be called "Object", while default, I'm still wondering if they can be labeled instead?
Further down the line, I try to access the id to compare it to a selection by the user,
but it doesn't work.
var selection = $(div_id).val();
var arr = $.inArray(selection, markersArray.id);
if( arr >= 0) {
return search(selection, div_id);
} else {
throw("selection not found in selectionSearch()");
}
What am I doing wrong here..?
To label the objects, add a toString function, like this:
markers.push({
toString: function () { return this.something; },
category:data.category,
id:data.id,
location:{
latitude:data.location.latitude,
longitude:data.location.longitude
}
});
To search the array, you'll need your own loop.
jQuery's $.inArray function searches the array for an exact match; it cannot be used to find an object with a matching property.
You can find a match yourself, like this:
for(var i = 0; i < markers.length; i++) {
if (markers[i].id === selection)
return search(selection, div_id);
}
throw("selection not found in selectionSearch()");
The type of each item is Object, there is nothing strange about that. There is no reason to try to change that.
To filter out items from an array based on a property in the item, you use the grep method:
var arr = $.grep(markers, function(e){ return e.id === selection; });
To check if the array is empty, you don't compare the array to a number, you use the length property:
if (arr.length > 0) {

Categories

Resources