I have made a website where you can generate a random workout, that is, a workout with 5 random exercises combined with 5 random types of reps. To generate a random amount of reps I used Math.floor(Math.random() to the array I made. To generate 5 different, random workouts I used the shuffle function in Javascript to shuffle my array every time the page reloads.
Now I want the user to be able to save whatever result they got up on their webpage to the local storage on their computer so they can access that specific randomized workout whenever they want. How do I go about this???
Down here I publish the code I created to generate the result.
// This makes the reps generate randomly in a list of 5
let maxNr = 10;
function generateRep(){
let randomReps = [`4x10`,`4x8`, `4x20`, `4x12`, `4x15`,`3x10`, `3x15`, `4x5`, `5x10`, `10x10`];
for(let i=0; i < 5; i++){
let randomNr = Math.floor(Math.random() * maxNr);
if (randomNr==9) maxNr=9;
let repsText = "<li>"+randomReps[randomNr]+"</li>";
document.getElementById("repsList").innerHTML+=repsText;
console.log(maxNr);
}
}
//THIS IS A SHUFFLE FUNCTION
function shuffle(array) {
let currentIndex = array.length, randomIndex;
// While there remain elements to shuffle...
while (currentIndex != 0) {
// Pick a remaining element...
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex--;
// And swap it with the current element.
[array[currentIndex], array[randomIndex]] = [
array[randomIndex], array[currentIndex]];
}
return array;
}
//This is the workout generator for, in this case, a chest and back workout using the shuffle function from above.
function generateWorkout() {
let workoutList = [`Chins`, `Wide barbell row (bent over)`, `Row with machine`, `Cable pulldown`,
`Lat pulldown`, `Bent-over dumbbell alternating row`,`Reverse fly with barbell`,`Push-ups`,
`Face-pull with cable`, `Seated face pull`, `Single arm lat pulldown`, `Low position row with cable`,
`Split stance high anchor row with cable`, `Bench Press`, `Overhead press with dumbbells or barbell`,
` One arm row with dumbbell`,` Inverted row`, `Close grip dumbbell press`, ];
let shuffleWorkoutList= shuffle(workoutList);
for(let i=0; i < 5; i++){
let workoutText = "<li>"+workoutList[i]+"</li>";
document.getElementById("listOfWorkouts").innerHTML+=workoutText;
}
} ```
The syntax of using the LocalStorage is
save a value: localStorage.setItem("name-or-key", "value") where the key and value should be strings.
get a value: localStorage.getItem("name-or-key")
This is how you could implement this in your snippet:
function generateWorkout() {
let workoutList = [`Chins`, `Wide barbell row (bent over)`, `Row with machine`, `Cable pulldown`,
`Lat pulldown`, `Bent-over dumbbell alternating row`,`Reverse fly with barbell`,`Push-ups`,
`Face-pull with cable`, `Seated face pull`, `Single arm lat pulldown`, `Low position row with cable`,
`Split stance high anchor row with cable`, `Bench Press`, `Overhead press with dumbbells or barbell`,
` One arm row with dumbbell`,` Inverted row`, `Close grip dumbbell press`, ];
let shuffleWorkoutList= shuffle(workoutList);
for(let i=0; i < 5; i++){
let workoutText = "<li>"+workoutList[i]+"</li>";
};
localStorage.setItem("workout-list", workoutList.join(","));
};
and
function load_prev_workout() {
const prev_workout = localStorage.getItem("workout-list").split(",");
console.log(prev_workout);
for(let i=0; i < 5; i++){
let workoutText = "<li>"+prev_workout[i]+"</li>";
console.log("Now use this:",workoutText,"for something.");
};
};
Then call these conditionally:
if(localStorage.getItem("workout-list")) {
load_prev_workout();
} else {
generateWorkout();
};
If you want to save more than one workout at a time, let the user choose a name for that workout, or give it a unique id, then use that as part of the localStorage item's name/key
I have a number of embedded images, which I randomize 4 times without replacement (once an image is seen, you cannot see it again). I'd like to add a condition, which suggests that a set of additional images cannot be seen (not only the image that was previously selected). These are images that have similar traits to the one selected.
To demonstrate:
Let's say I have the following array of vars:
BF1, BA1, BF2, BA2, BF3, BA3
I want to randomly draw 3 vars (images) out of the array without replacement, AND I want vars that have the number 2 (same set) to be removed from the next array as well. So, if the first drawn var is BF2, the next draw will be from the following array:
BF1, BA1, BF3, BA3 (only one of these options can randomly appear)
Now let's say I drew the var BF1, so the next set of possible vars will be:
BF3, BA3.
I hope this makes sense. This is the code I have so far for the drawing without replacement:
function shuffle(array){
var counter = array.length,
temp, index;
while (counter > 0){
index = Math.floor(Math.random() * counter);
counter = counter-1;
temp = array[counter];
array[counter] = array[index];
array[index] = temp;
}
return array;
var myArray = [BF1,BA1,BF2, BA2, BF3,BA3, BA4, BF4, BA5, BF5, BF6, BA6, BF7, BA7, BA8, BF8, BA9, BF9, BF10, BA10, BA11, BF11, BA12, BF12, BA13, BF13, BA14, BF14, BA15, BF15, BA16, BF16, BA17, BF17, BA18, BF18, BA19, BF19, BA20, BF20, BA21, BF21, BF22, BA23, BF23, BA24, BF24, BA25, BF25, BA26, BF26, BA27, BF27, BA28, BF28, BA29, BF29, BA30, BF30, BA31, BF31, BA32, BF33, BA33, BA34, BF35, BA35, BA36, BF36];
shuffle(myArray)
You can definitely implement this in any number of ways, but no matter what you use, you'll need to perform the following 3 steps in some capacity (I split them out into separate methods, but you can combine them as you see fit):
Shuffle the list
Pick an item
Filter out the items matching the pick (in this case, those with the same number)
You have the shuffle routine covered, so that just leaves the pick and the filter.
For the pick, I just used Math.random to pull a random member of the list:
return array[Math.floor(Math.random() * array.length)];
For the filter, I used Array.prototype.filter to pull out the desired items. In this case, with the strings, I parse the number out of the string and then remove any items in the array that have the same number as the last pick:
return array.filter(el => +el.match(/\d+/).join() != +picked.match(/\d+/).join());
But with actual images, you'll just replace that with however you read the labels of your images.
Example
Here's the full working example, with the list of picks first, followed by a sorted array of the picks showing they were all used.
var imageList = ['BF1', 'BA1', 'BF2', 'BA2', 'BF3', 'BA3', 'BA4', 'BF4', 'BA5', 'BF5', 'BF6', 'BA6', 'BF7', 'BA7', 'BA8', 'BF8', 'BA9', 'BF9', 'BF10', 'BA10', 'BA11', 'BF11', 'BA12', 'BF12', 'BA13', 'BF13', 'BA14', 'BF14', 'BA15', 'BF15', 'BA16', 'BF16', 'BA17', 'BF17', 'BA18', 'BF18', 'BA19', 'BF19', 'BA20', 'BF20', 'BA21', 'BF21', 'BF22', 'BA23', 'BF23', 'BA24', 'BF24', 'BA25', 'BF25', 'BA26', 'BF26', 'BA27', 'BF27', 'BA28', 'BF28', 'BA29', 'BF29', 'BA30', 'BF30', 'BA31', 'BF31', 'BA32', 'BF33', 'BA33', 'BA34', 'BF35', 'BA35', 'BA36', 'BF36'];
var selection = imageList.slice();
var picked = [];
function shuffle(array) {
var counter = array.length, temp, index;
while (counter > 0) {
index = Math.floor(Math.random() * counter);
counter = counter - 1;
temp = array[counter];
array[counter] = array[index];
array[index] = temp;
}
return array;
}
function pick(array) {
return array[Math.floor(Math.random() * array.length)];
}
function filterPicked(picked, array) {
return array.filter(el => +el.match(/\d+/).join() != +picked.match(/\d+/).join());
}
while (selection.length) {
// 1. Shuffle
shuffle(selection);
// 2. Pick
picked.push(pick(selection));
// 3. Filter
selection = filterPicked(picked[picked.length-1], selection);
}
console.log(`Picks: [${picked.join(', ')}]`);
console.log(`Sorted picks: [${picked.sort((a, b) => +a.match(/\d+/).join() - +b.match(/\d+/).join()).join(', ')}]`);
Step-by-step
Shuffle the selection array (a copy of the full array or all selections)
Pick an item off the selection array, push it onto the array of picks
Filter the selection array to remove items matching the last pick
Repeat 1-3 with each newly filtered array, until no selections remain
You can shuffle array with loop and random numbers, then in another loop extract first image in resulting array, filter array with numbers at the end of string
var myArray="BF1, BA1, BF2, BA2, BF3, BA3, BA4, BF4, BA5, BF5, BF6, BA6, BF7, BA7, BA8, BF8, BA9, BF9, BF10, BA10, BA11, BF11, BA12, BF12, BA13, BF13, BA14, BF14, BA15, BF15, BA16, BF16, BA17, BF17, BA18, BF18, BA19, BF19, BA20, BF20, BA21, BF21, BF22, BA23, BF23, BA24, BF24, BA25, BF25, BA26, BF26, BA27, BF27, BA28, BF28, BA29, BF29, BA30, BF30, BA31, BF31, BA32, BF33, BA33, BA34, BF35, BA35, BA36, BF36";
function shuffle(a) {
for (let i = a.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[a[i], a[j]] = [a[j], a[i]];
}
return a;
}
const arr = shuffle(myArray.split(','));
function draw(a, times) {
let res =[]
for (let i = 1; i <= times; i++) {
let str = a[0]
res.push(str)
a = a.filter(a => parseInt(a.match(/\d+$/)[0], 10) !== parseInt(str.match(/\d+$/)[0], 10))
}
return res
}
console.log(draw(arr, 4))
I am new to Javascript and working with the basics. I am wanting to create an array whose individual elements are randomly drawn, one at a time, with a click of a button, until all array elements are displayed on the screen. The code I have is almost there. But the issue is that when it runs, it always grabs 2 elements on the first button click, rather than 1. It runs well for the remaining elements. Sure would appreciate some insight to this problem. Thank you.
var myArray=['1','2','3','4','5','6','7']
var text = "";
var i;
function RandomDraw() {
for(i = 0; i < myArray.length; i+=text) {
var ri = Math.floor(Math.random() * myArray.length);
var rs = myArray.splice(ri, 1);
document.getElementById("showSplice").innerHTML = text+=rs;
//document.getElementById("showArrayList").innerHTML = myArray;
}
}
It "always" draws 2 elements because of the i+=text. Your array is small thus the loop needs 2 iteration (of cocatinating the strings to get the number i) to go over myArray.length.
First iteration:
i = 0 => 0 < myArray.length => true
prints number
Second iteration: (say '4' get choosen)
i = i + text and text = '4' => i = "04" => "04" < myArray.length => true
prints number
Third iteration: (say '3' get choosen)
i = i + text and text = '43' => i = "0443" => "0443" < myArray.length => false
loop breaks
So there is a possibility that two elements get printed. Depending on the length of the array, there could be more.
You don't need the loop, just choose a number and print it:
function RandomDraw() {
if(myArray.length > 0) { // if there still elements in the array
var ri = Math.floor(Math.random() * myArray.length); // do your job ...
var rs = myArray.splice(ri, 1);
document.getElementById("showSplice").textContent = rs; // .textContent is better
}
else {
// print a message indicating that the array is now empty
}
}
Another solution is to shuffle the array and then, on each click, pop the element from the shuffled array.
function shuffle(array) {
return array.sort(function() { return Math.random() - 0.5; });
}
var button = document.getElementById('button');
var origin = ['1','2','3','4','5','6','7'];
var myArray = shuffle(origin);
var currentValue = null;
button.onclick = function() {
currentValue = myArray.pop();
if(!!currentValue) {
console.log(currentValue);
}
}
<button id='button'>
get element
</button>
You can shuffle the array again on each click, but I think it is not necessary whatsoever...
If you're wondering about Math.random() - 0.5:
[...] Math.random is returning a number between 0 and 1. Therefore, if you call Math.random() - 0.5 there is a 50% chance you will get a negative number and 50% chance you'll get a positive number.
If you run a for loop and add these results in an array, you will effectively get a full distribution of negative and positive numbers.
https://teamtreehouse.com/community/mathrandom05
I would do it this way:
let myArray=['1','2','3','4','5','6','7']
function RandomDraw(){
const selectedIndex = Math.floor(Math.random() * myArray.length);
const selected = myArray[selectedIndex]
myArray = myArray.slice(0, selected).concat(myArray.slice(selected + 1));
return selected;
}
Every time you call RandomDraw it will return a random number, without repeating.
The way I understand it, you want to draw every items from the array after a single click. So the loop is needed.
As others have said, there are several issues in your for loop :
that i+= text makes no sense
you are looping until i reaches the length of your array, but you are splicing that array, hence reducing its length
You could correct your for loop :
function RandomDraw() {
var length = myArray.length;
var ri = 0;
for (var i=0;i<length;i++) {
ri = Math.floor(Math.random() * myArray.length);
console.log("Random index to be drawn : " + ri);
// removing that index from the array :
myArray.splice(ri, 1);
console.log("myArray after a random draw : ", myArray);
}
}
Or, you could use a while loop :
function RandomDraw() {
var ri = 0;
while (myArray.length > 0) {
ri = Math.floor(Math.random() * myArray.length);
console.log("Random index to be drawn : " + ri);
// removing that index from the array :
myArray.splice(ri, 1);
console.log("myArray after a random draw : ", myArray);
}
}
I'm trying to implement the endless loading of items in Javascript. Like the effect you get when you scroll through your messages in your favorite messaging application. I have a big array like this (+1000 objects):
var array = [
{ id : 1, text: "First"},
{ id : 2, text: "Second"},
{ id : 3, text: "Third"},
{ id : 4, text: "Forth"},
{ id : 5, text: "Fifth"},
{ id : 6, text: "Sixth"},
{ id : 7, text: "Seventh"},
...
];
Now I want to load only 10 items at a time. For example I'm showing only the items with the id of 30 to 39. Now the user wants to see the items before 30. What is the best way to select last 10 items before that object with the id of 30? As mentioned before, the array's size is big so performance does matter here.
EDIT
The example above is just one case. I should be able to filter my array 10 items at a time as many times as needed.
What I'm trying to achieve is loading a big array but not all at once. I want to load 10 items at a time. I'm keeping track of the first object in my filtered array (e.g 30) and then I want to get the last 10 objects before that particular object.
EDIT 2
This is my View:
<div ng-repeat="message in messages" class="message-wrapper">
// Show content of message
</div>
Now initially i'm showing the last 10 items in messages
$scope.init = function(){
var messages = Database.AllMessages(); // This function returns all my messages
$scope.messages = messages.length > 10 ? messages.slice(-10) : messages;
}
Now let's say the items returned by this function are the items with the Id of 30 to 39. The user scrolls up and wants to see the messages prior to 30. So how can i filter the whole array returned by AllMessages() to get 10 last items before the 30 message?
Thanks in advance for any insights.
You want to query your data source as efficient as possible. Make sure the array is sorted by id first:
array.sort(function(a, b) {
if (a.id === b.id) return 0;
if (a.id > b.id) return 1;
return -1;
});
Then, binary search can be performed to find the index of the element you're looking for:
var search = function(arr, val) {
if (arr.length === 0) {
return -1;
}
var max = arr.length - 1, min = 0;
while (max !== min) {
var index = Math.floor((max + min) / 2);
if (arr[index].id === val) {
return index;
}
else if (arr[index].id < val) {
min = index;
}
else {
max = index;
}
}
return arr[index].id === val ? index : -1;
}
// find index of element with desired id.
var index = search(array, 30);
Once we know the index, we simply have to select the elements before/after the index:
var rangeMin = index - 10; // Inclusive. 10 is the maximum number of elements you want to render.
var rangeMax = index; // Not inclusive.
if (rangeMin < 0) { rangeMin = 0; }
if (rangeMax > array.length) { rangeMax = array.length; }
var elementsToRender = array.slice(rangeMin, rangeMax);
elementsToRender will now contain all the elements you want to render.
Now let's say the items returned by this function are the items with the Id of 30 to 39. The user scrolls up and wants to see the messages prior to 30. So how can i filter the whole array returned by AllMessages() to get 10 last items before the 30 message?
(...) my view should show the items 20 to 39
Suppose allMessages contains all messages (very large array).
Suppose ctrl is a controller instance
Suppose ctrl.messages contains the items currently displayed.
Suppose currentIndex is 30
Suppose stepSize is 10
Then the following code will extend messages to contain items 20 to 39 and will set currentIndex to 20:
currentIndex -= stepSize; // the new first message will have index 20
var extraItems = allMessages.slice(currentIndex, currentIndex+stepSize);
Array.prototype.push.apply(extraItems, ctrl.messages); // append ctrl.messages to extraItems
ctrl.messages = extraItems;
For more information about Array.prototype.push.apply, see Mozilla (scroll down to 'Merging two arrays').
Here is small app to demonstrate it (you'll have to add logic to protect the user to cross the array boundaries):
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.2/angular.min.js"></script>
<script>
var myApp = angular.module('myApp', []);
myApp.controller('MyController', function() {
var ctrl = this;
var allMessages = Database.AllMessages();
var stepSize = 10;
var currentIndex = allMessages.length - stepSize;
// show the last 10 messages
ctrl.messages = allMessages.length > 10 ? allMessages.slice(-10) : allMessages;
// show ten more messages
ctrl.showMore = function () {
currentIndex -= stepSize;
var extraItems = allMessages.slice(currentIndex, currentIndex+stepSize);
Array.prototype.push.apply(extraItems, ctrl.messages);
ctrl.messages = extraItems;
};
});
</script>
</head>
<body ng-app="myApp">
<div ng-controller="MyController as ctrl">
<table>
<tr ng-repeat="message in ctrl.messages"><td>{{message.text}}</td></tr>
</table>
<button ng-click="ctrl.showMore();">show more</button>
</div>
</body>
</html>
You can try this to get the object with id 30 without a loop using the following code:
var array = [
{ id : 1, text: "First"},
{ id : 2, text: "Second"},
{ id : 3, text: "Third"},
{ id : 4, text: "Forth"},
{ id : 5, text: "Fifth"},
{ id : 6, text: "Sixth"},
{ id : 7, text: "Seventh"}
];
var result = $.grep(array, function(e){ return e.id == 5; });
console.log(result);
What is the best way to select last 10 items before that object with the id of 30?
var index = 30;
var count = 10;
// 10 items before 30 :
array.slice(index-count, index);
I knew this thing I made could be helpful to somebody. Here's the relevant piece:
$('button.getResult').on('click', function(e) {
e.preventDefault();
var limit = $('input.getResult').val();
if (limit != '' && !isNaN(limit)) {
var dots = $('ul li');
if (dots.length > limit) {
//show the limit as group
var foundSelected = false;
var counter = 0;
for (var i = dots.length - 1; i >= 0; i--) {
dots.eq(i).addClass('group');
dots.eq(i + parseInt(limit)).removeClass('group');
counter++;
if (i == $('li.selected').index()) {
foundSelected = true;
};
if (foundSelected && counter >= limit) {
i = -1;
};
};
} else {
//show all as group
dots.addClass('group');
};
};
return false;
});
dots is basically your array and the first conditional is just checking to make sure the number of results you want is smaller than the length of the array. Then a loop is performed through the array (backwards in my case) checking for the important item. If I find it, I change the boolean to true.
Additionally, I mark each as selected as I iterate and if the selected index is outside of the limit of pagination, then I remove the selected mark (to keep the pagination). Once I've both found the important item and have marked the correct number of items past the important one, I stop the loop.
Of course, you might not be able to mark things in your case but I figure you could look at this for some inspiration.