Chaining Methods error in Javascript - javascript

I would like to know the reason why this simple piece of code fails:
var arr = [1, 2, 3];
arr.push(arr[0]).shift();
console.log(arr);
it returns in firebug console "TypeError: arr.push(...).shift is not a function"
I think it happens because I invoke the shift() method not on an array but on the pushed element.
Is there a more elegant way to obtain the same result that,
var arr = [1, 2, 3];
arr.push(arr[0]);
arr.shift();
console.log(arr);
produce ?
Thanks in advance!

From the MDN:
The push() method adds one or more elements to the end of an array and
returns the new length of the array.
arr.push(arr[0]) doesn't return the array but a number which, obviously, has no shift function.
To my knowledge, there's no simple expression pushing an element to an array and returning that array. But in your case you may simply reverse the operations and do
arr.push(arr.shift());

I think it happens because I invoke the shift() method not on an array but on the pushed element.
Almost. push returns the new length of the array. A number obviously doesn't have a shift() method.
Your method of putting it on two lines is the simplest way.

Essentially this question is saying, can I somehow "elegantly" express the notion of moving the first item of an array to the end. Luckily, JS is a Turing-complete language, which allows us to define functions, so the "elegant" answer is just
rotate(arr)
Now it merely remains to define rotate. To rotate is to drop the first element in the result of adding the head element to the end:
function rotate(arr) { return drop(add(arr, head(arr))); }
Now drop is
function drop(arr) { return arr.shift(), arr; }
and head of course is
function head(arr) { return arr[0]; }
and add is
function add(arr, elt) { return arr.push(elt), arr; }
Another approach
I could also write a function to move n elements from position i to position j, using splice, as follows:
function move(arr, n, i, j) {
arr.splice.apply(arr, [j-n+1, 0].concat(arr.splice(i, n)));
return arr;
}
Then to rotate is to move one element at the beginning to the end:
function rotate(arr) { return move(arr, 1, 0, 999); }

Related

How to update an array after splice in Svelte?

I'm learning Svelte, and read in the documentation that arrays need to be reassigned in order for a component or page to update it. For that they devised a more idiomatic solution. Instead of writing:
messages.push('hello');
messages = messages;
you can write instead:
messages = [...messages, 'hello'];
Alright, makes sense. But then the documentation says:
You can use similar patterns to replace pop, shift, unshift and splice.
But how? I cannot see how you can remove items from an array. More to the point, how could I write the following more idiomatically?
messages.splice(messages.indexOf('hello'), 1);
messages = messages;
You could e.g. use the filter array method to create a new array without the element 'hello':
messages = messages.filter(m => m !== 'hello');
As mentioned, Svelte's reactivity is triggered by assignments. The current Svelte tutorial uses JavaScript's (ES6) spread syntax (three dots) to add the next-higher number to an array, providing a more idiomatic solution than a redundant assignment using push:
function pushNumber() {
numbers = [...numbers, lastnumber]; // 1, 2, 3, 4, 5
}
You could use spread syntax to replace pop, shift, unshift and splicethough it might increase the time and complexity of the operation in some cases:
function unshiftNumber() {
numbers = [firstnumber, ...numbers]; // 0, 1, 2, 3, 4
}
function popNumber() {
numbers = [...numbers.slice(0,numbers.length - 1)]; // 1, 2, 3
}
function shiftNumber() {
numbers = [...numbers.slice(1,numbers.length)]; // 2, 3, 4
}
function spliceNumber() {
numbers = [firstnumber, ...numbers.slice(0,numbers.length-1)];// 0, 1, 2, 3
}
Spread is just one way to do it, though. The purpose behind not using pop/push etc is to encourage immutability. So any removal can just be a filter, for example.
There are several things to consider here.
Given this code:
messages.splice(messages.indexOf('hello'), 1);
messages = messages;
What's happening here is:
Looking for the first occurrence of the string "hello" in the array
Removing such element from the array, based on the index found.
The assumption here is that "hello" needs to exists, otherwise the could would remove the last item from the array (since indexOf returns -1).
The original array is therefore mutate: depends by the context, that sometimes can be preferable instead of copying the whole array into a new one; otherwise it's generally a better practice avoid such mutation.
So. If you want to have this behavior exactly, probably this is the best code you can have. For example, takes the filter example:
messages = messages.filter(message => message !== "hello")
What's happening here is:
Filter out any element equals to "hello"
Returns a new array without such element
So it's quite different from the original code: first of all, it always loop the whole array. If you have thousands of element, even if you have only one "hello" at the second index, it would always iterate all of them. Maybe it's what you want, maybe not. If the element is unique, such as an id, maybe you want to stop once you find it.
Second, it returns a new array. Again, that usually a better practice than mutate the array, but in some context it's preferable mutate it instead of create a new one.
So, if you want to mutate the original array, it's probably better to stick to your original code.
If, instead, you don't care (such as the example of push), I believe that in the intention of svelte's developers, your code would be roughly translate to:
let i = messages.indexOf("hello");
messages = [...messages.slice(0, i), ...messages.slice(i + 1)];
(Still assuming there is a "hello" message and you're interested only in the first occurrence).
It's unfortunate that JS doesn't have a better syntax to handles slices.
In case you're wandering, filter can also be used to remove elements using a given index:
let elements = ['a','b', 'c'];
let idx = 1;
elements = elements.filter( (e,i) => i !== idx );
// => ['a', 'c']
You can perform the usual push and pop or `splice on your Array
But because Svelte's reactivity is triggered by assignments, using array methods like push and splice won't automatically cause updates.
According to All about Immutable Arrays and Objects in JavaScript you can do it this way...
let messages = ['something', 'another', 'hello', 'word', 'another', 'again'];
const indexOfHello = messages.indexOf('hello');
messages = [...messages.slice(0, indexOfHello), ...messages.slice(indexOfHello + 1)];
Note the difference between splice and slice
The splice() method adds/removes items to/from an array, and returns
the removed item(s). Note: This method changes the original array.
Syntax: array.splice(start, deleteCount, itemstoAdd, addThisToo);
But
The slice() method returns the selected elements in an array, as a new array object. The slice() method selects the elements starting at the given start argument, and ends at, but does not include, the given end argument.
Note: The original array will not be changed.
In order words
It return a shallow copy of a portion of an array into a new array
object selected from begin to end (end not included). The original
array will not be modified.
Syntax: array.slice(start, end);
You can try this: https://svelte.dev/repl/0dedb37665014ba99e05415a6107bc21?version=3.53.1
use a library called svelox. It allows you to use the Array native api(push/splice...etc.) without reassignment statements.
Spread the spliced array to reassign it to itself ;)
messages = [...messages.splice(messages.indexOf('hello'), 1)];
The goal is to make Svelte detecting that array messages (a property of your component or a variable in the Svelte store) has changed. This is why the array messages must be declared with let or var keyword, not const. This way you're allowed to reassign it. And the reassign operation itself is sufficient to make Svelte detecting that the array has changed.
Perhaps even, simply by doing so works too:
messages = messages.splice(messages.indexOf('hello'), 1);

How to prevent a recursive function from re-initializing an accumulating variable?

This function is written in JavaScript but I think that the concept can be implemented with some other programming languages.
function uniteUnique(arr) {
let seenBefore = []; //the accumulating array
for (let item of arguments) {
if (typeof (item) == "object") {
uniteUnique(...item);
}
else if (!seenBefore.includes(item)) {
seenBefore.push(item);
}
}
return seenBefore;
}
In short, the function iterates over arrays it receives as arguments, which may or may not contain other arrays themselves. the deepest level of any of those arrays contains int values. The function returns an array that contains all those ints (i.e those that appeared in nested arrays), but it returns each int only once, even if it appeared more than one time.
My problem lies in the fact that every time the recursion returns to a higher level, it initializes again the array that contains the saved ints, namely the array that the function needs to return (seenBefore), and therefore ruins the whole process. On one hand, I must initialize the array when the function starts, but on the other hand, it is initialized more than once and loses its previously stored values.
for example, if I would run the function
uniteUnique([1, 3, [6, 3], 2], [5, 2, 1, 4], [2, 1]);
the output should be
[1,3,6,2,5,4]
because the function has to return the numbers it stumble upon only once by the order of processing. The function actually returns an empty array because it is initialized again right before the function returns from the top-most level of the recursion.
How can I bypass this problem?
(P.S: I know this can be "solved" by pulling the accumulating array out of the function to a different scope, but that causes other problems, such as the need to reinitialize the accumulating array each time before I run the function if I run it more than once.)
You've misidentified your problem. Each call to uniteUnique() has a separate value for the local variable seenBefore -- nothing is being "initialized again" during recursive calls.
Your real problem is that the line:
uniteUnique(...item);
discards the result of that function call, so the contents of any nested arrays are ignored. You need to assign the return value of this function somewhere and use it.
You may also want to change the condition for this function call to:
if (Array.isArray(item)) {
as the current condition typeof item == "object" will include objects which cannot be iterated over.
Another possibility would be to define seenBefore as the empty array as a default second parameter, which is recursively passed to every call of the function:
function uniteUnique(arr, seenBefore = []) {
for (const item of arr) {
if (typeof (item) == "object") {
uniteUnique(item, seenBefore);
}
else if (!seenBefore.includes(item)) {
seenBefore.push(item);
}
}
return seenBefore;
}
uniteUnique(someArr);
Note that this accepts a single argument as an array, rather than multiple arguments.
You can utilize a nested function, so that you don't need to reinitialize the accumulating array each time:
function uniteUnique(arr) {
function iterate(seenBefore, arr)
{
for (let item of arr) {
if (Array.isArray(item)) {
iterate(seenBefore, item);
}
else if (!seenBefore.includes(item)) {
seenBefore.push(item);
}
}
}
let result = []; // the accumulating array
iterate(result, arr);
return result ;
}
You don't actually need to use arguments and spread operator here, because your function expects an array and you can simply pass an array.
You may also want to use Set for seenBefore, because Array.prototype.includes scans through an array, which is ineffective.
Just in case: if you can use the last ES2019 features and prefer simplicity, this can also be achieved with a one-liner:
const uniquesArray = [...new Set(nestedArray.flat(Infinity))];

How to manipulate a JavaScript array passed to a function without changing the original argument array?

I would like to work with an array passed to a function while leaving the original array untouched. Here is a simple example of the problem.
function whyDoesArrChange(arr) {
var newArr = arr;
newArr.push(4);
return arr;
}
console.log(whyDoesArrChange([1,2,3]));
// OUT: [1,2,3,4]
I would like only newArr to be changed, but the arr (from the arguments) returns the pushed value as well (returns [1,2,3,4]). How can I avoid this?
When passing an array to a function, the value of that array is a reference, you have to clone it to break the reference.
There are multiple ways to achieve this:
1 - Using .slice();
var newArr = arr.slice();
2 - Using .concat
var newArr = arr.concat([]);
3 - Using JSON.parse & JSON.stringify
var newArr = JSON.parse(JSON.stringify(arr));
You can check more ways and see how they perform in this jsperf I found.
While Marcos' answer is correct, no doubt. There are more pure Array functions that can be used (A pure function is a function that doesn't alter data outside of its' scope).
Usually, if you'd like to do multiple actions on the array, I would go with Marcos' answer and then do those changes as usual. But when that's not the case, the following information may be useful:
Adding: arr.concat([1]);
Subarray (i to j): arr.slice(i, j + 1);
Removing (i to j): arr.slice(0, i).concat(arr.slice(j + 1));
Also, filter and map are pure function that will not alter the array.
In JavaScript, when you use (=) to assign a value from a variable to another one, you're just passing the entire variable, so each time one or another changes, the other will change too.
According to your question, the best way that works for me is using the native .slice() JavaScript method to the arrays object. For your code:
function whyDoesArrChange(arr) {
var newArr = arr.slice();
newArr.push(4);
return arr;
}
Because reference types (arrays and objects) can get modified inside functions when passed as arguments, while primitive types (numbers, strings, booleans etc.) don't.
You can make a shallow copy the array with .slice() and work on that array, or return a new array with the .concat method.
function whyDoesArrChange(arr) {
return arr.concat(4);
}

Overwrite the this value in array.prototype

I've found that the native js sort function screws up from time to time so I wanted to implement my own. Lets say i have the following :
Array.prototype.customSort = function(sortFunction, updated) {...}
var array = [5, 2, 3, 6]
array.customSort(function(a,b) {return a - b})
console.log(array)
Array should be [2, 3, 5, 6]
Updated is the array that has been sorted.
No matter what I return in customSort, the order of array is still in the original order. How do I overwrite the 'this' value / get it to point to the array with the correct order?
If you consider the actual code you gave above, you have to make sure that your customSort function updates this.
One case is that customSort only uses this as "read-only" input, that is - only puts the sorted array in updated, rather than changing this.
In such case, considering the code above (which you might have performed tests with), no updated parameter is sent to the function, to receive the sorted values.
Another case is that customSort returns the sorted array, in which case you have to collect it:
array = array.customSort(function(a,b) {return a - b});
console.log(array);
I just ended up iterating over the updated array and replacing each value in this with the value in updated. In code, that looks like...
function customSort(cb) {
...//updated is the sorted array that has been built
var that = this;
_.each(updated, function (ele, index) {
that[index] = ele;
})
}
I wanted the function to operate in the exact same way the native array.sort function does - it overwrites the array provided instead of returning a new, sorted array.
I find it odd that this works... you cannot overwrite the entire this value in one clean sweep but you can in steps. I couldn't do this for in the customSort function :
this = updated;

Javascript function and Array

I am trying to cut some values from an array, for example i want to cut 1 from it's position and return array as (3,5,4,6,2,7), but there is an error saying splice is not a function.
function findMin() {
var arr = arguments;
arr.splice(1,1);
return arr[0];
}
document.getElementById("demo").innerHTML = findMin(1,3,5,4,6,2,7);
arguments isn't an array, it's array-like. You can get a copy of its contents as an array like this:
var arr = Array.prototype.slice.call(arguments, 0);
...and then you can use array operations on it.
Two other issues with the code:
Indexes you use with splice start at 0, not 1
You're returning the (new) first entry, not the array
But note that for your specific task, you don't need to go through all those operations, you can just use slice directly; Denys Séguret shows you that.
Here's an example just using slice to copy arguments, without combining it with the other code, with the other issues above fixed as well:
function findMin() {
var arr = Array.prototype.slice.call(arguments, 0);
arr.splice(0, 1);
return arr;
}
document.getElementById("demo").innerHTML = findMin(1, 3, 5, 4, 6, 2, 7);
<div id="demo"></div>
The problem is arguments isn't an array, but an array-like object.
The usual solution is to use slice to get an array from an array-like object. And as the normal purpose of slice is to get a slice from an array, here's a solution giving you a copy of arguments without the first element:
function findMin() {
return Array.prototype.slice.call(arguments, 1);
}
Notes
Manipulating arguments, even using slice has a performance impact on the whole function in which you do it. Avoid it when you can.
Thanks to ES6 (the next iteration of the standard behind JavaScript), we'll be able to use Array.from instead of slice in the near future.

Categories