What is wrong with my shuffling program? - javascript

I wrote a shuffling program below and ran it through "Will It Shuffle?". The results appear to show that it's working in the console; it's shuffling the array. But the website shows me an all red box, making me think something is wrong with my code, but I don't see it.
function shuffle (array) {
var arr = [],
length = array.length,
el;
while (length > 0) {
var randomEl = Math.floor(Math.random() * (length - 0) - 0);
if (length > 1) {
el = array.splice(randomEl,1);
} else {
el = array.splice(0,1);
}
arr.push(el[0]);
length -= 1;
}
return arr;
}

That page ignores the returned value of the function, because it expects an in-place sort.
If you add this at the end of your code, it works as expected:
array.push(...arr);
You can also do it in-place directly:
function shuffle (array) {
var length = array.length;
while (length) {
var randomEl = Math.floor(Math.random() * length);
var el = array.splice(randomEl, 1);
array.push(el[0]);
--length;
}
}

They alter the array, you do not alter the array.
You need to alter the original array, not return a new array.
function shuffle (array) {
var arr = [],
length = array.length,
el;
while (length > 0) {
var randomEl = Math.floor(Math.random() * (length - 0) - 0);
if (length > 1) {
el = array.splice(randomEl,1);
} else {
el = array.splice(0,1);
}
arr.push(el[0]);
length -= 1;
}
//replace array with the new items
//it is like using concat, but does not produce a new array,
//just appends it to the original which has zero items in it.
Array.prototype.push.apply(array, arr);
}

What you are doing is creating a new array with the elements of the original shuffled around.
However, if you go back and look at the array that you passed in, you'll notice it has not been shuffled, but rather emptied. Apparently, this is not what "Will it Shuffle?" asks you to do.
splice() and push() both always mutate the array you call those methods on.
To answer your question about .push(...arr), an elipses in javascript is a feature that arrived with the latest version, EcmaScript 2015. It is the "spread operator".
When you call a function with a "spread" array, it's like calling the function with the contents of the array as separate arguments. For instance,
array.push(...[1,2,3])
is the same as calling
array.push(1,2,3)
push() can add an arbitrary number of comma-separated arguments to an array. So, after emptying the array argument with your looped splices, you could push the contents of the newly-created arr to the empty array using the spread operator.

Related

Nodejs code is not executing as expected

I am trying the below line of codes. I want to save the numbers after each iteration of outer while loop in an array named sn. But, after each iteration sn contains only the numbers of last iteration. May be I am missing whole concept of sync and async.
function test() {
var numbers = [0, 2, 7, 0];
var check = true;
var sn = [];
var p = 0;
while (check) {
var index = numbers.indexOf(Math.max(...numbers));
var value = Math.max(...numbers);
numbers[index] = 0;
for (var i = value; i > 0; i--) {
var temp = ++index;
index = temp % (numbers.length);
numbers[index] += 1;
}
console.log("numbers", numbers);
if (sn.includes(numbers)) { check = false };
sn.push(numbers);
console.log("hey there=========");
}
}
test();
There is nothing to do with sync or async here.
Here what is happening is that, you are trying to push 'numbers' array to 'sn' array.
Statement is "sn.push(numbers);"
So here we are pushing the Object reference of numbers array to 'sn', means you are not making a copy of numbers array and pushing to 'sn'.
You are just pushing the Memory reference of 'numbers' array.
So during first iteration, 'sn' will have exact value as you calculates.
But during the second iteration 'sn' will have two arrays. But those two values are same and points to the same memory location of 'number'.
So here what you should do is create a clone of 'numbers' array during each iteration.
if (sn.includes(numbers)) { check = false };
var cloneArray = numbers.slice(0);
sn.push(cloneArray);
This if statement: if (sn.includes(numbers)) { check = false }; will never be true because the Array.prototype.includes() method does not accept an array as a parameter; only individual elements. numbers is an array and thus will never be truthy.
If you are trying to see if an array contains a sub-array. The answer that Mild Fuzz has in this stack overflow: Javascript array contains/includes sub array should work.

How can I get an array of random keys from an object, as opposed to a full list?

Rather than getting, for example, the first 5 keys from an object with 10 keys:
var keys = Object.keys(brain.layers[this.layer]).slice(0, 5);
I'd like to get 5 of the keys at random. I know of bulky, long, roundabout ways of doing it, such as something like this:
function getRandomNumber(n1, n2) { ... }
var list = [];
var count = 0;
function choose(arr, count, list, max) {
for (let prop in arr) {
var choice = Math.round(getRandomNumber(0, 1));
if (choice === 1 && count < max && !list.includes(arr[prop])) {
list.push(arr[prop]);
count++;
}
}
if (count >= max) {
return list;
} else {
choose(arr, count, list, max)
}
}
But I was wondering if there's a simpler, more elegant solution.
To get truly a truly (pseudo)random sort use something like an in place random sort.
let arrRand=(a,i=a.length)=>{while(i){a.push(a.splice(Math.random()*i--|0,1)[0])}}
let keys = Object.keys(brain.layers[this.layer])
randSort(keys)
keys=keys.slice(0,5)
This takes an array and the array length though if it is a modern browser you can use default arguments for that. Warning it modifies the passed in array.

What is the in-place alternative to Array.prototype.filter()

I've got an array that I would like to remove some elements from. I can't use Array.prototype.filter(), because I want to modify the array in place (because it saves a memory allocation and, more important for me, makes the code more simple in my use case). Is there an in-place alternative to filter that I can use, maybe analogously to how Array.prototype.forEach() can be used as an in-place variant to Array.prototype.map()?
Edit: Minimum example upon request:
function someCallback(array) {
// do some stuff
array.filterInPlace(function(elem) {
var result = /* some logic */
return result;
})
// do some more stuff
}
Is there an in-place alternative to filter
No, but it's not hard to write your own. Here is an approach which squeezes out all the values which fail a condition.
function filterInPlace(a, condition) {
let i = 0, j = 0;
while (i < a.length) {
const val = a[i];
if (condition(val, i, a)) a[j++] = val;
i++;
}
a.length = j;
return a;
}
condition is designed to have the same signature as the callback passed to Array#filter, namely (value, index, array). For complete compatibility with Array#filter, you could also accept a fourth thisArg parameter.
Using forEach
Using forEach has the minor advantage that it will skip empty slots. This version:
Compacts arrays with empty slots
Implements thisArg
Skipps the assignment, if we have not yet encountered a failing element
function filterInPlace(a, condition, thisArg) {
let j = 0;
a.forEach((e, i) => {
if (condition.call(thisArg, e, i, a)) {
if (i!==j) a[j] = e;
j++;
}
});
a.length = j;
return a;
}
a = [ 1,, 3 ];
document.write('<br>[',a,']');
filterInPlace(a, x=>true);
document.write('<br>[',a,'] compaction when nothing changed');
b = [ 1,,3,,5 ];
document.write('<br>[',b,']');
filterInPlace(b, x=>x!==5);
document.write('<br>[',b,'] with 5 removed');
You could use the following:
array.splice(0, array.length,...array.filter(/*YOUR FUNCTION HERE*/))
Explanation:
Splice acts in place
First argument means we start at the start of the array
Second means we delete the entire array
Third means we replace it with its filtered copy
The ... is the spread operator (ES6 only) and changes each member of the array into a separate argument
What you could use
Array#filter returns an array with the same elements, but not necesserily all.
Array#map returns something for each loop, the result is an array with the same length as the source array.
Array#forEach returns nothing, but every element is processed, like above.
Array#reduce returns what ever you want.
Array#some/Array#every returns a boolean value.
But nothing from above is mutating the original array in question of length in situ.
I suggest to use a while loop, beginning from the last element and apply splice to the element, you want to remove.
This keeps the index valid and allows to decrement for every loop.
Example:
var array = [0, 1, 2, 3, 4, 5],
i = array.length;
while (i--) {
if (array[i] % 2) {
array.splice(i, 1);
}
}
console.log(array);
If you are able to add a third-party library, have a look at lodash.remove:
predicate = function(element) {
return element == "to remove"
}
lodash.remove(array, predicate)
The currently selected answer works perfectly fine. However, I wanted this function to be a part of the Array prototype.
Array.prototype.filterInPlace = function(condition, thisArg) {
let j = 0;
this.forEach((el, index) => {
if (condition.call(thisArg, el, index, this)) {
if (index !== j) {
this[j] = el;
}
j++;
}
})
this.length = j;
return this;
}
With this I can just call the function like so:
const arr = [1, 2, 3, 4];
arr.filterInPlace(x => x > 2);
// [1, 2]
I just keep this in a file called Array.js and require it when needed.
A slightly simplified TypeScript variant of user663031's answer:
function filter_in_place<T>(array: Array<T>, condition: (value: T) => boolean)
{
let next_place = 0;
for (let value of array)
{
if (condition(value))
array[next_place++] = value;
}
array.splice(next_place);
}
Using splice() instead of setting the length results in a 1.2x speedup for 1400000 iterations on Chrome 76.

Javascript/Angular returning array after shuffle

I am creating a simple shuffle/unshuffle function in an Angular app I'm building. The idea is there is a single button, on click it will either clone the array, shuffle the array, then return a shuffled order of the array, or if the array has already been shuffled, it will return the clone of the original array so that the user can revert back to the original order.
The issue I am having is I cannot figure out how to return the original order clone back to the view.
Here is a Fiddle: http://jsfiddle.net/nf6j1qvz/
Here is some function code:
$scope.shuffleThis = function(array) {
if(!$scope.isShuffled){
$scope.isShuffled = true;
$scope.unshuffled = array.slice(0);
var m = array.length, t, i;
// While there remain elements to shuffle
while (m) {
// Pick a remaining element…
i = Math.floor(Math.random() * m--);
// And swap it with the current element.
t = array[m];
array[m] = array[i];
array[i] = t;
}
return array;
}else{
console.log('unshuffling');
$scope.isShuffled = false;
array = $scope.unshuffled;
return array;
}
}
You can change your ng-click as follows
<button ng-click="array = shuffleThis(array)">
And you're done!
Plunkr:
http://jsfiddle.net/grmqxx9e/
Use angular.copy instead to clone the array. It's a deep copy and has always worked for me where the method you are using is not reliable.
https://docs.angularjs.org/api/ng/function/angular.copy
var originalArray = [];
angular.copy(array, originalArray);
// Continue doing your stuffs
But also, you are calling a function that has a return, so you are not setting that variable properly.
You could either change your ng-click to
ng-click='array = shuffleThis(array)'
Or instead of
return array
in your function, do
$scope.array = array;
I would do the second method personally.

Removing all items from an array (individually)

I have an array with a finite number of items in it. I want to randomly remove items until all the items have been used once.
Example [1,2,3,4,5]
Random number 5 is used, so I don't want that again.
Random number 2 is used, so I don't want that again.
And so on..
I could have another list of used numbers and check that the new random number is not in it, but that could take a long time when there is only 1 or two numbers left in the array out of 50.
Is there a way to remove an item from an array in javascript? Does it create a new array and would that be inefficient?
Are arrays the wrong way to do this?
EDIT: A few good answers here. I ended up randomizing the array list and then splicing the first item which is the one I took out.
Use the combination of Math.random() and splice():
var arr = [1, 2, 3, 4, 5],
i;
while ( arr.length ) {
i = Math.floor( Math.random() * arr.length );
alert( arr.splice(i, 1) );
}
Live demo: http://jsfiddle.net/simevidas/n2Bmk/
Update: You can use this function to remove a random item from your array:
function popRandomItem(arr) {
return arr.length ?
arr.splice(Math.floor(Math.random() * arr.length), 1)[0] : null;
}
So, if you have an array x, then popRandomItem(x) will remove a random item from that array and return that item. (If x is an empty array, the function will return null.)
Live demo: https://jsbin.com/wetumec/edit?js,console
You can use splice() to remove array elements.
Update
The function below lets you specify an upper and lower bound, and calling the resulting returned function will return a random number until the pool is empty, in which case it will return false (make sure you detect this with getRandom() === false).
var getRandomNumberOnce = function(lower, upper) {
var pool = [];
for (var i = lower; i <= upper; i++) {
pool.push(i);
}
return function() {
if (pool.length == 0) {
return false;
}
var randomIndex = Math.floor(Math.random() * pool.length ),
randomNumber = pool[randomIndex];
pool.splice(randomIndex, 1);
return randomNumber;
}
}
jsFiddle.
Usage
var myRandom = getRandomNumberOnce(0, 50);
myRandom(); // 4 (guaranteed to be random) :P http://xkcd.com/221/
I understand you're asking about removing elements but I'm going to go ahead and suggest an alternative way. This way does the same thing you want (getting a random element only once) but doesn't involve removing elements - well, it doesn't have to.
Essentially, randomise the order of your array rather than looking for random elements. Then work your way through the array for the random element you need. Your first random element will be at the 0 index, second random element will be at 1 index, etc.
var array = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14];
array.sort(function(){ return (Math.round(Math.random())-0.5); });
for(var i=0;i<array.length;i++){
$("#list").append('<li>Random element ' + i + ': ' +array[i]);
}
Example: http://jsfiddle.net/jonathon/Re4HK/
Another question talks about randomising a JavaScript array but I just used a basic random sort as it works to illustrate the idea. You might want to look at that if you're concerned about how random your array is.
Sometimes you want a random item and don't
care if it is repeated once in a while.
Other times you want to quit when the aray is empty,
and see no repeats.
Array.prototype.getRandom= function(cut){
var i= Math.floor(Math.random()*this.length);
return cut? this.splice(i, 1): this[i];
}

Categories

Resources