How to update an array after splice in Svelte? - javascript

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);

Related

Difference between returning a copy or manipulating original objects in array.prototype.map (In RxJS pipe)

I am working on an Angular 9, RxJS 6 app and have a question regarding the different outcomes of piping subject values and doing unit conversion in that pipe.
Please have a look at this stackblitz. There, inside the backend.service.ts file, an observable is created that does some "unit conversion" and returns everything that is emmitted to the _commodities Subject. If you look at the convertCommodityUnits function, please notice that I commented out the working example and instead have the way I solved it initially.
My question: When you use the unsubscribe buttons on the screen and subscribe again, when using the "conversion solution" that just overrides the object without making a copy, the values in the HTML are converted multiple times, so the pipe does not use the original data that the subject provides. If you use the other code, so creating a clone of the commodity object inside convertCommodityUnits, it works like expected.
Now, I don't understand why the two ways of converting the data behave so differently. I get that one manipulates the data directly, because js does Call by sharing and one returns a new object. But the object that is passed to the convertCommodityUnits function is created by the array.prototype.map function, so it should not overwrite anything, right? I expect that RxJS uses the original, last data that was emitted to the subject to pass into the pipe/map operators, but that does not seem to be the case in the example, right?
How/Why are the values converted multiple times here?
This is more or less a follow-up question on this: Angular/RxJS update piped subject manually (even if no data changed), "unit conversion in rxjs pipe", so it's the same setup.
When you're using map you got a new reference for the array. But you don't get new objects in the newly generated array (shallow copy of the array), so you're mutating the data inside the element.
In the destructuring solution, because you have only primitive types in each object in the array, you kind of generate completely brand new elements to your array each time the conversion method is called (this is important: not only a new array but also new elements in the array => you have performed a deep copy of the array). So you don't accumulate successively the values in each subscription.
It doesn't mean that the 1-level destructuring solution like you used in the provided stackblitz demo will work in all cases. I've seen this mistake being made a lot out there, particularly in redux pattern frameworks that need you to not mutate the stored data, like ngrx, ngxs etc. If you had complex objects in your array, the 1-level destructuring would've kept untouched all the embedded objects in each element of the array. I think it's easier to describe this behavior with examples:
const obj1 = {a: 1};
const array = [{b: 2, obj: obj1}];
// after every newArray assignment in the below code,
// console.log(newArray === array) prints false to the console
let newArray = [...array];
console.log(array[0] === newArray[0]); // true
newArray = array.map(item => item);
console.log(array[0] === newArray[0]); // true
newArray = array.map(item => ({...item}));
console.log(array[0] === newArray[0]); // false
console.log(array[0].obj === newArray[0].obj); // true
newArray = array.map(item => ({
...item,
obj: {...item.obj}
}));
console.log(array[0] === newArray[0]); // false
console.log(array[0].obj === newArray[0].obj); // false

How does native sort method deal with sparse arrays

When a sparse array is sorted [5, 2, 4, , 1].sort() -> [1, 2, 4, 5, empty], the empty value is always last even with callback no matter the return statement.
I'm building my own sort method as a challenge and I solved this problem by using filter method since filter skips empty values. Then iterate over filtered array and set original array's index to filtered array's values. Then I shorten the original array's length since the remaining items will be duplicates, and I can finally feed it in my sorting algorithm. Once that's done, then I set it's length back to original which adds appropriate amount of empty items at the end. Here's a snippet of code, but here's a link of the entire code
const collectUndefined = [];
// remove empty items and collect undefined
const removeSparse = this.filter(el => {
if (el === undefined) {
collectUndefined.push(el);
}
return el !== undefined;
});
const tempLength = this.length;
// reset values but will contain duplicates at the end
for (let i = 0; i < removeSparse.length; i++) {
this[i] = removeSparse[i];
}
// shorten length which will remove extra duplicates
this.length = removeSparse.length;
// sort algorithm ...
// place undefineds back into the array at the end
this.push(...collectUndefined);
// restores original length and add empty elemnts at the end
this.length = tempLength;
return this
Is the native sort implemented in this similar fashion when dealing with sparse arrays, or no.
When it comes to implementation of Array.sort you have to also ask which engine? They are not all equal in terms of how they end up getting to the final sorted version of the array. For example V8 has a pre-processing and post-processing step before it does any sorting:
V8 has one pre-processing step before it actually sorts anything and
also one post-processing step. The basic idea is to collect all
non-undefined values into a temporary list, sort this temporary list
and then write the sorted values back into the actual array or object.
This frees V8 from caring about interacting with accessors or the
prototype chain during the sorting itself.
You can find pretty detailed explanation of the entire process V8 goes through here
The actual source code for the V8 sort (using Timsort) can be found here and is now in Torque language.
The js tests for V8 Array.sort can be seen here
Bottom line however is that nothing is actually removed from the original array since it should not be. Sort is not supposed to mutate the original array. That would be super weird if you call myArray.sort() and all of a sudden it has 5 elements less from its 8 total (for example). That is not something you would find in any Array.sort specs.
Also Array.sort pays close attention to the types it sorts and orders them specifically. Example:
let arr = [4,2,5,,,,3,false,{},undefined,null,0,function(){},[]]
console.log(arr.sort())
Notice in the output above how array is first, followed by numeric values, object literal, Boolean, function, null and then undefined / empty. So if you want to really match the spec you would have to consider how different types are also sorted.

How to iterate through a linked list in javascript

Someone shared this beautifully elegant way to create a linked list from an array.
function removeKFromList(l, k) {
let list = l.reduceRight((value, next)=>({next, value}), null);
console.log(list);
}
let l = [3, 1, 2, 3, 4, 5];
let k = 3;
removeKFromList(l, k);
It's pretty easy to iterate arrays (for, filter, map, reduce, etc.) but a linked list has none of those features. I need to iterate through to remove k values from the list. I'm guessing I need to create a variable for the current node and use a while loop, but there's no documentation on this. I've seen some repl code doing it but it seems unnecessarily complicated.
How do you iterate through a linked list in javascript?
First of all, although the idea of using reduce is indeed beautiful, I must say that the result is not so good, because the resulting nodes have a field "next" where the value is and a field "value" where the next is, i.e. they are swapped. So let's fix that:
function removeKFromList(l, k) {
let list = l.reduceRight((value, next)=>({value: next, next: value}), null);
console.log(list);
}
Secondly, the name of that function is awful, it should be named "arrayToLinkedList" or something more suggestive. Also, logging the result does not make sense, we should return it instead. Moreover, the parameter k is simply unused. Fixing those things:
function arrayToLinkedList(array) {
return array.reduceRight((prev, cur) => ({ value: cur, next: prev }), null);
}
Now, let's work on how to iterate over this. What do you think? I will give some hints because giving the answer directly probably won't help you learn much:
Observe that the linked list itself is:
Either the value null, or
A plain object with two fields, one field "value" which can be anything, and one field "next" which is a linked list.
Observe that the above (recursive) definition is well-defined and covers all cases.
Now, use that to your assistence. How do you get the first value? That would be simply myList.value, right? Now how do I get the second value? By applying .next, we get the next linked list, and so on. If next is null, you know you have to stop.
Let me know if you need further assistance.
EDIT: I noticed that you're looking for the right way to make an iterating method on lists with an idea of "adding it to the prototype" or something. So, about that:
To add an instance method by a prototype, you'd need your linked lists to be a class. That would be overcomplicating, unless you really have a good reason to define a class for this (which would be creating several methods and utility things around it).
It is much better, in my opinion, to just define a function that takes one linked list as the first parameter and a callback function as the second parameter, similarly to lodash's .each:
function forEachValueInLinkedList(linkedList, callback) {
// here you loop through all values and call
// callback(value)
// for each value.
}
I'd say this would be the most "javascriptonic" way to do it.

How to find the first undefined array element in javascript, to fill it?

If entries are being added and removed in an array (not pushed/popped) what is the optimal way to scan and find the first undefined element, so that it can be set with a new value?
If the array contains holes with findIndex you can find the first one like so:
[1, 2,, 4, 5].findIndex(Object.is.bind(null, undefined))
indexOf will ignore holes. So
[1, 2,, 4, 5].indexOf(undefined)
Always returns -1.
Assuming we have no prior knowledge of operations being performed on the array, the fastest way is to simply iterate through the entire array linearly
var arr = [1, 2, , 4];
for (var i = 0; i < arr.length; i++) {
if (typeof arr[i] === 'undefined') {
arr[i] = 'foo';
break;
}
}
Or we can keep track of whats being removed using something like this
var earliestRemoved;
if (newRemoved < earliestRemoved || !earliestRemoved) {
earliestRemoved = newRemoved;
}
I guess the simplest way would be to use Array.indexOf to find the index of the first undefined element.
Today I've got the same question, so here's my take on it.
It all depends on your specific problem. My problem was this:
I have an array instances[], with objects in it;
Methods of some of these are registered as callbacks elsewhere;
Sometimes I do need to remove some instance from an array;
Sometimes I need to add new instance;
The problem is that I can't just splice an array, because it'll invalidate all my callback registrations. Well, I can just leave them in the array and forget, adding new elements at the end. But it's.... bad. It's leaking memory.
So I came with an idea of making "holes" in an array in place of removed elements, and then re-using these holes for new elements.
Thus the question "how to find undefined elements in an array?".
Someone here suggested using array.indexOf(undefined) - unfortunately that won't work... for some reason.
But, if it's okay in your case to use null or 0 instead, then it will work.
Here's an example:
var A = [1,1,1,1, , ,0,0,null,1,1];
A.indexOf(undefined); // output: -1 (doesn't work)
A.indexOf(0); // output: 6
A.indexOf(null); // output: 8
Hope this'll be useful.
With the hindsight of about 4.3 years behind me, What I'd do now is, since I'm the owner of the array, is to make an object with 2 arrays in it, the 2nd just contains the indexes that are cleared out. (prob. manage it in a class). So always have clear set with knowledge of what's undefined.
This to avoid iterating through arrays linearly.
2021
Nowadays you can safely use:
myArray.findIndex((element) => (typeof element === "undefined"));

should i use `delete array[x]; array.length-=1` instead of `array.splice(x,1)`?

I want to remove an item from an array, is array.splice(x,1) the best way to go?
Is array.splice(x,1) functionally equivalent to delete array[x]; array.length-=1 ?
I've read these threads: Javascript object with array, deleting does it actually remove the item? and Deleting array elements in JavaScript - delete vs splice
and did some tests:
<script>
var arr=[];
for(var x=0;x<100000;++x){
arr.push(x);
}
var a=new Date().getTime();
for(var x=0;x<50000;++x){
arr.splice(49999,1);
}
var b=new Date().getTime();
alert(b-a);
</script>
<script>
var arr=[];
for(var x=0;x<100000;++x){
arr.push(x);
}
var a=new Date().getTime();
for(var x=0;x<50000;++x){
delete arr[49999];
arr.length-=1;
}
var b=new Date().getTime();
alert(b-a);
</script>
The timing difference is over a magnitude of 100, making the itch to use the second solution almost irresistable.. but before using it, I would like to ask this question: are there any traps i should look out for when i use delete array[x]; array.length-=1 instead of array.splice(x,1)?
If you're just lopping off the last element in the array, you can use pop() and throw away the result or just decrement the length by 1. The delete operator isn't even required here, and splice() is more appropriate for other uses.
Specifically, section 15.4 of the ECMAScript specification says:
whenever the length property is changed, every property whose name is an array index whose value is not smaller than the new length is automatically deleted.
Both methods mentioned are outlined at MDC:
Array pop()
Array length
Either are appropriate for your situation - by all means modify length if you get better performance from it.
array.splice may perform other internal operations relevant to the splice.
Your delete array[x]; array.length-=1 just hacks around the public interface and assumes that there's nothing else to do internally.
In fact, this is the cause of the timing difference: there is plenty more to do internally in order to actually splice the array.
Use the proper interface. That's why it's there.
Using delete does not delete the element. After delete somearr[n] somearr[n] still exists but its value is undefined. There are a few ways to remove elements from arrays.
within an array for one ore more elements: Array.splice
from the end of an array (i.e. the last element): Array.pop() or maybe Array.length = Array.length-1
from the beginning of an array (i.e. the first element): Array.shift() or Array.slice(1)
To be complete, using Array.slice you could make up a function too:
function deleteElementsFromArray(arr,pos,n){
return arr.slice(0,pos).concat(arr.slice(pos+n));
}
Deleting array elements just sets them to undefined - that's why it is so fast. It does not remove the element. Decreasing the length of the array makes it even worse as the array length doesn't change at all!
In other words: the response to your question title is no and you should use splice() to achieve the intended effect. You can use the delete 'trick' to achieve greater performance only if your code handles the possibility of undefined elements. That can be useful, but it has nothing to do with 'removing an item from an array'.

Categories