How to render elements based on a given value in React JS? - javascript

I want to render some stars based on a specific value, and this is what I have done so far
const Rating = ({ value }) => {
const renderStars = () => {
let stars = []; // This array should contain all stars either full, half or empty star
for (let i = 0; i < value; i++) {
if (value % 1 !== 0) {
// If the value has decimal number
} else {
// If the value has NO decimal number
}
}
return stars?.map((star) => star); // Mapping the stars array
};
return <div className="rating">{renderStars()}</div>;
};
export default Rating;
Now I have 3 icons: a full star, a half star, and an empty star. Let's say the rating value is 3.5, so what I want is to push to the stars array 3 full stars 1 half star and 1 empty star so that will be 5 stars in total. And then I can map through the array and render all the stars.

You can loop through up until your value as you're currently doing, where for each iteration you push a full star, and then after the loop is complete, check if value is a decimal to determine if you should push an additional half star:
const STAR_COUNT = 5;
const Rating = ({ value }) => {
const stars = Array.from({length: STAR_COUNT}, () => <EmptyStar />);
let i;
for (i = 0; i < value; i++) { // this will loop Math.floor(value) times
stars[i] = <FullStar />;
}
if (value % 1 != 0) // if value is a decimal, add a half star
stars[i-1] = <HalfStar />;
return <div className="rating">{stars}</div>;
};
I would also suggest wrapping this component in a call to React.memo() so that the for loop logic only runs when your value prop changes, and not what the parent rerenders.
Another, perhaps more concise way, is to use some array methods to help, such as .fill() to populate an array firstly with empty stars, then replace those empty stars up to a given index based on your value, and finally add a half star if required:
const STAR_COUNT = 5;
const Rating = ({ value }) => {
const stars = Array(STAR_COUNT).fill(<EmptyStar />).fill(<FullStar />, 0, Math.floor(value));
if (value % 1 != 0) // if value is a decimal, add a half star
stars[Math.floor(value)] = <HalfStar />;
return <div className="rating">{stars}</div>;
};

Try this in your code
let rating = 3.5;
let ratingCount = 0
for(let i=1; i<=5; i++){
if(i<=rating){
console.log("full")
ratingCount++
}
}
if(rating-Math.floor(rating) !== 0){
console.log("Half")
ratingCount++
}
for(let i=ratingCount ; i<5; i++){
console.log("empty")
}

Related

Fill a 6x6 grid with 6 colors without same colors touching each other

I'm trying to create a board game with p5.js (Javascript)
To set up the game board which is a 6 by 6 grid, I have to fill the grid with 6 colors in a way that no horizontal or vertical touching cells have the same color. And all 6 colors have to be used in 6 cells.
But now I'm struggling a bit creating an algorithm that places the colors randomly but keeping the rules.
I tried to start at the top left corner, filling with a random color.
Then I start to fill the cell to the left and the bottom with a different color.
The problem is, that when the script wants to fill the last few cells, there are no colors left to use (either already 6 cells filled or a remaining color is a neighbor)
Example:
Still two cells need to be red, but only one place is left for red (under white):
//fill placedColors Array with zeros
placedColors = [];
for(let i=0; i<6; i++) {
placedColors[i] = 0;
}
//fill allIndexes Array with indizies to keep control of visited cells
let allIndexes = [];
for(let i=0; i<36; i++) {
allIndexes.push(i);
}
//build board
//when I set the limit to 36 the script runs forever because no solution is found
for(let i=0; i<33; i++) {
fillCells(i);
}
function fillCells(index) {
//get top and left color
let topColor = false;
//index is in the second row
if(index >= 6) {
topColor = cells[index-6].color;
}
let leftColor = false;
//index is not in the first column
if(index % 6 > 0 && index > 0) {
leftColor = cells[index-1].color;
}
if(allIndexes.indexOf(index) > -1) {
cells.push(new Cell(index, pickColor(topColor, leftColor)));
}
//mark index as visited
var allIndexesIndex = allIndexes.indexOf(index);
if (allIndexesIndex !== -1) {
allIndexes.splice(allIndexesIndex, 1);
}
}
function pickColor(invalidColor1,invalidColor2) {
let colorFound = false;
do {
randColor = floor(random(6));
if(placedColors[randColor] < 6 && randColor!=invalidColor1 && randColor!=invalidColor2) {
placedColors[randColor]++;
colorFound = true;
}
} while(!colorFound);
return randColor;
}
One way to look at this would be as searching for a path through a tree where each node has 6 possible children for the six colours which could come next. Ignoring all the constraints initially, you pick one of these at random 36 times, and have your order of placements.
Using a recursive function (which will be useful in a moment), an unconstrained search would look like this:
function randomChoice(list) {
return list[Math.floor(Math.random() * list.length)];
}
function placeNext(sequenceSoFar) {
const availableColours = [0,1,2,3,4,5];
let chosenColour = randomChoice(availableColours);
sequenceSoFar = [...sequenceSoFar, colourToAdd];
if ( sequenceSoFar.length == 36 ) {
// Completed sequence!
return sequenceSoFar;
}
else {
// Recurse to make next choice
return placeNext(sequenceSoFar);
}
}
// Start with an empty array
let sequence = placeNext([]);
console.log(sequence);
Next, we need to eliminate choices that would violate the constraints of the problem:
The cell to the left isn't the same colour, i.e. chosenColour !== sequenceSoFar[nextIndex-1]
The cell above isn't the same colour, i.e. chosenColour !== sequenceSoFar[nextIndex-6]
The colour doesn't already occur six times in the sequence, i.e. sequenceSoFar.filter((element) => element === chosenColour).length < 6
If the chosen colour doesn't meet these requirements, we remove it from the list of candidates and try again:
function randomChoice(list) {
return list[Math.floor(Math.random() * list.length)];
}
function newColourIsValid(sequenceSoFar, chosenColour) {
// We haven't added the next colour yet, but know where it *will* be
let nextIndex = sequenceSoFar.length;
return (
// The cell to the left isn't the same colour
( nextIndex < 1 || chosenColour !== sequenceSoFar[nextIndex-1] )
&&
// The cell above isn't the same colour
( nextIndex < 6 || chosenColour !== sequenceSoFar[nextIndex-6] )
&&
// The colour doesn't already occur six times in the sequence
sequenceSoFar.filter((element) => element === chosenColour).length < 6
);
}
function placeNext(sequenceSoFar) {
let availableColours = [0,1,2,3,4,5];
let chosenColour;
do {
// If we run out of possible colours, then ... panic?
if ( availableColours.length === 0 ) {
console.log(sequenceSoFar);
throw 'No sequence possible from here!';
}
chosenColour = randomChoice(availableColours);
// Eliminate the colour from the list of choices for next iteration
availableColours = availableColours.filter((element) => element !== chosenColour);
} while ( ! newColourIsValid(sequenceSoFar, chosenColour) );
sequenceSoFar = [...sequenceSoFar, colourToAdd];
if ( sequenceSoFar.length == 36 ) {
// Completed sequence!
return sequenceSoFar;
}
else {
// Recurse to make next choice
return placeNext(sequenceSoFar);
}
}
// Start with an empty array
let sequence = placeNext([]);
console.log(sequence);
So far, this has the same problem as your original code - if it hits a dead end, it doesn't know what to do. To solve this, we can use a backtracking algorithm. The idea is that if we run out of candidates for position n, we reject the choice at position n-1 and try a different one.
Instead of placeNext, we need our function to be tryPlaceNext, which can return false if the sequence hits a dead end:
function randomChoice(list) {
return list[Math.floor(Math.random() * list.length)];
}
function newColourIsValid(sequenceSoFar, chosenColour) {
// We haven't added the next colour yet, but know where it *will* be
let nextIndex = sequenceSoFar.length;
return (
// The cell to the left isn't the same colour
( nextIndex < 1 || chosenColour !== sequenceSoFar[nextIndex-1] )
&&
// The cell above isn't the same colour
( nextIndex < 6 || chosenColour !== sequenceSoFar[nextIndex-6] )
&&
// The colour doesn't already occur six times in the sequence
sequenceSoFar.filter((element) => element === chosenColour).length < 6
);
}
function tryPlaceNext(sequenceSoFar, colourToAdd) {
let availableColours = [0,1,2,3,4,5];
if ( ! newColourIsValid(sequenceSoFar, colourToAdd) ) {
// Invalid choice, indicate to caller to try again
return false;
}
// Valid choice, add to sequence, and find the next
sequenceSoFar = [...sequenceSoFar, colourToAdd];
if ( sequenceSoFar.length === 36 ) {
// Completed sequence!
return sequenceSoFar;
}
while ( availableColours.length > 0 ) {
// Otherwise, pick one and see if we can continue the sequence with it
let chosenColour = randomChoice(availableColours);
// Recurse to make next choice
let continuedSequence = tryPlaceNext(sequenceSoFar, chosenColour);
if ( continuedSequence !== false ) {
// Recursive call found a valid sequence, return it
return continuedSequence;
}
// Eliminate the colour from the list of choices for next iteration
availableColours = availableColours.filter((element) => element !== chosenColour);
}
// If we get here, we ran out of possible colours, so indicate a dead end to caller
return false;
}
// Root function to start an array with any first element
function generateSequence() {
let availableColours = [0,1,2,3,4,5];
let chosenColour = randomChoice(availableColours);
return tryPlaceNext([], chosenColour);
}
let sequence = generateSequence();
console.log(sequence);
Thanks for your suggestions! I tried to find an own solution parallel to the posted one. Now with this code, it works fine:
function buildBoard() {
cells = [];
for(let i=0; i<gameSize; i++) {
placedColors[i] = 0;
}
for(var i=0; i<gameSize*gameSize; i++) {
cells.push(new Cell(i, pickColor()));
}
do {
invalidFields = [];
findInvalidFields();
if(invalidFields.length > 0) {
let cell1index = Math.floor(Math.random() * invalidFields.length);
cell1 = invalidFields[cell1index];
//check, if cell in different color available
let otherColorAvailable = false;
for(cell of invalidFields) {
if(cell.color != cell1.color) {
otherColorAvailable = true;
break;
}
}
if(otherColorAvailable) {
//pick invalid cell
do {
let cell2index = Math.floor(Math.random() * invalidFields.length);
cell2 = invalidFields[cell2index];
} while (cell2.color == cell1.color)
} else {
//pick random cell
do {
let cell2index = Math.floor(Math.random() * cells.length);
cell2 = cells[cell2index];
} while (cell2.color == cell1.color)
}
//switch colors of cells
let tempColor = cell1.color;
cell1.color = cell2.color;
cell2.color = tempColor;
}
} while (!checkStartField());
}
function findInvalidFields() {
for(let index=0; index<cells.length; index++) {
let thisColor = cells[index].color;
//right
if(index%gameSize < gameSize-1 && cells[index+1].color == thisColor) {
if(invalidFields.indexOf(cells[index+1])) {
invalidFields.push(cells[index+1]);
}
}
//bottom
if(index < gameSize*gameSize-gameSize && cells[index+gameSize].color == thisColor) {
if(invalidFields.indexOf(cells[index+gameSize])) {
invalidFields.push(cells[index+gameSize]);
}
}
}
}
function checkStartField() {
for(let index=0; index<cells.length; index++) {
let thisColor = cells[index].color;
//top
if(index >= gameSize && cells[index-gameSize].color == thisColor) {
//console.log(index+'top');
return false;
}
//right
if(index%gameSize < gameSize-1 && cells[index+1].color == thisColor) {
//console.log(index+'right');
return false;
}
//bottom
if(index < gameSize*gameSize-gameSize && cells[index+gameSize].color == thisColor) {
//console.log(index+'bottom');
return false;
}
//left
if(index%gameSize > 0 && cells[index-1].color == thisColor) {
//console.log(index+'left');
return false;
}
}
return true;
}
An easy approach is to start with a valid coloring (for example, any 6x6 latin square is a valid coloring) and them mix it up by finding a pair of things that can be swapped, and swap them.
An improvement (to increase mixing speed, and to prevent the algorithm getting stuck) is to allow at most one cell to be invalid (ie: one cell which if removed leaves the remainder in a valid coloring). If there's no invalid cell, then swap two random cells if at least one of them will be valid after the swap. And if there's one invalid cell, pick that cell and one other random cell to be swapped, assuming again swapping leaves at least one of them valid. Again repeat lots of time, stopping only when the coloring is valid.
An implementation of this idea (sorry, not Javascript) is here: https://go.dev/play/p/sxMvLxHfhmC

Design a layout sorting algorithm

I'm trying to come up with an algorithm to make sure my items fit into the screen. These items are randomly generated and have a score of either 1, 2, or 4. This score lets met know how much space they take up on the screen. Score 4 means it takes up 4x the size of a score 1 item. It's important to note that an item with a score of 1 always needs a 'buddy', so they take up the width of the page.
I want to fit up to a score of 6 on my page, to an infinite amount of pages.
A valid result could be [ [1, 1, 2, 2], [4, 2] ]. Each array in the root array is its own page, so this would mean I have 2 pages that are properly filled since their child scores count up to 6.
Result:
This is what I came up with, but it's not so bulletproof. I'm not so good at writing these kinds of algorithms. Perhaps someone could help me in the right direction?
It's okay to skip items like I'm doing, but I'm also skipping items that might find a match later. Currently, all skipped items will be added to the front of items before sortItems is called again. So they will just be sorted with the next batch of items to find a new match.
const getItemScore = (size) => {
switch (size) {
case 'small':
return 1;
case 'medium':
return 2;
case 'large':
return 4;
default:
console.error('Unknown size:', size);
}
}
const sortItems = (items) => {
const maxPageScore = 6;
let sorted = [];
let perPage = [];
let smallSort = [];
let skippedItems = [];
let currentPageScore = 0;
items.forEach((item) => {
const itemScore = getItemScore(item.size);
if (currentPageScore + itemScore < maxPageScore) {
// Item fits in page
if (item.size === 'small') {
// A small item needs to have a 'buddy'
if (smallSort.length < 2) {
// the small item can be added
smallSort.push(item);
}
if (smallSort.length === 2) {
// we have found a buddy for the item
smallSort.forEach((smallItem) => {
perPage.push(smallItem);
});
// reset smallSort
smallSort = [];
// increment pageScore
currentPageScore += 2;
}
}
else if (item.size === 'medium' || item.size === 'large') {
// medium and large can always be added if their score isnt more than maxPageScore
perPage.push(item);
currentPageScore += itemScore;
}
}
else if (currentPageScore + itemScore === maxPageScore) {
// we have a full page
perPage.push(item);
sorted.push(perPage);
// reset values
perPage = [];
smallSort = [];
currentPageScore = 0;
}
else {
// item doesnt fit in page
skippedItems.push(item);
}
});
// done looping over items
const result = {
sorted: sorted,
skipped: skippedItems,
};
return result;
}
The intuition would tell us to come up with a recurrence-relation so we can reduce the problems into smaller-subproblems.
Lets say we are calculating for size = N
The top-section could either be:
2 blocks of 1: [1,1] ; which leaves us with size = N - 1
1 block of 2: 2 ; which leaves us with size = N - 1
1 block of 4: 4 ; which leaves us with size = N - 2
Say, S(n) = #ways to arrange these blocks, then:
S(n) = S(n-1) + S(n-1) + S(n-2)
^from-(1) ^from-(2) ^from-(3)
which becomes:
S(n) = 2S(n-1) + S(n-2)
with boundary conditions:
S(0) = 0
S(1) = 2
This makes it quite easy to solve:
def get_pattern_combs(N):
if N < 1:
return 0
if N == 1:
return 2
lval = 0
val = 2
for i in range(2, N+1):
new_val = 2*val + lval
lval = val
val = new_val
return val

Get pseudo-random item with given probability

I want to give the user a prize when he signs in;
but it needs to be there some rare prizes so I want to appear prizes with different chances to appear using percents
i want to display one of these
[50 : 'flower'], [30 : 'book'], [20 : 'mobile'];
using percents they have
if there any way using Node.js or just javascript functions it would be great
You can create a function to get weighted random results, something like this:
const weightedSample = (items) => {
// cache if necessary; in Chrome, seems to make little difference
const total = Object.values(items).reduce((sum, weight) => sum + weight, 0)
const rnd = Math.random() * total
let accumulator = 0
for (const [item, weight] of Object.entries(items)) {
accumulator += weight
if (rnd < accumulator) {
return item
}
}
}
// check frequencies of each result
const prizes = { flower: 50, book: 30, mobile: 20 }
const results = Object.fromEntries(Object.keys(prizes).map(k => [k, 0]))
for (let i = 0; i < 1e6; ++i) {
const prize = weightedSample(prizes)
++results[prize]
}
// sample results: { flower: 500287, book: 299478, mobile: 200235 }
console.log(results)
This will work regardless of whether the weights add up to 100, whether they're integers, and so on.
'Right off the top of my head'-approach would be to prepare an array where each source item occurs the number of times that corresponds to respective probability and pick random item out of that array (assuming probability value has no more than 2 decimal places):
// main function
const getPseudoRandom = items => {
const {min, random} = Math,
commonMultiplier = 100,
itemBox = []
for(item in items){
for(let i = 0; i < items[item]*commonMultiplier; i++){
const randomPosition = 0|random()*itemBox.length
itemBox.splice(randomPosition, 0, item)
}
}
return itemBox[0|random()*itemBox.length]
}
// test of random outcomes distribution
const outcomes = Array(1000)
.fill()
.map(_ => getPseudoRandom({'flower': 0.5, 'book': 0.3, 'mobile': 0.2})),
distribution = outcomes.reduce((acc, item, _, s) =>
(acc[item] = (acc[item]||0)+100/s.length, acc), {})
console.log(distribution)
.as-console-wrapper{min-height:100%;}
While above approach may seem easy to comprehend and deploy, you may consider another one - build up the sort of probability ranges of respective width and have your random value falling into one of those - the wider the range, the greater probability:
const items = {'flower': 0.5, 'book': 0.2, 'mobile': 0.2, '1mUSD': 0.1},
// main function
getPseudoRandom = items => {
let totalWeight = 0,
ranges = [],
rnd = Math.random()
for(const itemName in items){
ranges.push({
itemName,
max: totalWeight += items[itemName]
})
}
return ranges
.find(({max}) => max > rnd*totalWeight)
.itemName
},
// test of random outcomes distribution
outcomes = Array(1000)
.fill()
.map(_ => getPseudoRandom(items)),
distribution = outcomes.reduce((acc, item, _, s) =>
(acc[item] = (acc[item]||0)+100/s.length, acc), {})
console.log(distribution)
"Certain probability" and "random" could lead to different approaches!
If you want random each time, something like:
let chances = [[0.2,'mobile'],[0.5,'book'],[1.0,'flower']]
let val = Math.random() // floating number from 0 to 1.0
let result = chances.find( c => c[0] <= val )[1]
This will give a random result each time. It could be possible to get 'mobile' 100 times in a row! Rare, of course, but a good random number generate will let that happen.
But perhaps you want to ensure that, in 100 results, you only hand out 20 mobiles, 30 books, and 50 flowers. Then you might want a "random array" for each user. Pre-fill the all the slots and remove them as they are used. Something like:
// when setting up a new user
let userArray = []
let chances = [[20,'mobile'],[30,'book'],[50,'flower']]
changes.forEach( c => {
for(let i = 0; i < c[0]; i++) userArray.push(c[1])
})
// save userArray, which has exactly 100 values
// then, when picking a random value for a user, find an index in the current length
let index = Math.floor(Math.random() * userArray.length)
let result = userArray[index]
userArray.splice(index,1) // modify and save userArray for next login
if(userArray.length === 0) reinitializeUserArray()
There are different approaches to this, but just some ideas to get you started.

Implementing OneRule algorithmn in javascript [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 2 years ago.
Improve this question
OneR, short for "One Rule", is a simple yet accurate classification algorithm that generates one rule for each predictor in the data, then selects the rule with the smallest total error as its "one rule".
I tried to find code samples on GitHub, but found only one, developed with R language. How could I implement this algorithm in Javascript?
What I have tried?
I am trying to implement following this sample article:
https://www.saedsayad.com/oner.htm
class OneR {
/**
* Pass dataset which will be an array of values.
* Last value is classifcator's value.
* All other values are predictors.
*
* Example
*
* The meaning of sequence values:
* |Outlook|Temp|Humidity|Windy|Play Golf|
*
* Representation of a sequence:
* ['rainy', 'hot', 'high', 0, 0]
*
* True and False are represented as zeros or ones
*/
constructor(data = []) {
this.data = data;
this.frequences = {};
}
predict() {
if (this.data && this.data.length > 0) {
const firstRow = this.data[0];
const predictorCount = firstRow.length - 1;
let classifcator;
// For each predictor,
for (let i = 0; i < predictorCount; i++) {
// For each value of that predictor, make a rule as follos;
for (let y = 0; y < this.data.length; y++) {
// Count how often each value of target (class) appears
classifcator = this.data[y][predictorCount];
console.log(classifcator);
// Find the most frequent class
// Make the rule assign that class to this value of the predictor
}
// Calculate the total error of the rules of each predictor
}
// Choose the predictor with the smallest total error
} else {
console.log("Cannot predict!");
}
}
}
module.exports = {
OneR
};
I have loaded data from csv
rainy,hot,high,0,0
rainy,hot,high,1,0
overcast,hot,high,0,1
sunny,mild,high,0,1
sunny,cool,normal,0,1
sunny,cool,normal,1,0
overcast,cool,normal,1,1
rainy,mild,high,0,0
rainy,cool,normal,0,1
sunny,mild,normal,0,1
rainy,mild,normal,1,1
overcast,mild,high,1,1
overcast,hot,normal,0,1
sunny,mild,high,1,0
If I understand correctly how the frequency tables must be compared (lowest error rate, highest accuracy), you could use Maps so to cope with non-string types if ever necessary.
Although your example has target values that are booleans (0 or 1), in general they could be from a larger domain, like for example "call", "fold", "raise", "check".
Your template code creates a class, but I honestly do not see the benefit of that, since you can practically only do one action on it. Of course, if you have other actions in mind, other than one-rule prediction, then a class could make sense. Here I will just provide a function that takes the data, and returns the number of the selected predictor and the rule table that goes with it:
function oneR(data) {
if (!data && !data.length) return console.log("Cannot predict!");
const predictorCount = data[0].length - 1;
// get unique list of classes (target values):
let classes = [...new Set(data.map(row => row[predictorCount]))];
let bestAccuracy = -1;
let bestFreq, bestPredictor;
// For each predictor,
for (let i = 0; i < predictorCount; i++) {
// create frequency table for this predictor: Map of Map of counts
let freq = new Map(data.map(row => [row[i], new Map(classes.map(targetValue => [targetValue, 0]))]));
// For each value of that predictor, collect the frequencies
for (let row of data) {
// Count how often each value of target (class) appears
let targetValue = row[predictorCount];
let predictorValueFreq = freq.get(row[i]);
let count = predictorValueFreq.get(targetValue);
predictorValueFreq.set(targetValue, count+1);
}
// Find the most frequent class for each predictor value
let accuracy = 0;
for (let [predictorValue, predictorValueFreq] of freq) {
let maxCount = 0;
let chosenTargetValue;
for (let [targetValue, count] of predictorValueFreq) {
if (count > maxCount) {
// Make the rule assign that class to this value of the predictor
maxCount = count;
chosenTargetValue = targetValue;
}
}
freq.set(predictorValue, chosenTargetValue);
accuracy += maxCount;
}
// If this accuracy is best, then retain this frequency table
if (accuracy > bestAccuracy) {
bestAccuracy = accuracy;
bestPredictor = i;
bestFreq = freq;
}
}
// Return the best frequency table and the predictor for which it applies
return {
predictor: bestPredictor, // zero-based column number
rule: [...bestFreq.entries()]
}
}
let data = [
["rainy","hot","high",0,0],
["rainy","hot","high",1,0],
["overcast","hot","high",0,1],
["sunny","mild","high",0,1],
["sunny","cool","normal",0,1],
["sunny","cool","normal",1,0],
["overcast","cool","normal",1,1],
["rainy","mild","high",0,0],
["rainy","cool","normal",0,1],
["sunny","mild","normal",0,1],
["rainy","mild","normal",1,1],
["overcast","mild","high",1,1],
["overcast","hot","normal",0,1],
["sunny","mild","high",1,0]
];
let result = oneR(data);
console.log(result);

List array elements, one by one, with a click of a button

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

Categories

Resources