Why is pop faster than shift? - javascript

Douglas Crockford, in JavaScript: The Good Parts, states that "shift is usually much slower than pop". jsPerf confirms this. Does anyone know why this is the case? From an unsophisticated point of view, they seem to be doing pretty much the same thing.

To remove the returned item without re-addressing the array and invalidating all references to it, shift() requires moving the entire array around; pop() can simply subtract 1 from its length.

shift() has to re-index the whole array while pop() doesn't.
pop() simply removes the last element in the array. Therefore, the elements do not move; simply the .length has to be updated.
shift() removes the first element in the array. This requires a re-indexing of all elements in the array, so that [1] becomes [0] and so on.

I was doing some tests on this with node (which uses chrome v8) and noticed that for arrays up to around 120k elements the performance of shift is pretty close to pop. Once you get bigger than 120K it seems to slow down dramatically.
var sum;
var tests = [125000,130000];
console.log(JSON.stringify(process.versions));
tests.forEach(function(count) {
console.log('Testing arrays of size ' + count);
var s1 = Date.now();
var sArray = new Array(count);
var pArray = new Array(count);
for (var i = 0; i < count ; i++) {
var num = Math.floor(Math.random() * 6) + 1
sArray[i] = num;
pArray[i] = num;
}
console.log(' -> ' + (Date.now() - s1) + 'ms: built arrays with ' + count + ' random elements');
s1 = Date.now();
sum = 0;
while (pArray.length) {
sum += pArray.pop();
}
console.log(' -> ' + (Date.now() - s1) + 'ms: sum with pop() ' + count + ' elements, sum = ' + sum);
s1 = Date.now();
sum = 0;
while (sArray.length) {
sum += sArray.shift();
}
console.log(' -> ' + (Date.now() - s1) + 'ms: sum with shift() ' + count + ' elements, sum = ' + sum);
});
Output:
{"http_parser":"1.0","node":"0.10.22","v8":"3.14.5.9","ares":"1.9.0-DEV","uv":"0.10.19","zlib":"1.2.3","modules":"11","openssl":"1.0.1e"}
Testing arrays of size 125000
-> 14ms: built arrays with 125000 random elements
-> 2ms: sum with pop() 125000 elements, sum = 436673
-> 6ms: sum with shift() 125000 elements, sum = 436673
Testing arrays of size 130000
-> 50ms: built arrays with 130000 random elements
-> 1ms: sum with pop() 130000 elements, sum = 455971
-> 54372ms: sum with shift() 130000 elements, sum = 455971

Because shift() reindex array so the shift method is very slow on large array.
var array = [];
for(var i = 0;i< 1000000;i++){
array.push(i)
}
var start = new Date().getTime()
for(var i = 0; i< 100000; i++){
array.shift();
}
var duration = new Date().getTime() - start;// duration is so large, greater than 3 minutes
But the duration is just 8ms when using linked-queue
var LQueue = require('linked-queue')
var queue = new LQueue()
for(var i = 0;i< 1000000;i++){
queue.enqueue(i);
}
console.log("Queue length:"+ queue.length);
var start = new Date().getTime()
queue.dequeueAll(function(data){
})
var end = new Date().getTime();
console.log("Time:" + (end - start));// 8 ms
console.log("Queue length:"+ queue.length);

The difference can be negligible—Unoptimized executors may run shift much slower than pop, but optimized ones won't.
You can optimize like this:
let WrapArray = _=>{
//Ensure no other ref to `_`.
let numlike = _=>isNaN(_)?false:true
let num = _=>Number(_)
{
let shift_q = 0
return new Proxy(_, {
get(first_t, k){
switch(k){
case 'shift': return (z={})=>(z.r=first_t[0 + shift_q], delete first_t[0 + shift_q++], z.r)
break; case 'length': return first_t.length - shift_q
break; default: return first_t[numlike(k)?num(k) +/*todo overflowguard*/shift_q:k]
}
},
set(first_t, k, v){
switch(k){
case 'length': first_t.length = v + shift_q
break; default: first_t[numlike(k)?num(k) +/*todo overflowguard*/shift_q:k] = v
}
},
has(first_t, k){
return (numlike(k)?num(k) +/*todo overflowguard*/shift_q:k) in first_t
},
deleteProperty(first_t, k){
delete first_t[numlike(k)?num(k) +/*todo overflowguard*/shift_q:k];return 543
},
apply(first_t, t, s){
first_t.call(t, s)
},
construct(first_t, s, t){
new first_t(...s)
},
})
}
}
(_=WrapArray(['a','b','c'])).shift()
console.log(_.length/*2*/, _[0]/*b*/, _[1]/*c*/, _[2]/*undefined*/)

If you shift, you have copy all the elements in the array backwards. To pop, you only need to decrement the length of the array. Technically, an implementation could get around this, but you would need to store an extra `shift' variable that tells you where the real start of the array is. However, this type of operation has not proven to be very useful in practice and so most implementations save space by only storing a start of array pointer and a length value.

Related

JS: How to make a for loop out of this?

I have to create a 20 column bar-chart. The bar on the very right gets updated every second with a random number which gives the bar its height. After one second the value gets transferred to its neighbour on the left and so on. To connect the values from the array to css, I created this piece of code. On the left (barchart) is connected to the div via querySelectorAll and then indexed with [i], on the right i take the corresponding value from the array. Since I need to do 20 bars, it would make sense to use a for loop, but I don't really know how to create it... Any ideas?
const arr = [];
let number = "";
function timer() {
setInterval(function () {
number = Math.floor((Math.random() * 100) + 1);
const barchart = document.querySelectorAll(".bar");
barchart[4].style.height = number + "%";
barchart[3].style.height = arr[4] + "%";
barchart[2].style.height = arr[3] + "%";
barchart[1].style.height = arr[2] + "%";
barchart[0].style.height = arr[1] + "%";
arr.push(number);
if (arr.length > 5) {
arr.shift();
}
console.log(arr);
}, 1000);
}
How to shorten this into a loop?
barchart[4].style.height = number + "%";
barchart[3].style.height = arr[4] + "%";
barchart[2].style.height = arr[3] + "%";
barchart[1].style.height = arr[2] + "%";
barchart[0].style.height = arr[1] + "%";
Looking at what you have so far the first time the code runs arr is only initialized with no elements at all and you are attempting to reach out of bound indices which returns undefined
Moreover, you should not use initialize an array as a const if you are planning on changing the values it stores.
Now for the problem at hand:
Considering the bar on the far right is positioned at the last index of barchart and that arr contains the corresponding bar's heights:
//give the bar on the far right a random height.
barchart[barchart.length-1].style.height = number + "%";
//loop through barcharts array from end to start excluding the last bar.
for(let i=barchart.length-2; i>=0; i--)
{
//give each bar the height of his right neighbour
barchart[i].style.height = arr[i+1] + "%";
}
How about just making sure arr has what you want before updating the bar chart, then you can just iterate over it.
Adding the new number and then making sure there are at most 5 numbers allows you to just iterate over all the elements in the array and it will only update the bars for the values it contains - no index out of bounds issues (you just get undefined with JS though.)
NOTE, I replaced querySelectorAll with querySelector as querySelectorAll returns a collection of matched elements rather than a single element.
const arr = [];
let number = "";
function timer() {
setInterval(function() {
number = Math.floor((Math.random() * 100) + 1);
arr.push(number);
// make sure arr max's out at 5 numbers
if (arr.length > 5)
arr.shift();
// update the bar chart(s)
const barchart = document.querySelector(".bar");
for (let i = 0; i < arr.length; i++)
barchart[i].style.height = arr[i] + "%";
console.log(arr);
}, 1000);
}
for(var i = 0;i < 5;i++){
barchart[i].style.height = arr[i+1] + "%";
}

Sorting and Median with Arrays in Javascript

I have been working on this code, and the goal is to sort out the numbers in the array, and then find the median. My median isn't outputting correctly, and when I try to just see what is in array[0], it never has the right value. I'm not exactly sure where I messed up.
Code:
var array = [];
window.onload = function (){
var answer = '';
var median = 0;
for (var i = 0; i < 8; i++) {
var rand = Math.floor(Math.random() * 101);
array.push(rand);
array.sort(function(a, b){return a-b});
answer = answer + array[i] + " ";
}
median = ((array[3] + array[4]) /2);
document.getElementById("result").innerHTML = answer + "<br />" + median;
}
I would suggest first moving your loops ending. Currently you are sorting every single time you add a new number to the array. This means two things : you are wasting computation power on something you should only do once and when you 'log' your result in the line answer = answer + array[i] + " "; its constantly changing since the order is changing. Your functions logic is correct so by making the change below you should get the result you want.
var array = [];
window.onload = function (){
var answer = '';
var median = 0;
//Loop is simplified to just push a random value
for (var i = 0; i < 8; i++) {
array.push(Math.floor(Math.random() * 101));
}
//Sort is outside of the loop;
array.sort(function(a, b){return a-b});
//Median is outside of the loop
median = ((array[3] + array[4]) /2);
//answer is outside of the loop (if you don't know reduce look at the link below)
answer = array.reduce( function ( answer , value ) {
return answer + ',' + value;
} );
// put into the dom
document.getElementById("result").innerHTML = answer + "<br />" + median;
}
If you need help with this feel free to message me, also checkout the documentation for reduce HERE.
Using purely SO posts, I came up with a solution.
Strategy
Shuffle
At first, the partial expression (Math.floor(Math.random() * 101)) came up with duplicates, that's weaksauce. Fisher-Yates (aka Knuth) Shuffle has an excellent algorithm.
Your var answer and reduce expression is now combined and out of the loop as per #hyphnKnight explained. There's no need to break it down any further because reduce return is everything you need to display a sorted array. I also used unshift instead of push, I read that it's faster to use the front of the array rather than the back, but you can't tell the difference, too small of a function and all.
Snippet
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>35469092</title>
</head>
<output id="result"></output>
<body>
<script>
// 1. Populate an array with the numbers 1 through 100.
var arr = [];
for(var i = 1; i <= 100; i++) {
arr.unshift(i);
}
median(arr);
function median(arr){
var median = 0;
// 2. Shuffle
var ran100 = shuffle(arr);
var ran8 = [];
for(var j = 0; j < 8; j++) {
// Take the first 8 elements of the resulting array.
ran8.unshift(ran100[j]);
}
var answer = ran8.sort(function(a, b){return a-b});
median = ((ran8[3] + ran8[4]) /2);
document.getElementById("result").innerHTML = answer + "<br />" + median;
}
function shuffle(arr) {
var curIdx = arr.length, tmpVal, randIdx;
while (0 !== curIdx) {
ranIdx = Math.floor(Math.random() * curIdx);
curIdx -= 1;
tmpVal = arr[curIdx];
arr[curIdx] = arr[ranIdx];
arr[ranIdx] = tmpVal;
}
return arr;
}
</script>
</body>
</html>

Accessing indexes of a string inside an array and taking the sum

Ok so I am trying to access each individual number in the strings inside of this array.
var array = ['818-625-9945','999-992-1313','888-222-2222','999-123-1245'];
var str = "";
for (i=0; i<array.length; i++) {
str = array[i];
}
The problem is that this is the output: '999-992-1313'
and not the first element array[0]: '818-625-9945'
When I try doing a nested for loop to go through each element inside the string I am having trouble stating those elements.
var array = ['818-625-9945','999-992-1313','888-222-2222','999-123-1245'];
for (i=0; i<array.length; i++) {
for (j=0; j<array[i].length; j++) {
console.log(array[i][j]);
}
}
I do not know how to access each individual number inside of the string array[i]. I would like to find a way to make a counter such that if I encounter the number '8' I add 8 to the total score, so I can take the sum of each individual string element and see which number has the highest sum.
var array = ['818-625-9945','999-992-1313','888-222-2222','999-123-1245'];
for (i=0; i<array.length; i++) {
for (j=0; j<array[i].length; j++) {
if (array[i](j).indexOf('8') !== -1) {
// add one to total score
// then find a way to increase the index to the next index (might need help here also please)
}
}
}
Mabe this works for you. It utilized Array.prototype.reduce(), Array.prototype.map() and String.prototype.split().
This proposal literates through the given array and splits every string and then filter the gotten array with a check for '8'. The returned array is taken as count and added to the return value from the former iteration of reduce - and returned.
var array = ['818-625-9945', '999-992-1313', '888-222-2222', '999-123-1245'],
score = array.reduce(function (r, a) {
return r + a.split('').filter(function (b) { return b === '8'; }).length;
}, 0);
document.write('Score: ' + score);
A suggested approach with counting all '8' on every string:
var array = ['818-625-9945', '999-992-1313', '888-222-2222', '999-123-1245'],
score = array.map(function (a) {
return a.split('').filter(function (b) { return b === '8'; }).length;
});
document.write('Score: ' + score);
Actually rereading your question gave me a better idea of what you want. You simply want to count and retrieve the number of 8's per string and which index in your array conforms with this maximum 8 value. This function retrieves the index where the value was found in the array, how many times 8 was found and what is the string value for this result. (or returns an empty object in case you give in an empty array)
This you could easily do with:
'use strict';
var array = ['818-625-9945', '999-992-1313', '888-222-2222', '999-123-1245'];
function getHighestEightCountFromArray(arr) {
var max = 0,
result = {};
if (arr && arr.forEach) {
arr.forEach(function(value, idx) {
var cnt = value.split('8').length;
if (max < cnt) {
// found more nr 8 in this section (nl: cnt - 1)
max = cnt;
// store the value that gave this max
result = {
count: cnt - 1,
value: value,
index: idx
};
}
});
}
return result;
}
console.log(getHighestEightCountFromArray(array));
The only thing here is that when an equal amount of counts is found, it will still use the first one found, here you could decide which "maximum"
should be preferred(first one in the array, or the newest / latest one in the array)
OLD
I'm not sure which sums you are missing, but you could do it in the following way.
There I first loop over all the items in the array, then I use the String.prototype.split function to split the single array items into an array which would then contain ['818', '625', '9945']. Then for each value you can repeat the same style, nl: Split the value you are receiving and then loop over all single values. Those then get convert to a number by using Number.parseInt an then all the values are counted together.
There are definitelly shorter ways, but this is a way how you could do it
'use strict';
var array = ['818-625-9945','999-992-1313','888-222-2222','999-123-1245'],
sumPerIndex = [],
totalSum = 0;
array.forEach(function(item, idx) {
var values = item.split('-'), subArray = [], itemSum = 0;
values.forEach(function(value) {
var singleItems = value.split(''),
charSum = 0;
singleItems.forEach(function(char) {
charSum += parseInt(char);
});
itemSum += charSum;
subArray.push(charSum);
console.log('Sum for chars of ' + value + ' = ' + charSum);
});
sumPerIndex.push(subArray);
totalSum += itemSum;
console.log('Sum for single values of ' + item + ' = ' + itemSum);
});
console.log('Total sum of all elements: ' + totalSum);
console.log('All invidual sums', sumPerIndex);

math random number without repeating a previous number

Can't seem to find an answer to this, say I have this:
setInterval(function() {
m = Math.floor(Math.random()*7);
$('.foo:nth-of-type('+m+')').fadeIn(300);
}, 300);
How do I make it so that random number doesn't repeat itself. For example if the random number is 2, I don't want 2 to come out again.
There are a number of ways you could achieve this.
Solution A:
If the range of numbers isn't large (let's say less than 10), you could just keep track of the numbers you've already generated. Then if you generate a duplicate, discard it and generate another number.
Solution B:
Pre-generate the random numbers, store them into an array and then go through the array. You could accomplish this by taking the numbers 1,2,...,n and then shuffle them.
shuffle = function(o) {
for(var j, x, i = o.length; i; j = parseInt(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x);
return o;
};
var randorder = shuffle([0,1,2,3,4,5,6]);
var index = 0;
setInterval(function() {
$('.foo:nth-of-type('+(randorder[index++])+')').fadeIn(300);
}, 300);
Solution C:
Keep track of the numbers available in an array. Randomly pick a number. Remove number from said array.
var randnums = [0,1,2,3,4,5,6];
setInterval(function() {
var m = Math.floor(Math.random()*randnums.length);
$('.foo:nth-of-type('+(randnums[m])+')').fadeIn(300);
randnums = randnums.splice(m,1);
}, 300);
You seem to want a non-repeating random number from 0 to 6, so similar to tskuzzy's answer:
var getRand = (function() {
var nums = [0,1,2,3,4,5,6];
var current = [];
function rand(n) {
return (Math.random() * n)|0;
}
return function() {
if (!current.length) current = nums.slice();
return current.splice(rand(current.length), 1);
}
}());
It will return the numbers 0 to 6 in random order. When each has been drawn once, it will start again.
could you try that,
setInterval(function() {
m = Math.floor(Math.random()*7);
$('.foo:nth-of-type(' + m + ')').fadeIn(300);
}, 300);
I like Neal's answer although this is begging for some recursion. Here it is in java, you'll still get the general idea. Note that you'll hit an infinite loop if you pull out more numbers than MAX, I could have fixed that but left it as is for clarity.
edit: saw neal added a while loop so that works great.
public class RandCheck {
private List<Integer> numbers;
private Random rand;
private int MAX = 100;
public RandCheck(){
numbers = new ArrayList<Integer>();
rand = new Random();
}
public int getRandomNum(){
return getRandomNumRecursive(getRand());
}
private int getRandomNumRecursive(int num){
if(numbers.contains(num)){
return getRandomNumRecursive(getRand());
} else {
return num;
}
}
private int getRand(){
return rand.nextInt(MAX);
}
public static void main(String[] args){
RandCheck randCheck = new RandCheck();
for(int i = 0; i < 100; i++){
System.out.println(randCheck.getRandomNum());
}
}
}
Generally my approach is to make an array containing all of the possible values and to:
Pick a random number <= the size of the array
Remove the chosen element from the array
Repeat steps 1-2 until the array is empty
The resulting set of numbers will contain all of your indices without repetition.
Even better, maybe something like this:
var numArray = [0,1,2,3,4,5,6];
numArray.shuffle();
Then just go through the items because shuffle will have randomized them and pop them off one at a time.
Here's a simple fix, if a little rudimentary:
if(nextNum == lastNum){
if (nextNum == 0){nextNum = 7;}
else {nextNum = nextNum-1;}
}
If the next number is the same as the last simply minus 1 unless the number is 0 (zero) and set it to any other number within your set (I chose 7, the highest index).
I used this method within the cycle function because the only stipulation on selecting a number was that is musn't be the same as the last one.
Not the most elegant or technically gifted solution, but it works :)
Use sets. They were introduced to the specification in ES6. A set is a data structure that represents a collection of unique values, so it cannot include any duplicate values. I needed 6 random, non-repeatable numbers ranging from 1-49. I started with creating a longer set with around 30 digits (if the values repeat the set will have less elements), converted the set to array and then sliced it's first 6 elements. Easy peasy. Set.length is by default undefined and it's useless that's why it's easier to convert it to an array if you need specific length.
let randomSet = new Set();
for (let index = 0; index < 30; index++) {
randomSet.add(Math.floor(Math.random() * 49) + 1)
};
let randomSetToArray = Array.from(randomSet).slice(0,6);
console.log(randomSet);
console.log(randomSetToArray);
An easy way to generate a list of different numbers, no matter the size or number:
function randomNumber(max) {
return Math.floor(Math.random() * max + 1);
}
const list = []
while(list.length < 10 ){
let nbr = randomNumber(500)
if(!list.find(el => el === nbr)) list.push(nbr)
}
console.log("list",list)
I would like to add--
var RecordKeeper = {};
SRandom = function () {
currTimeStamp = new Date().getTime();
if (RecordKeeper.hasOwnProperty(currTimeStamp)) {
RecordKeeper[currTimeStamp] = RecordKeeper[currTimeStamp] + 1;
return currTimeStamp.toString() + RecordKeeper[currTimeStamp];
}
else {
RecordKeeper[currTimeStamp] = 1;
return currTimeStamp.toString() + RecordKeeper[currTimeStamp];
}
}
This uses timestamp (every millisecond) to always generate a unique number.
you can do this. Have a public array of keys that you have used and check against them with this function:
function in_array(needle, haystack)
{
for(var key in haystack)
{
if(needle === haystack[key])
{
return true;
}
}
return false;
}
(function from: javascript function inArray)
So what you can do is:
var done = [];
setInterval(function() {
var m = null;
while(m == null || in_array(m, done)){
m = Math.floor(Math.random()*7);
}
done.push(m);
$('.foo:nth-of-type('+m+')').fadeIn(300);
}, 300);
This code will get stuck after getting all seven numbers so you need to make sure it exists after it fins them all.

How to get a unique random integer in a certain range for every number in that range in javascript?

I have:
function getRandomInt(min, max){
return Math.floor(Math.random() * (max - min + 1)) + min;
}
But the problem is I want randomise the population of something with elements in an array (so they do not appear in the same order every time in the thing I am populating) so I need to ensure the number returned is unique compared to the other numbers so far.
So instead of:
for(var i = 0; i < myArray.length; i++) {
}
I have:
var i;
var count = 0;
while(count < myArray.length){
count++;
i = getRandomInt(0, myArray.length); // TODO ensure value is unique
// do stuff with myArray[i];
}
It looks like rather than independent uniform random numbers you rather want a random permutation of the set {1, 2, 3, ..., N}. I think there's a shuffle method for arrays that will do that for you.
As requested, here's the code example:
function shuffle(array) {
var top = array.length;
while (top--) {
var current = Math.floor(Math.random() * top);
var tmp = array[current];
array[current] = array[top - 1];
array[top - 1] = tmp;
}
return array;
}
Sometimes the best way to randomize something (say a card deck) is to not shuffle it before pulling it out, but to shuffle it as you pull it out.
Say you have:
var i,
endNum = 51,
array = new Array(52);
for(i = 0; i <= endNum; i++) {
array[i] = i;
}
Then you can write a function like this:
function drawNumber() {
// set index to draw from
var swap,
drawIndex = Math.floor(Math.random() * (endNum+ 1));
// swap the values at the drawn index and at the "end" of the deck
swap = array[drawIndex];
array[drawIndex] = array[endNum];
array[endNum] = swap;
endNum--;
}
Since I decrement the end counter the drawn items will be "discarded" at the end of the stack and the randomize function will only treat the items from 0 to end as viable.
This is a common pattern I've used, I may have adopted it into js incorrectly since the last time I used it was for writing a simple card game in c#. In fact I just looked at it and I had int ____ instead of var ____ lol
If i understand well, you want an array of integers but sorted randomly.
A way to do it is described here
First create a rand function :
function randOrd(){
return (Math.round(Math.random())-0.5); }
Then, randomize your array. The following example shows how:
anyArray = new Array('1','2','3','4','5');
anyArray.sort( randOrd );
document.write('Random : ' + anyArray + '<br />';);
Hope that will help,
Regards,
Max
You can pass in a function to the Array.Sort method. If this function returns a value that is randomly above or below zero then your array will be randomly sorted.
myarray.sort(function() {return 0.5 - Math.random()})
should do the trick for you without you having to worry about whether or not every random number is unique.
No loops and very simple.

Categories