In one sense, I think your grader is confused. The only global variables here are data and your winner function. That's entirely appropriate.
But there is a fair bit to clean up.
const winner = (teamObj) => {
const tally = {}
const tiedReduceData = []
const secondTally = {}
const tiedFinals=[]
This is probably what was meant. While these are not global, they serve a similar role as globals inside your function, data you initialize here and then manipulate elsewhere without passing them through from call to call.
const pickTeam = (teamObj) => {
const [[team1, score1], [team2, score2]] = Object.entries(teamObj)
return score1 === score2 ?
null : score1 > score2 ? team1 : team2
}
This seems fine, except that you reuse the name teamObj -- the parameter to winner, and you use it to mean something different here. Might I suggest game, contest, or some such instead? It's also not a descriptive name as the parameter to winner. Perhaps match or games or tournament?
for(const teamObj of data) {
const team = pickTeam(teamObj)
if(team) {
tally[team] = (tally[team] ?? 0) + 1
}
}
This is related to the point about the pseudo-globals. The earlier declaration of tally = {} and this for-loop would best be combined in a single reduce call.
const getMaxArray = Object.keys(tally)
.filter(x => {
return tally[x] ==
Math.max.apply(null, Object.values(tally))
})
Three things. First, you might want to keep a local variable, rather than recalculating the maximum as you test every contestant. Second the name getMaxArray sounds like an action, something that should be a function. winners, topTier, nextRoundContestants, something like that would be more clear.
Third, note that
Math.max.apply(null, Object.values(tally))
can better be written in modern JS as
Math .max (...Object .values (tally))
const compare = data.forEach( e => {
if(Object.keys(e).every(i => getMaxArray.includes(i))){
tiedReduceData.push(e);
}
})
The name compare here is totally misleading. What you're doing is filtering the games to only include those where both contestants are among the top tiers.
Again, removing the pseudo-global, we could write something like
const tiedReduceData = data .filter (game =>
Object .keys (game) .every (team => topTier .includes (team))
)
(I couldn't not change getMaxArray to topTier. That name is really bad!)
Skipping secondTally and maxArraySubset. These should not be needed if we do our recursion properly.
data.forEach( e => {
if(Object.keys(e).every(i => getMaxArraySubset.includes(i))){
tiedFinals.push(e);
}
})
const randomWinner = getMaxArraySubset[Math.floor(Math.random() * getMaxArraySubset.length)]
const allTied = "No Winner. All Tied."
Here you calculate all the things you might return, even though you will only ever return one of them. It makes more sense to do such calculations only when you know that you will need them.
if (getMaxArray.length === 1){
return getMaxArray
} else if (getMaxArray.length === 0){
return allTied
} else if( getMaxArraySubset.length === 1 ){
return getMaxArraySubset
} else {
return randomWinner
}
}
You have four return conditions here, all with different results. But the problem had three specifications. Are you sure your "allTied" scenario is possible?
In fact, in the problem specification, condition (b) does not really designate a winner, but points to a recursion. So it's time to talk about that recursion.
If we have several teams with the highest score, we want to call our function again, but only for the games that include those specific teams -- or that's the way I read the problem. The trouble is that we might not reduce our set of players by doing this, for instance, three games, A beats B, B beats C, and C beats A, each with one win and one loss. That's the case where we want to choose a random winner. If we have reduced the number of players ('lime' has two wins, 'magenta', 'sienna', and 'berry' each have three, so we try again without 'lime') then we make a recursive call. Here you'll need to filter the games in the input to exclude those with players not in the top tier. The code above shows plenty of ability to do this sort of manipulation, so I leave that to you.
Before I looked at your code, I tried this on my own, and wrote a version that overlaps some with your notions, but also differs from them substantially. I wouldn't suggest using any of it except for inspiration, but here it is without any annotations or discussions:
const matchWinner = (match) => {
const players = [...new Set (match .flatMap (
game => Object .keys (game)
))]
const scores = match .flatMap (Object .entries) .reduce (
(a, [t, s]) => ((a [t] = (a[t] || 0) + s), a), {}
)
const topScore = Math .max (...Object .values (scores))
const topTier = Object .entries (scores)
.filter (([k, v]) => v == topScore)
.map (([k]) => k)
return topTier .length == 1
? topTier [0]
: topTier .length == players .length
? topTier [Math .floor (Math .random () * topTier .length)]
: matchWinner (match .filter (game => Object .keys (game) .every (
contestant => topTier .includes (contestant)
)))
}
const data = [{magenta: 1, lime: 0}, {magenta: 1, berry: 0}, {berry: 1, magenta: 0}, {berry: 1, sienna: 0}, {sienna: 1, lime: 0}, {sienna: 1, magenta: 0}, {magenta: 0, lime: 1}, {magenta: 0, berry: 1}, {berry: 0, magenta: 1}, {berry: 0, sienna: 1}, {sienna: 0, lime: 1}]
console .log (matchWinner (data))
//=> {"magenta":3,"lime":2,"berry":3,"sienna":3} // First call
// {"magenta":2,"berry":3,"sienna":2} // Second call (recursively)
// "berry" // Sole winner
Update
With more data, I realized that the match results were not winner/loser but simply points per side. With that in mind, we can change how the scores are calculated inside the function. I choose the chess-style 1 point for a win, 0 points for a loss, and 1/2 point for a tie. It would be easy enough to change this to soccer-style 3 points for a win, 0 points for a loss, and 1 point for a tie, or really to any other system that assigns point in a similar manner. Here's a chess-style implementation. It should be able to simply drop into my function:
const scores = match.reduce ((a, game) => {
const [[name1, score1], [name2, score2]] = Object .entries (game)
a [name1] = (a [name1] || 0) + (score1 > score2 ? 1 : score1 < score2 ? 0 : .5)
a [name2] = (a [name2] || 0) + (score2 > score1 ? 1 : score2 < score1 ? 0 : .5)
return a
}, {})