Why is splice not removing elements from my array? - javascript

I am creating a roulette game that displays random items from different arrays when the wheel lands on a specific category. So far everything works except when the wheel lands on a category, it selects the same random item from the correct array over and over again. I am trying to use math.random and the splice method to randomly select an item from an array, and remove that item so only new, random items from the array can be displayed after, but it hasn't worked.

I rearranged the input arrays into an array of arrays (5 arrays of 9 elements = 45). I'm guessing that you want the whole thing shuffled.
const log = data => console.log(JSON.stringify(data));
let zones = [
["🎁", "🎆", "🎯", "🌈", "🌛", "💩", "💰", "🍒", "🌴"],
["🌞", "🍀", "💀", "💘", "💣", "🎲", "🎰", "🎈", "🗿"],
["🎁", "🎆", "🎯", "🌈", "🌛", "💩", "💰", "🍒", "🌴"],
["🌞", "🍀", "💀", "💘", "💣", "🎲", "🎰", "🎈", "🗿"],
["🎁", "🎆", "🎯", "🌈", "🌛", "💩", "💰", "🍒", "🌴"]
];
let zoneSize = zones.length * zones[0].length;
let symbolZones = [];
for (let i = zoneSize; i > 0; i--) {
let deg = zones.flatMap(z => z.splice(Math.floor(Math.random() * z.length), 1));
if(deg.length > 0) {
symbolZones.push(deg);
}
}
log(`Original Array zones`);
log(zones);
log(`New Array symbolZones`);
log(symbolZones);
.as-console-row-code {
display: block;
width: 100%;
overflow-wrap: anywhere;
}
.as-console-row::after {
content: ''!important
}

I don't know how the rest of the code is, but if you always get the same value over and over again it could be that you're not re-running the code that gets the values.
Try wrapping the logic for retrieving the symbolZones in a function:
function getSymbolZones() {
const symbolZones = [];
symbolZones[1] = a[Math.floor(Math.random()*a.length)];
symbolZones[2] = b[Math.floor(Math.random()*b.length)];
symbolZones[3] = c[Math.floor(Math.random()*c.length)];
symbolZones[4] = d[Math.floor(Math.random()*d.length)];
symbolZones[5] = e[Math.floor(Math.random()*e.length)];
symbolZones[6] = f[Math.floor(Math.random()*f.length)];
symbolZones[7] = g[Math.floor(Math.random()*g.length)];
symbolZones[8] = h[Math.floor(Math.random()*h.length)];
return symbolZones;
}
and then use it in the handleWin function.
const handleWin = (actualDeg) => {
const symbolZones = getSymbolZones();
const winningSymbolNr = Math.ceil(actualDeg / zoneSize);
display.innerHTML = symbolZones[winningSymbolNr];
}
P.S.
I understand that you start the array at index 1 because that's the smallest zone you can get from the operation 45 / 45.
But if I were you I'd start the index from 0 and just subtract 1 when accessing the array: symbolZones[winningSymbolNr - 1].
This way you don't get weird bugs when trying to loop through an array that has an empty first index.

Other than using array, I would suggest you use object and a for loop to make the code easier to execute. This code should work:
let deg = 0;
// The 360 degree wheel has 8 zones, so each zone is 45 degrees
let zoneSize = 45;
let symbol = {
a: ["a", "b", "c", "d", "e"],
b: ["f", "g", "h", "i", "j"],
c: ["a", "b", "c", "d", "e"],
d: ["f", "g", "h", "i", "j"],
e: ["a", "b", "c", "d", "e"],
f: ["f", "g", "h", "i", "j"],
g: ["a", "b", "c", "d", "e"],
h: ["f", "g", "h", "i", "j"],
}
// zones for each 8 categories
let ran = Math.floor(Math.random() * Object.keys(symbol).length)
console.log(Object.values(symbol)[ran])
//Random select a zone from above 8
let selectedzone = Object.values(symbol)[ran]
//Random select an item from selected zone
let index = Math.floor(Math.random() * selectedzone.length)
let symbolZones = selectedzone[index]
console.log(symbolZones)
const handleWin = (actualDeg) => {
const winningSymbolNr = Math.ceil(actualDeg / zoneSize);
display.innerHTML = symbolZones[winningSymbolNr];
}

Related

Converting nested loops into forEach();

Im trying to learn forEach() method but i cant find more advanced examples. So i thought about refactoring my Codewars code to learn from it. I dont know know to properly use forEach method in nested loops. Hope You can help me learn from this example :)
6 kyu - Replace With Alphabet Position
https://www.codewars.com/kata/546f922b54af40e1e90001da/train/javascript
function alphabetPosition(text) {
let textToArray = text.replace(/[^a-zA-Z]/gi,'').toUpperCase().split(''); //Eliminate anything thats not a letter
const alphabet = ["A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"];
let pointsHolder = []; //empty array for score
for (let i = 0; i < textToArray.length; i++){
for (let j = 0; j < alphabet.length; j++) {
if (textToArray[i] == alphabet[j] ) { //We check the index of given string letter in alphabet
pointsHolder.push(j+1) //give it a score based on place in alphabet(+1 for 0 as 1st index)
}
}
}
return pointsHolder.join(' '); //return scored array as a string with spaces
}
There is really no need to use a nested loop, which is computationally expensive. With that, you also don't have to manually create an A-Z array.
You can easily convert alphabets to any arbitrary number using String.charCodeAt(). a has a character code of 97, b has a character code of 98, and etc... to get a one-based index (a=1, b=2, ...) you jus t need to subtract 96 from the number.
function alphabetPosition(text) {
const alphabets = text.toLowerCase().replace(/[^a-z]/g, '').split('');
return alphabets.map(alphabet => alphabet.charCodeAt(0) - 96).join(' ');
}
Alternatively you can also use a for...of loop, but that requires storing the array in yet another variable before returning it:
function alphabetPosition(text) {
const alphabets = text.toLowerCase().replace(/[^a-z]/g, '');
const codes = [];
for (const alphabet of alphabets) {
codes.push(alphabet.charCodeAt() - 96);
}
return codes.join(' ');
}
(Note: #Terry's solution is still the more efficient solution to your code challenge)
You can replace it in the following way:
function alphabetPosition(text) {
let textToArray = text.replace(/[^a-zA-Z]/gi, '').toUpperCase().split('');
const alphabet = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"];
let pointsHolder = [];
textToArray.forEach(t2a => {
alphabet.forEach((a, j) => {
if (t2a == a) { pointsHolder.push(j + 1) }
})
})
return pointsHolder.join(' ');
}
console.log(alphabetPosition("ABCSTU"))
An alternative to the charCode solution proposed in Terry's answer but which also avoids nested loops is be to create a Map of the characters you want to score against and then access it per character from the passed string.
Keep in mind that strings are iterable without needing to convert to an array.
function alphabetPosition(text) {
text = text.toUpperCase().replace(/[^A-Z]/gi, '');
const alphabet = new Map(
["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"]
.map((v, i) => [v, i + 1])
);
const pointsHolder = [];
for (const char of text) {
pointsHolder.push(alphabet.get(char))
}
return pointsHolder.join(' ');
}
console.log(alphabetPosition("AB😬CS🐹TU"))
This also allows you to use a Map which doesn't necessarily have consecutive charCodes
function alphabetPosition(text) {
text = text.toUpperCase().replace(/[^😬🐹🤓]/gi, '');
const alphabet = new Map(
["😬", "🐹", "🤓"]
.map((v, i) => [v, i + 1])
);
const pointsHolder = [];
for (const char of text) {
pointsHolder.push(alphabet.get(char))
}
return pointsHolder.join(' ');
}
console.log(alphabetPosition("AB😬CS🐹TU"))

My replace function works with array but not with HTML collection which is also an array. Why?

I am writting a function to replace the position of some HTML elements in the page. Should be simple and it goes like this :
let square = document.getElementById("SquareCharts").children;
let testArray = ["A", "b", "c", "d", "E", "f", "g"];
function Replace(arr, oldPosition, newPosition)
{
let store;
store = arr[newPosition];
arr[newPosition] = arr[oldPosition];
arr[oldPosition] = store;
return console.log(arr);
}
replace(testArray, 4, 0);
replace(square, 4, 0);
It works with testArray but it doesn't seem to have any effect on the HTML elements order. Why and what can I do to change the original DOM?
You need to clear the element's current children then append them again.
let square = document.getElementById("SquareCharts").children;
let testArray = ["A", "b", "c", "d", "E", "f", "g"];
function Replace(arr, oldPosition, newPosition)
{
let store;
store = arr[newPosition];
arr[newPosition] = arr[oldPosition];
arr[oldPosition] = store;
// clear children
square.innerHTML = '';
for(const element of arr) {
square.append(element);
}
return console.log(arr);
}
replace(testArray, 4, 0);
replace(square, 4, 0);
document.getElementById("SquareCharts").children returns a HTMLCollection. Although it is iterable using a for-loop, it is not an Array.
You can also do:
let square = Array.from(document.getElementById("SquareCharts").children);
so that you can get more functionality with Array's built-in methods.

Checking values of one array against another in JS

I'm trying to do a check that the first array contains the same values of the second array.
However I'm confused about my code.
First question is: why is my code running my else statement if all letters in the first array are contained in the second? it will run 2 lines of "this is not valid"
Second question is: if my first array contains a duplicate letter it will still pass the check e.g
["a", "b" , "a", "d", "e", "f"]; even though there is two a's in the first it will see the same "a" again. Anyone know a way around this.
Sorry for my long winded questions but I hope it makes sense. Thanks :)
var letters = ["a", "b" , "c", "d", "e", "f"];
var otherLetters = ["a","b", "c" , "d", "e", "f"];
var i = -1;
while(i<=letters.length){
i++;
if(otherLetters.includes(letters[i])){
console.log("This is valid");
}
else
console.log("This is not valid");
}
You didn't close the brackets. And your loop is very confusing, please use foreach. Here is a working example:
const letters = ["a", "b" , "c", "d", "e", "f"];
const otherLetters = ["a","b", "c" , "d", "e", "f"];
letters.forEach(el => {
if (otherLetters.includes(el)) {
console.log(el + 'is valid');
} else {
console.log(el + 'is not valid');
}
});
You are trying to access array elements which are out of bounds. The script runs 8 iterations over an array with 6 elements.
Nothing to worry, cpog90.
Try this solution.
var letters = ["a", "b" , "c", "d", "e", "f"];
var otherLetters = ["a","b", "c" , "d", "e", "f"];
var i = 0;
while(i<letters.length){
if(otherLetters.includes(letters[i])){
console.log("This is valid");
}
else {
console.log("This is not valid "+i);
}
i++;
}
What went wrong in your logic?
If you declare i = -1 and while(i<=letters.length), As 6 is length of letters, 8 iterations will be done as follows.
For first iteration (i = -1), 'while' condition returns true and checks for 'a'
output: This is valid
For second iteration (i = 0), 'while' condition returns true and checks for 'b'
output: This is valid
For third iteration (i = 1), 'while' condition returns true and checks for 'c'
output: This is valid
For fourth iteration (i = 2), 'while' condition returns true and checks for 'd'
output: This is valid
For fifth iteration (i = 3), 'while' condition returns true and checks for 'e'
output: This is valid
For sixth iteration (i = 4), 'while' condition returns true and checks for 'f'
output: This is valid
For seventh iteration (i = 5), 'while' condition returns true and checks for undefined value.
output: This is not valid
For eighth iteration (i = 6), 'while' condition returns true and checks for undefined value.
output: This is not valid
First of all you have set i = -1 which is confusing since array start position is 0.
The reason your loop is running two extra times is because loop started at -1 instead of 0 and next the condition i <= length.
Since [array length = last index + 1] your loop runs extra two times.
Just to make your code work assign var i = 0 and while condition i < letters.length
Simplest solution is using lodash. It has all optimizations out-of-the-box:
var letters = ["a", "b" , "c", "d", "e", "f"];
var otherLetters = ["f", "a","b", "c" , "d", "e"];
const finalLetters = _.sortBy(letters);
const finalOtherLetters = _.sortBy(otherLetters);
if (_.isEqual(finalLetters, finalOtherLetters)) {
console.log('Two arrays are equal.');
} else {
console.log('Two arrays are not equal.');
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js"></script>
Arrays are index based and starts from 0. So, the -1 and the less than letters.length check puts the code into out of bounds.
var letters = ["a", "b" , "c", "d", "e", "f"];
var otherLetters = ["a","b", "c" , "d", "e", "f"];
var i = 0;
while(i<letters.length)
{
if(otherLetters.includes(letters[i]))
{
console.log("This is valid");
}
else
{
console.log("This is not valid");
}
i++;
}
You can use a combination of Array.prototype.every with Array.prototype.includes, along with some extra guard clauses.
const areSequenceEqual = (arr1, arr2) => {
if (!arr1 || !arr2) {
return false;
}
if (arr1.length !== arr2.length) {
return false;
}
return arr1.every(x => arr2.includes(x));
};
const letters = ["a", "b", "c", "d", "e", "f"];
const otherLetters = ["a", "b", "c", "d", "e", "f"];
const someOtherLetters = ["a", "b", "c", "d", "e", "f", "g"];
console.log(areSequenceEqual(letters, otherLetters));
console.log(areSequenceEqual(letters, undefined));
console.log(areSequenceEqual(letters, someOtherLetters));

Data structure for ngrams

I have built an ngram model implementation in Javascript, which works fine. However, I am looking to change my data structure so that I do not have to iterate through all the history each time a new word/character is observed.
Here, I take a seedtext and use it to build ngrams with an order 2.
var ngrams = {};
var order = 2;
var seedtext = "adadwsdawdsadawdsadadasdwdadaaasdsadsdadwdasdasd";
build();
function build(){
for (var i = 0; i < seedtext.length - order; i++) {
var gram = seedtext.substring(i, i + order);
var next = seedtext.charAt(i + order);
if (!ngrams.hasOwnProperty(gram)) {
ngrams[gram] = [];
}
ngrams[gram].push(next);
}
}
console.log(ngrams);
console.log(ngrams["wd"]);
I am looking to have a data structure that holds a record of each observed pattern (for a given order. Each observed pattern should have a next possible observation and its count.
For example, if you run the below code, an output such as this can be seen:
[object Object] {
aa: ["a", "s"],
ad: ["a", "w", "a", "a", "a", "a", "s", "w"],
as: ["d", "d", "d", "d"],
aw: ["d", "d"],
da: ["d", "w", "w", "d", "s", "d", "a", "d", "s", "s"],
ds: ["a", "a", "a", "d"],
dw: ["s", "d", "d"],
sa: ["d", "d", "d"],
sd: ["a", "w", "s", "a", "a"],
wd: ["s", "s", "a", "a"],
ws: ["d"]
}
["s", "s", "a", "a"]
Now, if we take "ad" for example: ngrams["ad"], we get back ["a", "w", "a", "a", "a", "a", "s", "w"].
Clearly, after ad we can either get a w,a or s.
I'd like to group the letters so that ngrams["ad"] returns something like:
{a: 5
w: 2
s :1}
Note that they are in order so that the most frequently occurring letter is at the top, with its count.
I'd like to be able to access the data like so (for example):
ngrams["ad"].a;
ngrams["ad"].w;
ngrams["ad"].s;
and get back 5 for a, 2 for w and 1 for s.
I also want to be able to increment the values as a previously seen pattern is observed again... I also want to be able to remove patterns.
Any ideas?
Here is a working version. Instead of an array, you add another object to store counts of next characters in it.
var ngrams = {};
var order = 2;
var seedtext = "adadwsdawdsadawdsadadasdwdadaaasdsadsdadwdasdasd";
build();
function build(){
for (var i = 0; i < seedtext.length - order; i++) {
var gram = seedtext.substring(i, i + order);
var next = seedtext.charAt(i + order);
if (!ngrams.hasOwnProperty(gram)) {
ngrams[gram] = {};
}
if (!ngrams[gram].hasOwnProperty(next)) {
ngrams[gram][next] = 0;
}
ngrams[gram][next] += 1;
}
}
console.log(ngrams);
console.log(ngrams["wd"]);

Removing Element From Array of Arrays with Regex

I'm using regex to test certain elements in an array of arrays. If an inner array doesn't follow the desired format, I'd like to remove it from the main/outer array. The regex I'm using is working correctly. I am not sure why it isn't removing - can anyone advise or offer any edits to resolve this problem?
for (var i = arr.length-1; i>0; i--) {
var a = /^\w+$/;
var b = /^\w+$/;
var c = /^\w+$/;
var first = a.test(arr[i][0]);
var second = b.test(arr[i][1]);
var third = c.test(arr[i][2]);
if ((!first) || (!second) || (!third)){
arr.splice(i,1);
}
When you cast splice method on an array, its length is updated immediately. Thus, in future iterations, you will probably jump over some of its members.
For example:
var arr = ['a','b','c','d','e','f','g']
for(var i = 0; i < arr.length; i++) {
console.log(i, arr)
if(i%2 === 0) {
arr.splice(i, 1) // remove elements with even index
}
}
console.log(arr)
It will output:
0 ["a", "b", "c", "d", "e", "f", "g"]
1 ["b", "c", "d", "e", "f", "g"]
2 ["b", "c", "d", "e", "f", "g"]
3 ["b", "c", "e", "f", "g"]
4 ["b", "c", "e", "f", "g"]
["b", "c", "e", "f"]
My suggestion is, do not modify the array itself if you still have to iterate through it. Use another variable to save it.
var arr = ['a','b','c','d','e','f','g']
var another = []
for(var i = 0; i < arr.length; i++) {
if(i%2) {
another.push(arr[i]) // store elements with odd index
}
}
console.log(another) // ["b", "d", "f"]
Or you could go with Array.prototype.filter, which is much simpler:
arr.filter(function(el, i) {
return i%2 // store elements with odd index
})
It also outputs:
["b", "d", "f"]
Your code seems to work to me. The code in your post was missing a } to close the for statement but that should have caused the script to fail to parse and not even run at all.
I do agree with Leo that it would probably be cleaner to rewrite it using Array.prototype.filter though.
The code in your question would look something like this as a filter:
arr = arr.filter(function (row) {
return /^\w+$/.test(row[0]) && /^\w+$/.test(row[1]) && /^\w+$/.test(row[2]);
});
jsFiddle
I'm assuming it is 3 different regular expressions in your actual code, if they are all identical in your code you can save a little overhead by defining the RegExp literal once:
arr = arr.filter(function (row) {
var rxIsWord = /^\w+$/;
return rxIsWord.test(row[0]) && rxIsWord.test(row[1]) && rxIsWord.test(row[2]);
});

Categories

Resources