JS Splice Array of Nulls Removes Last Index Regardless of Parameters - javascript

For a reason specific to this application an array of data or nulls is used to display a list of forms. The difference is based on whether data was provided by a service or manually added, where null indicates everything was manually added via
a button not a service.
So ignoring the use case of an array of nulls it turns out that [null, null, null].splice(0, 1); removes the null at index 2 instead of 0 based on entering values into the different forms displayed based on the array length, and then seeing who disappears on delete.
It can be made to work by adding something unique like { index: theIndex } to the array instead of null. So splice now works correctly removing the item at index 0 instead of 2, but now I'm curious what splice does under the covers that it can't remove an index regardless of its value compared to the other indices.
Can anyone explain what splice is doing? Based on the spec for splice I don't really see why this happens.

(This follows from the comments in the question but is too large to be a comment).
So I think you are having a conceptual misconception (^^). Look at this examples:
let a = [1, 2, 3]
a.splice(0, 1) // => 1
a // => [2, 3]
let b = [1, 2, 3]
delete b[0] // => true
b // => [<1 empty slot>, 2, 3]
The splice function modifies the array in-place. Note that, although we spliced the first element, we got as a result an array of two elements.
Look now at this example
let a = [1, 1, 1]
a.splice(0, 1)
a // => [1, 1]
let b = [1, 1, 1]
b.splice(2, 1)
b // => [1, 1]
We are deleting the first element from a and the last from b, but of course there's no way of telling so just looking at the result.
In the case with the nulls, the same thing is happening. Some library (Angular) is trying to figure out which element you deleted, but there's no way of knowing. This is because null === null.
Now if you use an array of empty objects, for example, there would be a way of knowing. Since {} !== {}---because each time you cast a {} you are creating a unique object---, then you could know which element is missing in an array of empty objects. The case is similar with the array [1, 2, 3].
let a = [{}, {}, {}] // lets imagine that each object has a unique id
// and we have [{}#1, {}#2, {}#3]
let [obj1, obj2, obj3] = a
obj1 === obj2 // => false, because they have different ids.
a.splice(0, 1)
a // => [{}#2, {}#3]
a.includes(obj1) // => false, because {}#1 is no longer in a
So an alternative to using an array of nulls, would be to use an array of empty objects. I think that is why the code works for you when you use objects like { index: theIndex }. But of course all depends on how smart Angular is. I bet there is a more native way of deleting an element, as #KaiserKatze points out, "it's always a bad idea to directly remove or add elements in the array if it maps to your model."
You have to understand that when you are splicing the array you're only doing that--removing an element from an array. You're not removing the "form element" when splicing the array. Instead, some foreign code is reading the array and trying to figure out --under the hood-- what you intended to do.

Related

Compare an array of arrays to check if the arrays are in order in Javascript

I have an array of arrays like this, const array = [[1,5],[7,9],[10,14]]; I need to check to see if the arrays are in order.
Basically, for the provided array, I need to compare the first array's second value, (5), with the second array's first value, (7), which I would then use to invalidate a form.
If the arrays or in order, const array = [[1,5],[6,9],[10,14]]; it returns false, it passes validation, if they aren't, validation fails and it returns true.
So far, I've done something like this.
const arrayIdxZeroValues = [];
const arrayIdxOneValues = [];
array.forEach((arr,idx) => {
arrayIdxZeroValues.push(arr[0])
arrayIdxOneValues.push(arr[1])
})
which separates the values fine. I then thought about using .shift() to remove the first value of arrayIdxZeroValues since it will never need to be compared, but I feel this is hacky and I'm not sure it's the right approach from here. Maybe .reduce() to compare the output arrays, but I haven't been able to get it to work right.
Any guidance would be much appreciated.
Thanks
Create an array that starts from the 2nd sub-array using Array.slice(). Use Array.some() and check if the 2nd item of the current sub-array is equal or less the 2nd item of the sub-array at the current index i of the original array. In this way you compare the 2nd with the 1st, the 3rd with the 2nd, and so on.
false - means the validation passed
true - means validation failed
const isFailed = arr => arr.slice(1)
.some(([n], i) => n !== arr[i][1] + 1)
console.log(isFailed([[1,5],[6,9],[10,14]])) // false
console.log(isFailed([[1,5],[7,9],[10,14]])) // true
You could check the actual value agains the last value of the previous array plus one and omit the check for the first inner array.
const
inOrder = array => array.every(([v], i, a) => !i || a[i - 1][1] + 1 === v);
console.log(inOrder([[1, 5], [7, 9], [10, 14]]));
console.log(inOrder([[1, 5], [6, 9], [10, 14]]));

Understanding indexOf and lastIndexOf

I'am doing some JavaScript exercises and I stumbled upon this one "Write a JavaScript program to filter out the non-unique values in an array."
I tried and found a solution, which worked but it was to cumbersome. A better answer, according to the site, is the following:
const filter_Non_Unique = arr =>
arr.filter(l => arr.indexOf(l) === arr.lastIndexOf(l));
console.log(filter_Non_Unique([1,2,3,4,4,5,6,6])) // 1,2,3,5
Now I recked my head trying to understand why this solution works but I still don't get it.
Can somebody explain to me?
Thanks in advance.
If the element only occurs once in the array, the first index will be the same as the last index, the index will not change for both calls.
eg:
const arr = [1,2,3,4,4,5,6,6]
console.log(arr.indexOf(5))
console.log(arr.lastIndexOf(5))
Since both of of these functions return the same index, filter will keep the element in.
On the other hand if there are multiple values, those values will have different indexes, so the filter will return false, and remove it from the array:
const arr = [1,2,3,4,4,5,6,6]
console.log(arr.indexOf(4))
console.log(arr.lastIndexOf(4))
I've answered a question similar to the one you solved here, you could try that logic too.
Beside of the indexOf/lastIndexOf approach which needs a lot of iterating the array, you could take a two loop approach.
By getting an array of single items by using a hash table and three states, like
undefined, the standard value of not declared properties of an object,
true for the first found value,
false for all values who are repeated in the array.
Then filter by the value of the hash table.
const
filterNonUnique = array => {
var hash = {};
for (let v of array) hash[v] = hash[v] === undefined;
return array.filter(v => hash[v]);
}
console.log(filterNonUnique([1, 2, 3, 4, 4, 5, 6, 6, 7, 7, 7, 7]))

Understanding Finding Duplicate Numbers And Returning The Lowest Index of The Second Occurence

Regarding the question that is to find the first duplicate number of numbers in an array for which the second occurrence, has the minimal index.
If I understand so far, the variable firstDuplicate is a function object that uses arrow notation to abbreviate 'var firstDuplicate = function (a){}', etc. Here's where my questions begin.
1) Creating a new set object automatically populates using the array the function was passed? How does the set method know to take the array passed to the function and make a set of it?
2) Now I understand in the for loop that each item in the array is being traversed and the current index is e, but here is where I begin to lose conceptually what is going on. Where in the following:
if (r.has(e))
where is the comparison happening exactly, that is the checking to see what the duplicative numbers are in this array, and the comparison that determines what the lowest index of the second occurrences of the duplicate are?
const test1 = [0, 3, 4, 10, 2, 4, 2, 3]
firstDuplicate = a => {
r = new Set()
for (e of a)
if (r.has(e))
return e
else
r.add(e)
return -1
}
console.log(firstDuplicate(test1));
Creating a new set object automatically populates using the array the function was passed? How does the set method know to take the array passed to the function and make a set of it?
Nothing is happening automatically here. The Set is populated in the else condition when you say r.add(e)
Now I understand in the for loop that each item in the array is being traversed and the current index is e...
e is the current element and not the current index. You're using a for..of statement and not a for..in statement. Difference between the two using a simple snippet
const a = [10, 5, 12];
console.log("for..in statement");
for (const i in a) {
// Here i is the index
console.log(i)
};
console.log("for..of statement");
for (const i of a) {
// Here i is the element
console.log(i)
};
...where is the comparison happening exactly, that is the checking to see what the duplicative numbers are in this array, and the comparison that determines what the lowest index of the second occurrences of the duplicate are?
The comparison happens at r.has(e). So the code checks if e was already encountered (hence also added in the Set) and returns e. This works because if e was already encountered once, then if it's encountered again before any other duplicate, it automatically means e is at the minimal index.
Here is a commented version of your code, to make it more clear
const test1 = [2, 3, 6, 10, 2, 3, 7, 9, 1]
firstDuplicate = a => {
// Create an empty Set
r = new Set()
// Iterate over every element of a
for (e of a) {
// Check if e was already encountered
if (r.has(e)) {
// If e was already encountered, this is the first time a duplicate
// has been found. This is definitely a duplicate at the minimal index,
// as a for loop iterates elements in order of their indices
return e
} else { // e is encountered the firt time
// Populate the Set with e, to say that it has been encountered atleast
// once
r.add(e)
}
}
// The if condition above never passed. That means array a has all
// unique elements. Return -1, assuming the array a will never contain
// -1 as one of it's elements.
return -1
}
console.log(firstDuplicate(test1));

What delete really means in NodeJS in arrays?

Reading the answers to this question I understand that delete sets undefined value for the element.
So, if we have an array: a = [1, 2, 3] (at least) on the client side using delete a[1] the array would become [1, undefined x 1, 3] that is true, on the client side.
What happens in NodeJS?
But testing same operation on NodeJS (server side) I get the following:
$ node
> a = [1, 2, 3]
[ 1, 2, 3 ]
> delete a[1]
true
> a
[ 1, , 3 ]
> a[1] = undefined
undefined
> a
[ 1, undefined, 3 ]
>
So, a[1] = undefined sets undefined value. But what does the space mean between the two commas ([1, , 3])? delete put it! Why?
I guess that the space means undefined, but why it doesn't appear as undefined only I set it as undefined?
Why is this happening?
When you use delete on an array entry, it removes that entry from the array. The array length is not changed, but that slot in the array is gone. If you try to look at the value of that slot it will show as undefined though deleting it is not quite the same as setting it to undefined. You can read about deleting an array entry here (scroll down to the subtitle "Deleting Array Elements").
This reference also explains the difference between using delete on an array entry vs. setting it to undefined. The difference is subtle and only makes a difference in a few situations. Here's an example from that article:
// using delete
var trees = ["redwood","bay","cedar","oak","maple"];
delete trees[3];
if (3 in trees) {
// this does not get executed
}
// setting to undefined
var trees = ["redwood","bay","cedar","oak","maple"];
trees[3]=undefined;
if (3 in trees) {
// this gets executed
}
So, this console output:
[ 1, , 3 ]
is showing you that the second array entry is missing (it's gone completely).
See this article http://perfectionkills.com/understanding-delete/ for a full run-down of how the delete operator works in other circumstances.
Array indexes at language level work like any other properties.
When you access a property that's not there, the proto chain is walked until a result is found or undefined is returned if nothing is found. But you can also use undefined as a normal value.
There can be major differences, eg:
Array.prototype[1] = 15;
var a = [1, 2, 3];
delete a[1];
//protip: the above can be written as `var a = [1,,3]`
var b = [1, undefined, 3];
//Logs 15 and undefined
console.log(a[1], b[1]);
In a, because the property 1 is not there, the prototype is checked and the value 15 is found.
In b, because the property 1 is there, the value of the property (undefined) is just directly returned.
Also the console formatted output for an array is usually different between environments but that doesn't really change what is actually going on.

Is it possible to chain array.push() in Javascript?

I have 3 separate arrays and I'm looking to load them all into to a single array. Am I able to use .push() several arrays into one? Is something like this possible?
var activeMembers=[]; // Active Users
var noactiveMsg=[]; // Non-Active Users with a Pending Message
var noactiveNomsg=[]; // Non-Active Users without a Pending Message
var chatCenterMembers=[]; // Final Array of Chat Center Members
chatCenterMembers.push(activeMembers).push(noactiveMsg).push(noactiveNomsg);
Is there a way to chain .push()?
You're looking for the (vanilla) JavaScript method Array.concat().
Returns a new array comprised of this array joined with other array(s) and/or value(s).
Example, following your code:
chatCenterMembers = chatCenterMembers
.concat(activeMembers)
.concat(noactiveMsg)
.concat(noactiveNomsg);
chatCenterMembers.push(activeMembers,noactiveMsg,noactiveNomsg)
This question is quite confusing. First of all, the question seems to be asking for a way to combine multiple arrays into one single array containing the elements of all the arrays. However, the accepted answer provides a solution for creating an array of arrays. Since the text in the question suggests merging the elements of multiple arrays into one array while the code example uses push with arrays as arguments, it's quite ambigious what the OP wants.
Furthermore, several answers have suggested using concat. While that fulfills the requirement of returning the resulting array after adding the provided element, and is fine for small sets of data and/or where performance and memory is not an issue, it's inefficient if dealing with large arrays, since each concat operation will allocate a new array, copy all the elements of the old array into it, then copy all the elements of the provided array into it, and dereference the old array (as opposed to simply adding elements to the same array object).
Consider calling concat N times, adding C elements each time:
allocate new array, copy C elements
allocate new array, copy 2 * C elements
allocate new array, copy 3 * C elements
...
A different approach would be to create your own method, either as a separate function or adding it to the Array prototype:
Array.prototype.append = function(e) {
this.push(e);
return this;
}
With this, you could do
[1, 2, 3].append(4).append(5).append(6)
without allocating more than one array object in total.
It could perhaps also be mentioned that with ES2015, the spread operator can be used to add all the elements of an array to another array using push:
const arr1 = [1, 2, 3]
const arr2 = [4, 5, 6]
arr1.push(...arr2); // arr1 is now [1, 2, 3, 4, 5, 6]
This will however not fulfill the requirement of returning the resulting array for chaining, but the append method above could be used to merge multiple arrays like this:
chatCenterMembers = activeMembers.append(...noactiveMsg).append(...noactiveNomsg);
You can do it instead with .concat().
var chatCenterMembers=[];
chatCenterMembers = chatCenterMembers.concat(activeMembers, noactiveMsg, noactiveNomsg);
Since on one else has posted it:
var chatCenterMembers = activeMembers.concat(noactiveMsg, noactiveNomsg);
push AND unshift chaining
I actually came here looking for both but didn't see any good answer so far for unshift so I'll note that here as well.
push chaining is straight forward
const list = ['hi', 'there']
.concat(['buddy'])
// list is now ['hi', 'there', 'buddy']
but unshift chaining is weird
// need to use concat + map to do unshift chaining
const list = ['hi', 'there']
.concat(['buddy'])
.map((e, i, a) => i == 0 ? a[a.length - 1] : a[i-1])
// list is now ['buddy', 'hi', 'there']
As you can see using map there is a 3rd param given for the array you are using so this gives you power to do all sorts of odd things.

Categories

Resources