首頁 > web前端 > js教程 > 我的第一個 JavaScript 套件(以遞迴取勝)

我的第一個 JavaScript 套件(以遞迴取勝)

Linda Hamilton
發布: 2024-11-29 08:50:10
原創
303 人瀏覽過

由於我是業餘開發者,仍在探索 JavaScript 的深度,並且之前從未編寫或發布過包,所以我當然沒有資格向任何人教授任何東西。不過,我可以做的是分享我的寫作經驗,雖然本質上仍然是一個不完美的包,但我在構思過程中得到了很多樂趣。

這一切都始於一個問題,我很驚訝地發現這個問題在網路上並沒有得到太多解決,而且我發現可以用一種意想不到的方式巧妙地解決這個問題:那就是使用遞歸。

聯賽表的問題

喜歡足球(或一般運動,或任何需要排名的競爭環境)的人肯定熟悉聯賽表的概念:一定數量的球隊相互競爭在一系列比賽中,之後每隊根據勝利、平局或失敗獲得一定數量的積分聯賽表按照積分降序收集所有球隊,從而明確哪支球隊獲得冠軍。

就編程而言,這種情況不會造成任何問題:檢查哪支球隊贏得了一場比賽很簡單(在足球中,就像查看兩支球隊中哪一支進球更多一樣簡單),併計算總數所有比賽的得分都可以歸結為簡單的相加。排序步驟也不複雜,因為 JavaScript 甚至自備了預設的陣列排序方法。

人們會想​​像,當兩支或更多球隊在積分上持平時,事情會變得不那麼瑣碎,在這種情況下,需要決勝局。再回到足球,常見的決勝局通常包括淨勝球數(進球數減去失球數)、進球數、勝場數等。這再次表明,問題並不是那麼難以解決:如果出現積分相同的情況,只需切換到根據不同的標準對相關球隊進行排序即可。只要選擇池是有限的且是硬編碼的,即使允許使用者選擇應用哪個決勝局,或以什麼順序,實現起來也不困難。

這就是大多數線上解決方案解決問題的程度。然而,事情比人們最初想像的要微妙一些,也更深入一些。

頭對頭 v. 整體風格

事實是,任何類型的標準(淨勝球、進球數等等,甚至是積分本身)都可以透過兩種不同的方式進行檢查:在全球意義上(所謂的總體檢查),這是一種標準的檢查類型,人們會查看完整表,因為它出現在所有匹配的比賽中所有球隊都參加過比賽,並據此對球隊進行比較;或者在有限的意義上(所謂的面對面檢查),如果兩支或多支球隊積分相同,則僅在兩隊之間進行的比賽中檢查任何後續的決勝局。有問題的團隊。

一個例子可以清楚地說明這種差異。以 2016 年歐洲盃 E 組為例,最終的決賽桌是這樣的


Position Team Won Drawn Lost GF GA GD Points
1 Italy 2 0 1 3 1 2 6
2 Belgium 2 0 1 4 2 2 6
3 Republic of Ireland 1 1 1 2 4 -2 4
4 Sweden 0 1 2 1 3 -2 1

GF = 進球數(進球數),GA = 失球數(失球數),GD = 淨勝球數。


義大利和比利時積分相同,歐洲盃的第一個決勝局是淨勝球(義大利和比利時仍然打平,各2 個),其次是進球數——此時人們會期望比利時以4 球對3 球戰勝義大利。

然而,

歐洲盃是一項比賽,採用面對面的方式對積分相同的球隊進行排序。 這意味著,一旦我們認識到義大利和比利時需要當他們的平局被打破時,我們立即計算一個僅由平局相關球隊組成的子表。由於他們在第一場比賽中以0-2 的比分結束了意大利隊的單場比賽,所以這個分錶看起來像這樣(足球為獲勝分配3 分,為平局分配1 分,為失敗分配1 分)


位置 團隊 贏得 繪製 丟失 GF GA GD 積分 標題> 1 義大利 1 0 0 2 0 2
3
Position Team Won Drawn Lost GF GA GD Points
1 Italy 1 0 0 2 0 2 3
2 Belgium 0 0 1 0 2 -2 0
2 比利時 0 0 1 0 2 -2 0 表>

GF = 進球數(進球數),GA = 失球數(失球數),GD = 淨勝球數。


這意味著,由於正面交鋒的分數(三比零),義大利立即排在比利時之上。

不同的比賽通常會採用不同的風格(歐洲杯使用我們剛才看到的面對面風格,而例如FIFA 世界杯則以整體檢查而聞名),儘管它們都會切換到另一個風格應該是第一輪標準在打破給定平局方面沒有決定性。

這意味著,潛在的正面交鋒遲早會發生(要么在歐洲杯這樣的比賽中立即發生,要么作為 FIFA 世界杯上潛在的第二次決勝局)。因此,我的結論是,我們查看的表格在排序過程中可能會發生變化,,因為頭對頭風格(無論是否立即出現)取決於此事實。

但這並不是我唯一的收穫,如下例所示。

更複雜的事情:頭對頭重新應用

比利時再次成為 2024 年歐洲盃著名 E 組的主角,每支球隊均積 4 分小組出線


位置 團隊 贏得 繪製 丟失 GF GA GD 積分 標題> 1 羅馬尼亞 1 1 1 4 3 1
Position Team Won Drawn Lost GF GA GD Points
1 Romania 1 1 1 4 3 1 4
2 Belgium 1 1 1 2 1 1 4
3 Slovakia 1 1 1 3 3 0 4
4 Ukraine 1 1 1 2 4 -2 4
4 2 比利時 1 1 1 2 1 1 4 3 斯洛伐克 1 1 1 3 3 0 4 4 烏克蘭 1 1 1 2 4 -2 4 表>

GF = 進球數(進球數),GA = 失球數(失球數),GD = 淨勝球數。


由於所有隊伍積分相同,所以對戰結果的子表無論如何仍然是完整的表。斯洛伐克和烏克蘭以淨勝球數排名墊底,羅馬尼亞則因進球數較多而排在比利時之前(羅馬尼亞 4 球,比利時 2 球)。事實上,這就是決賽桌的樣子。

但有人可能會覺得這很奇怪:比利時和羅馬尼亞之間只有一場比賽,比利時在第二個比賽日以 2-0 獲勝。那為什麼比利時的排名沒有超過羅馬尼亞呢?這沒關係嗎?為什麼在斯洛伐克和烏克蘭與其他國家分開後,沒有像我們之前那樣重新計算子表?

事情的真相是,根據歐洲足聯的規定(§20.01-d.),每一步都不會重新計算面對面的結果,但只有在決勝局的完整名單用完後。 由於在積分和淨勝球平局後,仍然需要檢查進球數,我們會查看並這就是羅馬尼亞最終獲勝的原因。如果那個也打成平局,只有在那個時候我們才會真正開始考慮基於仍然打平的球隊(比利時和羅馬尼亞)之間進行的單場比賽的分表,事實上,比利時會因為他們的勝利而名列前茅。

所以這是第二個要點:排序過程中涉及到一個深度的概念,取決於你對它的了解程度必須做出不同的決定-例如是否繼續進行子表重新計算。在這種情況下,您暫時不會繼續,因為標準清單仍在繼續。

這些是導致我決定排序函數形式的要點。

遞迴方法

透過我的套件實現的類別的 .stands() 方法存取的排序演算法依賴於遞歸函數

const sortAndDivideTable = (table, iteration, criteria) => {
    // ...
}
登入後複製

在任何給定的步驟表中,保存當前要排序的團隊資料的表,迭代會追蹤迭代次數和其他相關資訊(例如,如果這是頭對頭類型或整體類型)檢查),而criteria 是一個數組,表示要應用的標準的有序列表(例如積分、淨勝球、進球數)。

遞歸從一個表格開始,該表格是根據所有球隊參加的所有比賽計算得出的,並且仍然可能是無序的。請注意,演算法的第一次迭代始終是整體類型(因此在遞歸開始時進行初始化),因為根據定義第一次檢查始終是在所有中獲得的點數matches; 再次根據定義,條件數組中的第一個元素總是「點」。

這個起始表也被安全地隱藏起來:在任何給定的步驟中,它的條目都不會修改,但它的行會一點一點地排序。為了方便參考,我們可以稱之為‘原來的排名’

歐洲冠軍聯賽的真實例子

考慮到這一點,可以透過 2013-14 賽季歐洲冠軍聯賽 D 組提供的範例來理解該演算法,最終結果如下


位置 團隊 贏得 繪製 丟失 GF GA GD 積分 標題> 1 拜仁慕尼黑 5 0 1 17 5 12
Position Team Won Drawn Lost GF GA GD Points
1 Bayern Munich 5 0 1 17 5 12 15
2 Manchester City 5 0 1 18 10 8 15
3 Viktoria Plzeň 1 0 5 6 17 -11 3
4 CSKA Moscow 1 0 5 8 17 -9 3
15 2 曼城 5 0 1 18 10 8 15 3 維多利亞比爾森 1 0 5 6 17 -11 3 4 莫斯科中央陸軍 1 0 5 8 17 -9 3 表>

GF = 進球數(進球數),GA = 失球數(失球數),GD = 淨勝球數。


正如我們所指出的,在演算法的第一步,排序標準是「點」。 sortAndDivideTable 的第一個工作是根據它來分割表格,我們使用標準的 .reduce() JavaScript 陣列方法來執行此操作。

在這種情況下,我們會產生兩支球隊的兩個子表,因為有兩組並列球隊(拜仁慕尼黑/曼城和維多利亞比爾森/莫斯科中央陸軍,分別獲得15 分和3 分) 。然後對原始排名進行部分排序:其中一些球隊肯定會高於其他球隊(例如曼城肯定高於比爾森維多利亞),但積分相同的球隊仍懸而未決。

當第一個遞歸步驟接近結束時,我們決定下一步去哪裡:我們知道歐洲冠軍聯賽採用了面對面的風格,因此我們將其寫為下一次迭代的類型;由於切碎的組的長度大於一(每個組中有兩個組),這意味著我們在排序方面還有一些工作要做,我們將它們中的每一個作為新表反饋到sortAndDivideTable中。

讓我們回顧一下第一組平手球隊的歷史。我們已經進入了一場面對面的比賽標準,所以首先我們計算他們比賽的分錶:我們看到拜仁慕尼黑在主場輸了(拜仁慕尼黑2-3 曼城),但隨後在客場獲勝以更大的優勢(曼城1-3拜仁慕尼黑),從而產生分錶


位置 團隊 贏得 繪製 丟失 GF GA GD 積分 標題> 1 拜仁慕尼黑 1 0 1 5 4 1
Position Team Won Drawn Lost GF GA GD Points
1 Bayern Munich 1 0 1 5 4 1 3
2 Manchester City 1 0 1 4 5 -1 3
3 2 曼城 1 0 1 4 5 -1 3 表>

GF = 進球數(進球數),GA = 失球數(失球數),GD = 淨勝球數。


現在,它們被綁在(頭對頭)點上:因此分組步驟使該表保持不變,排序步驟無關,並且我們到達遞歸迭代的末尾而沒有完成太多工作;因此,我們進入下一個標準(淨勝球),保持頭對頭類型,然後將表回饋到函數中。

由於我們正處於一對一的運行過程中,我們跳過了表重新計算步驟(如第二個要點部分所述,這是我們僅在一對一過程中執行的操作開始, 或當 結束所有在應用條件後做出決定);但這次的標準是淨勝球,因此分組步驟成功地將表格分成兩個子表格(每個子表格的長度為一個),因為一支球隊的淨勝球為1 ,而另一支球隊的淨勝球為-1 。這使得排序步驟可以再次查看原來的排名,現在可以肯定地說拜仁慕尼黑排名高於曼城。

由於分組步驟中產生的切碎集的長度恰好為一(意味著沒有剩餘的聯繫),我們透過不再呼叫該函數來退出該分支上的遞歸。

另一方面,在第二盤的歷史上,我們看到比爾森維多利亞同樣在主場獲勝(比爾森維多利亞2-1 莫斯科中央陸軍),但隨後在客場以同樣的優勢輸掉了比賽(莫斯科中央陸軍3 場) -2 Viktoria Plzeň),因而產生


位置 團隊 贏得 繪製 丟失 GF GA GD 積分 標題> 1 維多利亞比爾森 1 0 4 4 4 0
Position Team Won Drawn Lost GF GA GD Points
1 Viktoria Plzeň 1 0 4 4 4 0 3
2 CSKA Moscow 1 0 1 4 4 0 3
3 2 莫斯科中央陸軍 1 0 1 4 4 0 3 表>

GF = 進球數(進球數),GA = 失球數(失球數),GD = 淨勝球數。


兩支球隊在頭對頭淨勝球和頭對頭進球數上並列,因此與對手不同,他們需要額外的標準(因此需要額外的遞歸步驟)進行排序。就歐洲冠軍聯賽而言,下一個標準是客場進球數,其中比爾森維多利亞隊為 2 粒,莫斯科中央陸軍隊為 1 粒,在他們的交鋒記錄中。

再次,遞歸在這一步驟結束,原來的排名現已完全排序。

它有什麼好處

這個例子顯示了遞歸方法的一些正面:兩個分支不需要互相「交談」——事實上,其中一個分支甚至需要一個額外的決勝局來進行排序,但這與遞歸方法無關。其他。任何關於我們已經達到的遞歸深度的問題,例如是否需要進行頭對頭的重新申請(如上面同名部分所解釋的),都可以由每個分支獨立地進行排序。

此外,由於任何兩個分支的交集始終為空(因為它們是透過.reduce() 在分組步驟中建立的,它總是將數組拆分為不相交的等價類),因此每個分支都可以獨立對自己的排序球隊在原來的積分榜上不踩對方的腳。這意味著,一支由極其平局的球隊組成的分支甚至可以用完整個面對面的標準,從而恢復到總體檢查,希望打破平局,而不會影響任何其他球隊的比較——因為沒有分支曾經影響過任何其他人。

也要注意遞歸如何結束:每當兩個團隊根據當前標準排序時,.reduce() 將產生長度嚴格小於當前表長度的子數組,這樣遞歸的下一步將更接近達到長度為 1的數組(此時不再調用該函數);如果平局一直持續到最後,最終標準始終是隨機抽籤或按字母順序排序,這必然會以任何一種方式產生結果(團隊標識符在輸入時必須是唯一的)。

最後的考慮因素

關於決勝局風格,以及該軟體包的功能以及用戶在定義錦標賽規則時有權訪問的所有自訂選項,還有很多要說的。

我將來可能會寫更多相關內容,特別是有關.ties() 方法的內容,該方法提供了對哪些團隊進行排序的文本描述,包括最終用戶可能想要打印的方式和步驟為了清晰起見。

但同時,如果您想的話,請隨時檢查 GitHub 上的儲存庫...

My first JavaScript package (with recursion for the win) 鈴鈴鈴春春 / 聯賽積分榜

一個 JavaScript 包,用於根據比賽資料產生聯賽表,具有可自訂的排序選項以及以文字和原始資料格式提供的決勝局應用程式的詳細說明。

聯盟積分榜

作為橄欖球(英式足球)的忠實粉絲,我可以自信地說,沒有什麼比將球隊放入表格更容易的了- 沒有什麼更難的了。它簡單,因為邏輯顯然足夠簡單:跟踪結果,計算點,按點排序;如果有任何平局,則應用平局決勝局。撇開一些細微差別不談,故事就到此結束了。但是,正如他們所說,細節決定成敗。

客場進球規則是一項著名的規則,早在2021 年就曾成為新聞,當時歐足聯廢除了這一規則;但它真的真的從決勝局名單中消失了嗎?或者再說一遍,在 2022-23 年歐洲聯賽 F 組中,所有球隊均以 8 分結束小組賽:這些平局是如何解決的?誰能預測如果兩隊在積分、淨勝球和進球數上相等,歐洲盃上會發生什麼事…


在 GitHub 上查看


...當然,這也是我學習的機會。如果您發現任何問題或需要改進的地方,請不要害羞並在評論中告訴我!

以上是我的第一個 JavaScript 套件(以遞迴取勝)的詳細內容。更多資訊請關注PHP中文網其他相關文章!

來源:dev.to
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
作者最新文章
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板