[英]Recursive (or iterative) solution for reducing data in an array of objects
編寫一個 function 接收顏色戰爭游戲結果列表並返回定義的獲勝者:
a) 獲勝最多的球隊 b) 如果平局,則在並列球隊的子集中獲勝最多的顏色 c) 最終,如果/當不能再減少並列 colors 的集合時,選擇剩余的一個隨機捆綁隊伍
問題:
const winner = (teamObj) => {
const tally = {}
const tiedReduceData = []
const secondTally = {}
const tiedFinals=[]
const pickTeam = (teamObj) => {
const [[team1, score1], [team2, score2]] = Object.entries(teamObj)
return score1 === score2 ?
null : score1 > score2 ? team1 : team2
}
for(const teamObj of data) {
const team = pickTeam(teamObj)
if(team) {
tally[team] = (tally[team] ?? 0) + 1
}
}
const getMaxArray = Object.keys(tally)
.filter(x => {
return tally[x] ==
Math.max.apply(null, Object.values(tally))
})
const compare = data.forEach( e => {
if(Object.keys(e).every(i => getMaxArray.includes(i))){
tiedReduceData.push(e);
}
})
for (const teamObj of tiedReduceData) {
const team = pickTeam(teamObj)
if(team) {
secondTally[team] = (secondTally[team] ?? 0) +1
}
}
const getMaxArraySubset = Object.keys(secondTally)
.filter(x => {
return secondTally[x] ==
Math.max.apply(null, Object.values(secondTally))
})
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."
if (getMaxArray.length === 1){
return getMaxArray
} else if (getMaxArray.length === 0){
return allTied
} else if( getMaxArraySubset.length === 1 ){
return getMaxArraySubset
} else {
return randomWinner
}
}
console.log(winner(data))
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},
]
從某種意義上說,我認為你的評分者很困惑。 這里唯一的全局變量是data
和你的winner
function。這是完全合適的。
但是有一點需要清理。
const winner = (teamObj) => {
const tally = {}
const tiedReduceData = []
const secondTally = {}
const tiedFinals=[]
大概就是這個意思。 雖然這些不是全局的,但它們在您的 function 中起着與全局類似的作用,您在此處初始化數據,然后在別處操作,而無需在調用之間傳遞它們。
const pickTeam = (teamObj) => {
const [[team1, score1], [team2, score2]] = Object.entries(teamObj)
return score1 === score2 ?
null : score1 > score2 ? team1 : team2
}
這看起來不錯,只是您重用了名稱teamObj
—— winner
的參數,並且在這里使用它來表示不同的含義。 我可以建議game
、 contest
或類似的東西嗎? 它也不是作為winner
參數的描述性名稱。 也許match
或games
或tournament
?
for(const teamObj of data) {
const team = pickTeam(teamObj)
if(team) {
tally[team] = (tally[team] ?? 0) + 1
}
}
這與偽全局變量有關。 tally = {}
的早期聲明和這個for
循環最好結合在一個reduce
調用中。
const getMaxArray = Object.keys(tally)
.filter(x => {
return tally[x] ==
Math.max.apply(null, Object.values(tally))
})
三件事。 首先,您可能希望保留一個局部變量,而不是在測試每個參賽者時重新計算最大值。 其次,名稱getMaxArray
聽起來像是一個動作,應該是winners
、 topTier
、 nextRoundContestants
類的東西會更清楚。
三、注意
Math.max.apply(null, Object.values(tally))
可以更好地用現代 JS 編寫為
Math .max (...Object .values (tally))
const compare = data.forEach( e => {
if(Object.keys(e).every(i => getMaxArray.includes(i))){
tiedReduceData.push(e);
}
})
此處的名稱compare
完全具有誤導性。 您正在做的是過濾游戲,只包括那些兩位參賽者都屬於頂級的游戲。
同樣,刪除偽全局,我們可以寫這樣的東西
const tiedReduceData = data.filter (game => Object.keys (game).every (team => topTier.includes (team)) )
(我不能不將getMaxArray
更改為topTier
。這個名字真的很糟糕!)
跳過secondTally
和maxArraySubset
。 如果我們正確地進行遞歸,則不需要這些。
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."
在這里你計算了你可能返回的所有東西,即使你只會返回其中一個。 只有當你知道你將需要它們時才進行這樣的計算更有意義。
if (getMaxArray.length === 1){
return getMaxArray
} else if (getMaxArray.length === 0){
return allTied
} else if( getMaxArraySubset.length === 1 ){
return getMaxArraySubset
} else {
return randomWinner
}
}
這里有四個返回條件,所有條件都有不同的結果。 但是這個問題有三個規格。 你確定你的“allTied”場景是可能的嗎?
事實上,在問題說明中,條件 (b) 並沒有真正指定一個贏家,而是指向一個遞歸。 所以是時候談談遞歸了。
如果我們有幾支得分最高的球隊,我們想再次調用我們的 function,但僅限於包含這些特定球隊的比賽——或者這就是我閱讀問題的方式。 麻煩的是這樣做可能不會減少我們的玩家組,比如三場比賽,A打B,B打C,C打A,每局一勝一負。 這就是我們要選擇一個隨機獲勝者的情況。 如果我們減少了玩家數量('lime' 贏了兩次,'magenta'、'sienna' 和 'berry' 各贏了 3 次,所以我們在沒有 'lime' 的情況下再試一次)然后我們進行遞歸調用。 在這里,您需要過濾輸入中的游戲,以排除那些玩家不在頂級的游戲。 上面的代碼展示了進行這種操作的很多能力,所以我把它留給你了。
在查看您的代碼之前,我自己嘗試了這個,並編寫了一個版本,該版本與您的概念有一些重疊,但也與它們有很大不同。 除了靈感,我不建議使用其中任何一個,但這里沒有任何注釋或討論:
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
有了更多數據,我意識到比賽結果不是贏家/輸家,而只是每方的得分。 考慮到這一點,我們可以更改 function 中的分數計算方式。我選擇國際象棋式,贏 1 分,輸 0 分,平局 1/2 分。 很容易將其更改為足球風格的贏 3 分,輸 0 分,平局 1 分,或者實際上更改為以類似方式分配分數的任何其他系統。 這是國際象棋風格的實現。 它應該能夠簡單地放入我的 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
}, {})
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.