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.
Related
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);
Is it possible to add to a specific part of an array, and then deleting a specific part of the array, in this case the end value using arr.splice()?
i currently do this like so:
var arr = [1,2,3,4,5,6,7,8,9];
arr.splice(0,0,"test");
which should return:
"test",1,2,3,4,5,6,7,8,9
i then do:
arr.pop();
which should return:
"test",1,2,3,4,5,6,7,8
I was wondering if it's possible to do this via the arr.splice() method or if there is any cleaner method to do the same, as potentially i'll be doing this a few times, so i would end up with something like:
arr.splice(0,0,"test");
arr.pop();
arr.splice(1,0,"test2");
arr.pop();
looking at the array.splice documentation it suggests i can only delete the element in the position i'm putting the new element into, not a different one.
In answer to my original question as confirmed in the comments. It is not possible to provide a separate index to insert and another to remove in an array using the splice method or potentially any other single statement in javascript. The best approach to try to achieve this in a single statement if i really needed it would be to create your own function, which really seems counter productive for the most part.
You can use Array.from() to set the .length and values of specific indexes of an array in a single call
var arr = [1,2,3,4,5,6,7,8,9];
arr = Array.from({length:arr.length - 1, ...arr, 0:"test"});
console.log(arr);
To achieve the pattern described at Question you can alternatively use a loop and Object.assign()
var arr = [1,2,3,4,5,6,7,8,9];
var n = 0;
var prop = "test";
while (arr.length > 1) {
Object.assign(arr, {length:arr.length -1,...arr, [n]:!n ? prop : prop+n});
++n;
console.log(arr);
}
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);
}
I am trying to create a function that mimics Array.prototype.push.
It takes a variable number of arguments and pushes them into a specific array.
I have managed to do this with the following code:
var array=[];
function append(){
for(var i=0;i<arguments.length;i++)
array.push(arguments[i]);
}
Now my question is:Can I rewrite the append function without using "for loop"?
Thanks in advance.
If you need to get arguments array, you should use Array's slice function on an arguments object, and it will convert it into a standard JavaScript array:
var array = Array.prototype.slice.call(arguments);
You could use Array.prototype.push.apply
function append(){
// make arguments an array
var args = Array.prototype.slice.call(arguments);
// return the number of elements pushed in the array
return Array.prototype.push.apply(array, args);
}
So, what's happening here with args? We use Array.prototype.slice.call with arguments, the purpose being to make arguments an array, because it is a special object. Function.prototype.call is used to call a function with a specific context (aka this), and then the arguments to call the function with (comma separated). Conveniently, it appears that slice() looks at the length property of the this context, and arguments has one too, and when not empty, has properties from 0 to length -1, which allows slice to copy arguments in a new array.
You can rewrite this without a for loop, but you have to use a loop of some sort (you're working with multiple items, it's a necessity).
If you have access to ES6 or Babel, I would use something like:
function append(...args) {
return array.concat(args);
}
Without ES6, you need to work around the fact that arguments isn't a real array. You can still apply most of the array methods to it, by accessing them through the Array prototype. Converting arguments into an array is easy enough, then you can concat the two:
function append() {
var args = Array.prototype.map.call(arguments, function (it) {
return it;
});
return array.concat(args);
}
Bear in mind that neither of these will modify the global array, but will return a new array with the combined values that can be used on its own or assigned back to array. This is somewhat easier and more robust than trying to work with push, if you're willing to array = append(...).
Actually i honestly believe that push must be redefined for the functional JS since it's returning value is the length of the resulting array and it's most of the time useless. Such as when it's needed to push a value and pass an array as a parameter to a function you cant do it inline and things get messy. Instead i would like it to return a reference to the array it's called upon or even a new array from where i can get the length information anyway. My new push proposal would be as follows;
Array.prototype.push = function(...args) {
return args.reduce(function(p,c) {
p[p.length] = c;
return p
}, this)
};
It returns a perfect reference to the array it's called upon.
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); }