Javascript/Angular returning array after shuffle - javascript

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.

Related

JavaScript - Filter array with mutation

I want to filter a array by keeping the same array without creating a new one.
with Array.filter() :
getFiltersConfig() {
return this.config.filter((topLevelConfig) => topLevelConfig.name !== 'origin')
}
what is the best way to get the same result by filtering by value without returning a new array ?
For completeness, I thought it might make sense to show a mutated array variant.
Below is a snippet with a simple function mutationFilter, this will filter the array directly, notice in this function the loop goes in reverse, this is a technique for deleting items with a mutated array.
Also a couple of tests to show how Array.filter creates a new array, and mutationFilter does not.
Although in most cases creating a new array with Array.filter is normally what you want. One advantage of using a mutated array, is that you can pass the array by reference, without you would need to wrap the array inside another object. Another advantage of course is memory, if your array was huge, inline filtering would take less memory.
let arr = ['a','b','a'];
let ref = arr; //keep reference of original arr
function mutationFilter(arr, cb) {
for (let l = arr.length - 1; l >= 0; l -= 1) {
if (!cb(arr[l])) arr.splice(l, 1);
}
}
const cond = x => x !== 'a';
const filtered = arr.filter(cond);
mutationFilter(arr, cond);
console.log(`ref === array -> ${ref === arr}`);
console.log(arr);
console.log(`ref === filtered -> ${ref === filtered}`);
console.log(filtered);
I want to filter a array by keeping the same array without creating a new one.
what is the best way to get the same result by filtering by value without returning a new array ?
I have an answer for the second criterion, but violates the first. I suspect that you may want to "not create a new one" specifically because you only want to preserve the reference to the array, not because you don't want to create a new array, necessarily (e.g. for memory concerns).
What you could do is create a temp array of what you want
var temp = this.config.filter((topLevelConfig) => topLevelConfig.name !== 'origin')
Then set the length of the original array to 0 and push.apply() the values "in-place"
this.config.length = 0; //clears the array
this.config.push.apply(this.config, temp); //adds what you want to the array of the same reference
You could define you custom method like so:
if(!Array.prototype.filterThis){
Array.prototype.filterThis = function (callBack){
if(typeof callBack !== 'function')
throw new TypeError('Argument must of type <function>');
let t = [...this];
this.length = 0;
for(let e of t) if(callBack(e)) this.push(e);
return this;
}
}
let a = [1,2,3,4,5,5,1,5];
a.filterThis(x=>x!=5);
console.log(a);
Warning: Be very cautious in altering built in prototypes. I would even say unless your making a polyfill don't touch. The errors it can cause can be very subtle and very hard to debug.
Not sure why would you want to do mutation but if you really want to do it, maybe assign it back to itself?
let arr = ['a','b','a'];
arr = arr.filter(x => x !== 'a');
console.log(arr)

JQuery object shuffle / randomize

Hi I have a JQuery object with multiple divs inside each containing text:
var object = $(<div>我</div>,<div>喜欢</div>,<div>吃</div>);
I want to copy the object and shuffle the divs inside so that when I display the new object, it will be a shuffled version of the original object. Is this possible with a JQuery object or am I going to have to store the divs in an array, and if so can someone tell me how I could achieve this?
Use a generic shuffle algorithm (e.g. the Fisher-Yates shuffle) and pass your object as the argument, e.g.
function shuffle(array) {
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;
};
var object = $('<div>1</div><div>2</div><div>3</div><div>4</div><div>5</div>');
/* use spread operator to pass the array of divs */
var shuffledArray = shuffle([...object]);
/* append divs to the body */
$('body').html($(shuffledArray));
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

checking if more elements of array have same value of key , if they have remove one?

so I've tried everything that i know. Using map and filter, prototypes. Didn't work. .
[{"color":"black","type":"bmw"},{"color":"gray","type":"golf"}, {"color":"red","type":"bmw"}, {"color":"black","type":"mercedes"}]
So what I want to achieve, is when i do ajax, with javascript, to check if two or more object have same value for type, if there is for example two or more bmw-s, remove others and and push just one object with bmw type. Hope i am clear enough.
Thanks in advance
function removeDuplicates(arr) {
var alreadyExist = {}; // hash object to keep track of elemnt that have already been encountered
var indexes = []; // array of indexes that will be removed
arr.forEach(function(o, i) { // for each object o in arr
if(alreadyExist[o.type]) // if the type of the object o at index i already exist
indexes.push(i); // mark its index i to be removed later
else // if not
alreadyExist[o.type] = true; // then mark the object as found so other ones will be removed
});
// for each index in the indexes array
for(var i = 0; i < indexes.length; i++)
arr.splice(indexes[i] - i, 1); // remove the object at that index ( - i because the array arr is continually changing. Its length decrease every time we remove an item)
}
var array = [{"color":"black","type":"bmw"},{"color":"gray","type":"golf"}, {"color":"red","type":"bmw"}, {"color":"red","type":"bmw"}, {"color":"red","type":"bmw"}, {"color":"black","type":"mercedes"}];
removeDuplicates(array);
console.log(array);
don't remove elements, create a filtered Array:
var yourArray = [{"color":"black","type":"bmw"},{"color":"gray","type":"golf"}, {"color":"red","type":"bmw"}, {"color":"black","type":"mercedes"}];
var cache = {},
filteredArray = yourArray.filter(({type}) => type in cache? false: (cache[type] = true));
console.log(filteredArray);
It's non destructive, more performant and even simpler and shorter.
Edit: And even without modern features:
var filteredArray = yourArray.filter(function(item){
return item.type in this? false: (this[item.type] = true);
}, {/* the cache-object */}); //abusing `this` to pass the cache object
You can keep track of which types are already present in your array with an object, then only push into new array those that are not present:
var vehicles = [{"color":"black","type":"bmw"},{"color":"gray","type":"golf"}, {"color":"red","type":"bmw"}, {"color":"black","type":"mercedes"}];
var uniques = [];
var types = {};
for (var i = 0; i < a.length; i++) {
if (!types[a[i].type]) { uniques.push(a[i]); }
types[a[i].type] = true;
}
//uniques = [{"color":"black","type":"bmw"},{"color":"gray","type":"golf"}, {"color":"black","type":"mercedes"}];

What is wrong with my shuffling program?

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.

How do you push a specified number of objects to an array?

I'm pretty new to javascript but I'm trying to push a specified number of objects to an array using the following code. When I check the console I see only one object is pushed to the array. What should I be doing differently? Thanks!
var albums = {};
function collection(numberOfAlbums) {
array = [];
array.push(albums);
return array;
};
console.log(collection(12));
From your code:
array.push(albums);
would add the same object each time (assuming you had added a loop) which isn't what you want.
This will add a new empty object for each iteration of numberOfAlbums:
function collection(numberOfAlbums) {
for (var array = [], i = 0; i < numberOfAlbums; i++) {
array.push({});
}
return array;
};
Here's another way using map. Array.apply trick from here.
function collection(numberOfAlbums) {
var arr = Array.apply(null, Array(numberOfAlbums));
return arr.map(function (el) { return {}; });
};
I could give you the code but that is not learning. So here are the steps:
use numberOfAlbums as an argument in a function.
create an empty array.
use numberOfAlbums in for-loops in that for-loops push albums. ==array.push(albums)== do not use {}curly brackets around albums.
return the array.

Categories

Resources