How to find numbers and remove them from a multidimensional array? - javascript

I'm trying to create a Bingo game to play with my family and I would like to know what is the best way to remove numbers that have been called and then create another array with the tickets that is empty - so I know they have already win.
So far I made this array (I reduced it to make easier to understand):
var tickets = [
['T1',1,2,8,10,23,18,44],
['T2',2,3,3,6,45,12,55],
['T3',4,7,10,42,55,45,60],
['T4',3,5,3,6,55,68,85],
['T5',1,4,3,6,66,74,80],
];
All the other part of bingo is done, I just need to realize how can I know the tickets that have finished already to make easier to validate the winners.

Do you have an array of numbers that have been already called?
If so:
var called = [1,2,8,10,23,18,44];
var tickets = [
['T1',1,2,8,10,23,18,44],
['T2',2,3,3,6,45,12,55],
['T3',4,7,10,42,55,45,60],
['T4',3,5,3,6,55,68,85],
['T5',1,4,3,6,66,74,80]
];
// Gets tickets with called numbers removed
tickets = tickets.map(x => x.filter(x => !called.includes(x)));
console.log(tickets);
// Gets winners
var winners = tickets.map(x => x.length === 1 ? x : []).flatMap(x=>x)
console.log(winners);
// You should really use objects here but you decided multidimentional arrays, Example of an object
/*
{
'T1': [1,2,8,10,23,18,44],
'T2': [2,3,3,6,45,12,55],
'T3': [4,7,10,42,55,45,60],
'T4': [3,5,3,6,55,68,85],
'T5': [1,4,3,6,66,74,80]
}
*/
/* this is to make the SO console bigger */
.as-console-wrapper{max-height: none !important;height:100% !important}

Related

How to pass an array to a function without mutating the original array?

I'm working on a helper function within a bigger function.
I have an array of team object (each team looks like this {team:unique number of the team, points:0, goalDiff:0, goalsFor:0}) as the first argument of my function and I have an array of game results as a second argument (each game looks like this [number ref home team, number ref away team, goals scored by home team, goals scored by away team]).
My function looks at each game in the second array and allocate 2 points to the winning team and zero to the losing one. in case of draw, it allocates 1 point to each team. Function also counts the goals scored by each team.
let computeGames = (arr, games) => {
games.forEach((game) => {
let homeTeamIndex = arr.findIndex(team => team.team === game[0])
let awayTeamIndex = arr.findIndex(team => team.team === game[1])
let homeTeam = arr[homeTeamIndex]
let awayTeam = arr[awayTeamIndex]
game[2] > game[3]
? (homeTeam.points += 2)
: game[2] === game[3]
? ((homeTeam.points += 1), (awayTeam.points += 1))
: (awayTeam.points += 2);
homeTeam.goalsFor += game[2];
awayTeam.goalsFor += game[3];
homeTeam.goalDiff += game[2] - game[3];
awayTeam.goalDiff += game[3] - game[2];
});
return arr
};
My code is counting the points and goals as expected, but my issue is that when I execute something like the code below, the points are being counted in my teams array as well as in my doNotMutate array.
I do not understand why teams is being mutated given it's a copy of teams that is passed to the function, not the teams array itself. If someone could explain, I'm getting really confused here.
Cheers
const teams = createTeams(number) // this is a helper function to generate my teams with all values = 0
let doNotMutate = [...teams]
doNotMutate= computeGames(doNotMutate,games)
console.log(teams, doNotMutate)
Doing [...teams] does not copy the objects stored within the array. Each item of the array is simply copied into a new array. In your case each item is an object, meaning the new array will contain the exact same objects (think of them as "references to the same value" if that helps).
You can observe this:
teams[0] === doNotMutate[0] // true! (we don't want this)
You'll want to step through the array and map each item to a copy of itself ({...obj}). Something like this:
const doNotMutate = [...teams].map(game => ({...game}));
Alternatively, explore deep-clone implementations such as lodash's.

Sort array based on intermediate model's attribute

I have three models (I am using Vue/Vuex-ORM on the frontend). Category, CategoryItem and Item.
I'm able to fetch an array of categories, and within each category is an array of items. An intermediate join model defines the relationships of these two models, and I am able to access as such:
// array of categories, each category has array of items
const categories = Category.query().where('pack_id', this.selectedPack.id).with('items').get();
categories.map(category => {
category.items.forEach(item => {
console.log('item.pivot: ', item.pivot); // pivot refers to join model
// how to order items based on item.pivot?
})
})
Within the .forEach, I can access the join model with item.pivot. What I am looking to do however, is sort each category's items based on item.pivot.position.
I started going down a path where the first line inside of the .map I defined a new empty array, and would then theoretically push in a new value based on whether the position was higher or lower, but I couldn't quite wrap my head around how to accomplish this.
Thanks!
Well just my luck. A half hour after posting this question, I figure it out! Here was what I did, in case anyone is curious.
categories() {
const categories = Category.query().where('pack_id', this.selectedPack.id).with('items').get();
categories.forEach(category => category.items.sort(this.compare));
return cats;
}
compare(a, b) {
let comparison = 0;
if (a.pivot.position > b.pivot.position) comparison = 1;
else if (a.pivot.position < b.pivot.position) comparison = -1;
return comparison;
},

Vue computed property overwriting global state without vuex

I have a list of people who have scores. In state I have them listed in an array, one of the items in the array is 'scoreHistory' which is an array of objects containing their scores at different points in time. I want to filter this set for different time periods i.e. -5 days, -30 days so instead of just seeing the overall score I can see the scores if everyone started at 0 say 30 days ago.
I have it (kind of) working. See my code below:
filteredScores () {
if(!this.people) {
return
}
// Here I was trying to ensure there was a copy of the array created in order to not change the original array. I thought that might have been the problem.
let allPeople = this.people.slice(0) // this.people comes from another computed property with a simple getter. Returns an array.
let timeWindow = 30 //days
const windowStart = moment().subtract(timeWindow,'days').toDate()
for (const p of allPeople ) {
let filteredScores = inf.scoreHistory.filter(score => moment(score.date.toDate()).isSameOrAfter(windowStart,'day'))
p.scoreHistory=filteredScores
//calculate new score
p.score = inf.scoreHistory.reduce(function(sum,item) {
return sum + item.voteScore
},0)
}
return allInf
}
I expected it to return to me a new array where each person's score is summed up over the designated time period. It seems to do that OK. The problem is that it is altering the state that this.people reads from which is the overall data set. So once it filters all that data is gone. I don't know how I am altering global state without using vuex??
Any help is greatly appreciated.
Your problem isn't that you're modifying the array, but that you're modifying the objects within the array. You change the scoreHistory and score property of each item in the array. What you want to do instead is create a new array (I recommend using map) where each item is a copy of the existing item plus a new score property.
filteredScores () {
if(!this.people) {
return
}
let timeWindow = 30 //days
const windowStart = moment().subtract(timeWindow,'days').toDate()
return this.people.map(p => {
let filteredScores = p.scoreHistory.filter(score => moment(score.date.toDate()).isSameOrAfter(windowStart,'day'))
//calculate new score
let score = filteredScores.reduce(function(sum, item) {
return sum + item.voteScore
}, 0)
// Create a new object containing all the properties of p and adding score
return {
...p,
score
}
}
})

Checking an input against a specific array string

I am trying to create a quiz that randomly selects a question from pool of questions in an array, which is answered in an input box that is to be checked against the corresponding answer string in the array. I used math.floor(math.random() *); to get my random number. The random number is intended to be used to find both the question and answer, which are arranged in order to correspond to one another, e.g. ['question1','question2'] and ['answer1','answer2'].
I am having difficulty with trying to get my input to properly be checked against the corresponding answer value from the array. I am fairly novice at Javascript, so I am not sure as to how to do this. I tried using the document.getElementById command to compare the two. I suspect it has something to do with the fact that ansNum doesn't get the value of questNum because of the fact that questNum is only given its value inside the generateQuiz function. (I realize ansNum is likely redundant, but I was just playing around to see if anything would happen)
Javascript:
const questions = ['What do young Roman males wear?','Who is the Roman god of the smith?','Who is the 6th king of Rome?'];
const answers = ['toga praetexta','vulcan','servius tullius'];
function getQuestNum() {
questNum = Math.floor(Math.random() * 3);
};
function getAnsNum() {
ansNum = questNum();
}
function generateQuiz() {
getQuestNum();
document.getElementById("question").innerHTML = questions[questNum];
};
function checkCorrect() {
getAnsNum();
if (answer[ansNum] = document.getElementById("input").innerHTML) {
document.getElementById("verification").innerHTML = "Correct!";
}
};
Codepen Link
An image of the code
Based on your code, I fixed it with some changes. It is not the best way to do this i think. I posted the js part here.
const questions = ['What do young Roman males wear?','Who is the Roman god of the smith?','Who is the 6th king of Rome?'];
const answers = ['toga praetexta','vulcan','servius tullius'];
var questNum;
function getQuestNum() {
questNum = Math.floor(Math.random() * 3);
};
function getAnsNum() {
ansNum = questNum;
}
function generateQuiz() {
getQuestNum();
document.getElementById("question").innerHTML = questions[questNum];
};
function checkCorrect() {
getAnsNum();
if (answers[ansNum] = document.getElementById("input").value) {
document.getElementById("verification").innerHTML = "Correct!";
}
};
First you need a global variable questNum then you can use it in all of your functions.
The function getAnsNum() is redundant, at least i think so, just use questNum in your checkCorrect() function.
For getElementByID function, insert an ID attribute to your input
<input id="input" type="text" name="input">
For input, if you want to take the value of the input field, use document.getElementById("input").value instead of innerHTML.
If you not sure about any result, console.log it or use Chrome dev debug tool to check the result. In the checkCorrect function, your array name should be answers instead of answer.
Shorter ver:
const questions = ['What do young Roman males wear?','Who is the Roman god of the smith?','Who is the 6th king of Rome?'];
const answers = ['toga praetexta','vulcan','servius tullius'];
var questNum;
function getQuestNum() {
questNum = Math.floor(Math.random() * 3);
};
function generateQuiz() {
getQuestNum();
document.getElementById("question").innerHTML = questions[questNum];
};
function checkCorrect() {
if (answers[questNum] = document.getElementById("input").value) {
document.getElementById("verification").innerHTML = "Correct!";
}
};
It would be simpler to create an array of objects that each contain both a question and an answer - and create a function that generates your random number and returns the object at the corresponding index.
Then you'll have access to everything you need without worrying about whether or not you can maintain access to the original randomly selected number, or matching up indices between two different arrays.

sorting two associative arrays/stacks

I am implementing an algorithm I designed and am exploring different approaches
This isn't a homework problem but I am going to explain it like one: lets say a merchant has bought inventory of apples on different days, and also sold some on different days. I want the weighted average timestamp of their current purchases.
I am storing this data object as timestamp string in epoch time, and quantity of apples. My dataset actually has the purchases and the sells in separate data sets, like so:
//buys
var incomingArray = {
"1518744389": 10,
"1318744389": 30
};
//sells
var outgoingArray = {
"1518744480": 3,
"1418744389": 5,
"1408744389": 8
};
and I would like the outcome to show only the remainding incomingArray timestamp purchase pairs.
var incomingArrayRemaining = {
"1518744389": 7,
"1318744389": 17
};
Where you see there was one outgoing transaction for 3 apples at a later timestamp, therefore subtracting from 10. And there were 13 outgoing transactions before the buy of 10, but after the purchase of 30, so they only subtract from the 30.
Note, if more than 10 were transferred after 10, it would subtract from both 10 and 30. The number of apples can never be less than 0.
First, to accomplish my goals it seems that I need to know how many are actually still owned from the lot they were purchased in.
Instead of doing stack subtracting in the LIFO method, it seems like this has to be more like Tax Lot Accounting. Where the lots themselves have to be treated independently.
Therefore I would have to take the timestamp of the first index of the sell in the outgoing array and find the nearest older timestamp of the buy in the incoming array
Here is what I tried:
for (var ink in incomingArray) {
var inInt = parseInt(ink);
for (var outk in outgoingArray) {
if (inInt >= 0) {
var outInt = parseInt(outk);
if (outInt >= inInt) {
inInt = inInt - outInt;
if (intInt < 0) {
outInt = inInt * -1; //remainder
inInt = 0;
} //end if
} //end if
} //end if
} //end innter for
} //end outer for
It is incomplete and the nested for loop solution will already have poor computational time.
That function merely tries to sort the transactions so that only the remaining balance remains, by subtracting an outgoing from the nearest incoming balance, and carrying that remainder to the next incoming balance
I feel like a recursive solution would be better, or maybe something more elegant that I hadn't thought of (nested Object forEach accessor in javascript)
After I get them sorted then I need to actually do the weighted average method, which I have some ideas for already.
First sorting, then weighted average of the remaining quantities.
Anyway, I know the javascript community on StackOverflow is particularly harsh about asking for help but I'm at an impasse because not only do I want a solution, but a computationally efficient solution, so I will probably throw a bounty on it.
You could convert the objects into an array of timestamp-value pairs. Outgoing ones could be negative. Then you can easily sort them after the timestamp and accumulate how you like it:
const purchases = Object.entries(incomingArray).concat(Object.entries(outgoingArray).map(([ts, val]) => ([ts, -val])));
purchases.sort(([ts1, ts2]) => ts1 - ts2);
Now you could iterate over the timespan and store the delta in a new array when the value increases (a new ingoing):
const result = [];
let delta = 0, lastIngoing = purchases[0][0];
for(const [time, value] of purchases){
if(value > 0){
// Store the old
result.push([lastIngoing, delta]);
// Set up new
delta = 0;
lastIngoing = time;
} else {
delta += value;
}
}

Categories

Resources