How does native sort method deal with sparse arrays - javascript

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.

Related

Why doesn't my MoveZeroes code modify the input array?

I am trying to solve the following leetcode problem in JS: MoveZeroes problem
The challenge is the following:
Given an array nums, write a function to move all 0's to the end of it while maintaining the relative order of the non-zero elements.
1.You must do this in-place without making a copy of the array.
2.Minimize the total number of operations.
Example:
Input: [0,1,0,3,12]
Output: [1,3,12,0,0]
In my code, I get the desired output on the console right before the end of the function, but it is being rejected and I would like to understand why.
This is my code:
/**
* #param {number[]} nums
* #return {void} Do not return anything, modify nums in-place instead.
*/
var moveZeroes = function(nums) {
let i = 0
let length = nums.length
while (i < length){
if(nums[i] === 0) {
nums = nums.slice(0, i).concat(nums.slice(i+1),0)
length--
continue;
}
length--
i++
}
console.log(nums) //logs the desired result
};
I have seen similar questions to this:
Using splice
This one does not do it in-place
Not doing it in-place either
So, I understand that this might be a duplicate but I would appreciate direct feedback on my code. Thanks!
your code is violating the first rule:
You must do this in-place without making a copy of the array
slice() and concat() both returns a new array which means copying the original array;
you may use shift(), unshift(), sort() ... or swap the element values, that mutate the original array
nums = nums.slice(0, i).concat(nums.slice(i+1),0)
This line assigns the nums variable to a new array with the desired content. But the original array remains unchanged.
As others have mentioned, your use of Array.slice(...) and Array.concat(...) violate the constraints of the question. Quoting some particular sections from the Mozilla Developer Network pages on each of those functions:
Array.slice(...): "The slice() method returns a shallow copy of a portion of an array into a new array object..."
Array.concat(...): "The concat() method is used to merge two or more arrays. This method does not change the existing arrays, but instead returns a new array. [emphasis added]"
I would highly recommend the usage of Array.splice(start[, deleteCount[, ... itemsToInsert]]) for this, which can be used not only to delete subsections of an array, but also to insert new elements into an array, all while modifying the array in-place. This versatility allows Array.splice(...) to function as an in-place replacement for both Array.concat() and Array.slice().

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

Is an array in JS actually filled with empty spaces or when printed is it filled with zeros?

I have an array:
mydata =[];
i am storing values in the array by using a numeric key that can be somewhat big
mydata[13525] = 1;
However if i only store one item as the above, when i print the array in the console it prints 13524 commas before the object, nevertheless the debugger tells me it has length 13526 and there is only one element in the array.
I am confused as i understood that JS arrays do not required every position to be filled and certainly this might consume a lot of memory on bigger numbers. Can you explain please?
The .length property of an array represents the highest index plus one in the array that has been assigned a value.
When you do not assign intermediate values (and thus have a sparse array), the intervening values that you have not assigned are undefined which is essentially the "nothing here" value in Javascript. The console chooses to display arrays by trying to show you every value between 0 and the end of the array which is simply not a very good way to display a sparse array that is mostly empty. That's more an artifact of a design choice in the console than anything else. One could design a different way to display contents of an array that would handle sparse arrays differently.
Arrays are most efficient if you use consecutive indexes started from 0. That's what they are mostly designed for and what many implementations are optimized for since a runtime can do some optimized things if it knows there is a sequential set of values.
If you know you're going to mostly not be using sequences of numeric indexes starting from 0 and as such the .length property is of little use to you, then perhaps a plain object with arbitrary properties is a better choice.
var mydata = {};
mydata[13525] = 1;
console.log(mydata[13525]); // 1
console.log(mydata.length); // undefined - no .length property on an object
console.log(myData); // {1: 13525}
If you only want to print any non-null value in the array, instead of printing the whole array I'd use a for loop that only prints non-null values. It'd be something like this...
for (i = 0; i < mydata.length; i++) {
if(mydata[i]!= null){
console.log(mydata[i]);
}
}
javascript array is bit different from others,
var foo = [1, 2, 3, 4, 5, 6];
foo.length = 3;
foo; // [1, 2, 3]
foo.length = 6;
foo.push(4);
foo; // [1, 2, 3, undefined, undefined, undefined, 4]
While the getter of the length property simply returns the number of elements that are contained in the array, in setter, a smaller value truncates the array, larger value creates a sparse array. Guess what the setter mydata[13525] = 1; would do.
src: Javascript Garden
Edit:
to print/use only the values present, you can do
mydata.forEach(function(v){console.log(v);});

Accessing Array beyond its size in javascript

I read in a particular book that an array in JavaScript can hold 4,294,967,295 items and would throw exception if the number reaches beyond that.
I tried out the functionality using the following code:
var a = ["a","b","c"];
a[4294967300] = "d";
console.log(a[4294967300]);
It shows the output "d" and no exception or error. Am I missing something here? Can someone put some light on the topic and share some knowledge regarding max array items in JavaScript and various scenarios related to it?
An array doesn't have to hold all the items from 0 to N to contain one with index N.
That's because arrays in JavaScript engines can switch to a dictionnary mode when the holes are too big, those arrays are called sparse arrays (vs dense arrays).
It's important to know this distinction because the implementation is leaking on one point : performance. You should read this on this topic : http://www.html5rocks.com/en/tutorials/speed/v8/
But regarding indexes starting at 2³², sebcap26 is right, there's a distinction due to the fact the index is handled as a string. This distinction is important and can be verified by logging a.length : you'll see the length isn't impacted by such an element. There's no exception or error per se but it makes it impossible to use normal array operations like iterating up to the length or using array functions like map or filter (the elements with index greater than the numeric index limit are ignored by those functions).
If I understand well the ECMAScript specifications, an index which is not in [0 .. 2^32-1] is converted into a String and used as an Object key, not as an Array index.
A property name P (in the form of a String value) is an array index if and only if ToString(ToUint32(P)) is equal to P and ToUint32(P) is not equal to 2^32−1.
Try running this code: fiddle : http://jsfiddle.net/vXtfE/
var a = ["a","b","c"];
a[4294967300] = "d";
console.log(a.length);
console.log(a);
console.log(a[4294967300]);
You will see this output:
3
["a", "b", "c", 4294967300: "d"]
d
The initial items get stored as array elements, but for large index, the storage changes to hash based sparse array. Hence, it is a mix of both in your case.
Good explanation of this :
Why is array.push sometimes faster than array[n] = value?

What use cases are there in JavaScript for Sparse Arrays?

What possible programming use could you have where a sparse array would be better than an (regular) object?
By sparse array I mean one where:
arr = []; //Initialize
arr[0] = 'W';
arr[1] = 'T';
arr[3] = 'F';
console.log(arr[0] !== undefined) //true
console.log(arr[1] !== undefined) //true
console.log(arr[2] === undefined) //true
console.log(arr[3] !== undefined) //true
Or more formally:
An object, O, is said to be sparse if the following algorithm returns true:
1. Let len be the result of calling the [[Get]] internal method of O with argument
"length".
2. For each integer i in the range 0≤i<ToUint32(len)
a. Let elem be the result of calling the [[GetOwnProperty]] internal method of O
with argument ToString(i).
b. If elem is undefined, return true.
3. Return false.
ECMA 262 5.1 - 15.4 Array Objects
Moreover, the ECMA 262 5.1 Standard further defines length specifically as:
The length property of this Array object is a data property whose value is always numerically greater than the name of every deletable property whose name is an array index.
So the example above, arr.length === 4 despite there only being three elements defined.
In fact, according to the standard, any Number greater than 3 is a valid length for arr, including Math.PI.
Consequently, does this mean that no one should use:
for(var i=0; i<arr.length; i++)
//Cannot trust arr[i] exists
and instead it would be more appropriate to use
for(key in arr)
//Always exists
I've never encountered an intentional one in the wild, and really only began thinking about it while reading an odd Q&A here, and now I'm a little unsettled.
I've long known that there's not a neat way to remove an element from an Array, but now I'm even more confused as to why you would intentionally leave a hole, let alone define a standard where length can be any number greater than the last defined element.
If I wanted random key value pairs, I'd use an Object. If I want to be able to cleanly iterate, I use an Array. Am I missing something?
Note, I'm looking for a specific use case, or a class of generalized use cases not a reference to the standards, or an opinion. I know it's allowed, and I already have an opinion. :)
To add a test or see some of the ones I've seen where the Array standard works in unexpected ways, check out this fiddle
Sorry if this is a bit abstract. Been thinking about it all night.
One possible use-case for sparse arrays that I've come across in real usage is for a heat-map.
Start with your map being an empty array of X × Y elements. Load your data, and populate it into the map by incrementing the array elements at the relevant co-ords.
Another similar example might be a battleship game, where boats are placed into an empty grid by populating the array elements at the appropriate co-ordinates.
That's not to say this is this only way to do this, or even the best way -- both examples can quite easily be achieved without using a sparse array -- but the question was asking for use cases, so there you go.
implementing the mathematical idea of 'sparse array' with JavaScript plain object ({}):
source={}
source[1000000] = 1; source[2000000]=2; source[3000000]=3
start = new Date();
target = (function(){
var ret = {};
Object.keys(source).forEach(function(key){
ret[key] = source[key]*source[key]
});
return ret
})();
end = new Date();
document.getElementById('runtime').textContent = end - start;
document.getElementById('result').textContent = JSON.stringify(target);
........................ result ..........................
<div id="result"></div>
...................... run time ..........................
<div id="runtime"></div>
implementing the mathematical idea of 'sparse array' with JavaScript array ([]):
source=[]
source[1000000] = 1; source[2000000]=2; source[3000000]=3
start = new Date();
target = source.map(function(u){ return u*u; });
end = new Date();
document.getElementById('runtime').textContent = end - start;
//document.getElementById('result').textContent = JSON.stringify(target);
console.log(target)
........................ result ..........................
<div id="result">see console</div>
...................... run time ..........................
<div id="runtime"></div>
The runtime is show to be zero in the first snippet (below what can be properly measured unless you run it repeatedly). In the 2nd snippet the runtime is about 100 milliseconds on my machine.
Would you like to "pay" for a little less typing with such differences in runtime? And if you don't do the things that have this horrible runtime (do you know what they are??) what remains then of the saved typing? the answer to the last question is: nothing.
{} is the correct choice. [] is the wrong choice. IMHO.
related question

Categories