【解惑】孜孜不倦,用足球賽程詳解c#中的yield return用法

来源:https://www.cnblogs.com/lan80/archive/2023/09/19/17711941.html
-Advertisement-
Play Games

近些年來,隨著WPF在生產,製造,工業控制等領域應用越來越廣發,很多企業對WPF開發的需求也逐漸增多,使得很多人看到潛在機會,不斷從Web,WinForm開發轉向了WPF開發,但是WPF開發也有很多新的概念及設計思想,如:數據驅動,數據綁定,依賴屬性,命令,控制項模板,數據模板,MVVM等,與傳統Wi... ...


在一個知名企業贊助的足球聯賽中,有256支球隊參賽。為了確保比賽的順利進行,企業指派了小悅負責熬夜加班制定每一個球隊的賽程。儘管她對足球的瞭解並不多,但是她對待工作的認真態度卻讓人欽佩。

在小悅的努力下,她順利完成了第一輪、第二輪和第三輪的比賽安排。然而,在大賽開始前的模擬比賽中,她發現了一個嚴重的問題:由於參賽球隊過多,人為的安排總會導致一些參賽球隊被遺漏了比賽。這讓她十分焦慮,因為如果不能儘快解決這個問題,聯賽的公平性和競爭性將受到嚴重影響。

為瞭解決這個問題,小悅開始了她的電話咨詢之旅。她先是聯繫了賽事主辦方,瞭解參賽球隊的具體情況。隨後,她又聯繫了電腦專家,希望找到一個解決辦法,確保每個參賽球隊都能順利比賽。

在與電腦專家溝通的過程中,小悅瞭解到這個問題並不簡單,因為要考慮到的因素非常多。不過,在專家的幫助下,她逐漸找到了問題的根源,並聽取專家的意見採取了一些有效的措施來解決它。最終,經過小悅的不懈努力和電腦專家的協助,每個參賽球隊的賽程都被安排得合理而公正。

小悅在熬夜加班制定賽程時,扎著馬尾辮,聚精會神地盯著電腦屏幕,手指在鍵盤上飛快地敲擊著。她的眼神中閃爍著智慧的光芒,仿佛在告訴人們:她有能力解決任何問題。當她下意識輓起頭髮時,這個簡單的動作,展現了她的優雅和美麗。

在這個過程中,小悅學到了很多關於足球和賽程安排的知識。她也深刻體會到團隊合作的重要性,學會瞭如何與同事合作解決問題。最終,她的努力得到了上司和公司的肯定,也為她帶來了不少寶貴的經驗。

小悅面臨的問題如下:錦標賽由256支隊伍組成。這是一場迴圈賽(全部比賽),所以有255輪,每支球隊每輪比賽一次。每支球隊在錦標賽中與其他球隊對抗一次(每場比賽在錦標賽中不會重覆)。

 她的任務是實現一個函數buildMatchesTable,它接收團隊的數量(總是正數和偶數)並返回一個矩陣。

 矩陣中的每一行代表一輪。矩陣的每一列表示一個匹配。這場比賽是由兩支隊伍組成的一個陣列。每個團隊都用一個數字表示,從1開始直到團隊數量。

 示例(假設4只球隊參賽): 構建匹配表(4)

應該返回一個元組矩陣,如下所示:

{

new[]{(1,2),(3,4)},//第一輪:1對2,3對4

new[]{(1,3),(2,4)},//第二輪:1對3,2對4

new[]{(1,4),(2,3)}//第三輪:1對4,2對3

}


演算法實現:

 1 public static (int, int)[][] BuildMatchesTable(int n)
 2 {
 3     var matches = new (int, int)[n - 1][];  // 創建一個二維數組,表示比賽表
 4     var teams = FairCycle(n).GetEnumerator();  // 獲取一個迴圈迭代器,用於生成比賽對陣
 5     for (int i = 0; i < n - 1; i++)
 6     {
 7         var round = new (int, int)[n / 2];  // 創建一個一維數組,表示當前輪次的比賽對陣
 8         for (int t = 0; t < n / 2; t++)
 9         {
10             teams.MoveNext();  // 迭代到下一個比賽對陣
11             round[t] = teams.Current;  // 將當前比賽對陣添加到當前輪次的比賽表中
12         }
13         matches[i] = round;  // 將當前輪次的比賽表添加到總的比賽表中
14         // FairCycle函數會在這裡負責迴圈到下一個比賽對陣
15     }
16     return matches;  // 返回生成的比賽表
17 }
18 
19 static IEnumerable<(int,int)> FairCycle(int n)
20 {
21     // 將數字1到n-1按順時針方式排列
22     // 從12點鐘方向開始,1位於12點鐘位置
23     int p = 1;
24     while (true)
25     {
26         // 返回n和12點鐘位置的數字
27         yield return (p, n);
28         // 然後返回12點鐘左右兩側的數字配對
29         // 例如,11和1點鐘,10和2點鐘,以此類推
30         for (int i = 1; i < n / 2; i++)
31         {
32             int l = p + i;
33             l = l > (n - 1) ? l - (n - 1) : l;  // 對n進行迴圈,但不包括n本身
34             int r = p + (n - 1) - i;
35             r = r > (n - 1) ? r - (n - 1) : r;
36             yield return (l, r);
37         }
38         // 將時鐘旋轉n/2,使得p + n/2現在位於12點鐘位置
39         p += n / 2;
40         p = p > (n - 1) ? p - (n - 1) : p;
41         // 這將開始下一輪的配對
42     }
43 }

這段代碼實現了一個生成比賽表的函數`BuildMatchesTable`和一個輔助函數`FairCycle`。

`BuildMatchesTable`函數接收一個正數n作為參數,返回一個二維數組,表示比賽表。

`FairCycle`函數是一個模擬時鐘迴圈迭代器,用於生成球隊比賽對陣表。

讓我們以`BuildMatchesTable(4)`為例來詳細介紹`yield return`的運行過程和直接使用`return`的運行過程:

首先,我們調用`BuildMatchesTable(4)`函數,它將生成一個4個參與者的比賽表。

執行`var teams = FairCycle(n).GetEnumerator();`時會調用`FairCycle(n)`方法,並開始執行其中的代碼。在`FairCycle(n)`方法中,第一次調用`yield return`語句會返回一個`IEnumerator`對象,該對象用於迭代生成比賽對陣。

這意味著在執行`var teams = FairCycle(n).GetEnumerator();`之後,`teams`變數將持有一個可以用於迭代生成比賽對陣的迭代器對象。你可以使用`teams.MoveNext()`方法來逐個獲取比賽對陣,每次調用`MoveNext()`方法時,都會執行`FairCycle(n)`方法中的代碼,直到遇到下一個`yield return`語句。

使用`yield return`的運行過程如下:

1. 初始化時鐘位置p為1。
2. 進入無限迴圈。
3. 第一次調用`yield return`語句,返回當前時鐘位置p和數字n的配對`(p, n)`,即`(1, 4)`。此時,比賽表中的第一場比賽是1號選手對陣4號選手。
4. 繼續迴圈,進入第二次調用`yield return`語句。
- 計算左側數字l,它等於p加上i,即2。
- 計算右側數字r,它等於p加上(n-1)-i,即3。
- 使用`yield return`語句返回左側數字l和右側數字r的配對`(l, r)`,即`(2, 3)`。此時,比賽表中的第二場比賽是2號選手對陣3號選手。
5. 更新時鐘位置p,使其加上n/2,即2。此時,時鐘位置p指向下一輪的起始位置。
6. 回到步驟3,開始下一輪的配對。
- 第三次調用`yield return`語句,返回當前時鐘位置p和數字n的配對`(p, n)`,即`(2, 4)`。此時,比賽表中的第三場比賽是2號選手對陣4號選手。
- 繼續迴圈,進入第四次調用`yield return`語句。
- 計算左側數字l,它等於p加上i,即3。
- 計算右側數字r,它等於p加上(n-1)-i,即2。
- 使用`yield return`語句返回左側數字l和右側數字r的配對`(l, r)`,即`(3, 2)`。此時,比賽表中的第四場比賽是3號選手對陣2號選手。
- 更新時鐘位置p,使其加上n/2,即4。此時,時鐘位置p指向下一輪的起始位置。
- 回到步驟3,開始下一輪的配對。由於此時時鐘位置p超過了(n-1),即4,所以將其減去(n-1),即得到1。此時,時鐘位置p重新指向下一輪的起始位置。
- 第五次調用`yield return`語句,返回當前時鐘位置p和數字n的配對`(p, n)`,即`(1, 4)`。此時,比賽表中的第五場比賽是1號選手對陣4號選手。
- 繼續迴圈,進入第六次調用`yield return`語句。
- 計算左側數字l,它等於p加上i,即2。
- 計算右側數字r,它等於p加上(n-1)-i,即3。
- 使用`yield return`語句返回左側數字l和右側數字r的配對`(l, r)`,即`(2, 3)`。此時,比賽表中的第六場比賽是2號選手對陣3號選手。
- 更新時鐘位置p,使其加上n/2,即2。此時,時鐘位置p指向下一輪的起始位置。
- 回到步驟3,開始下一輪的配對。由於此時時鐘位置p超過了(n-1),即4,所以將其減去(n-1),即得到1。此時,時鐘位置p重新指向下一輪的起始位置。
- ...

這樣,`yield return`語句會按需生成比賽對陣,每次調用時返回一個比賽對陣,併在下一次調用時從迭代器Enumerator上一次離開的地方繼續執行。

現在,讓我們來看看直接使用`return`的運行過程:

1. 初始化時鐘位置p為1。
2. 直接使用`return`語句返回當前時鐘位置p和數字n的配對`(p, n)`,即`(1, 4)`。此時,方法立即終止執行,並將配對`(1, 4)`作為方法的結果返回。這意味著只會生成一個比賽對陣,即1號選手對陣4號選手。

使用`return`語句會立即終止方法的執行,並將指定的值作為方法的結果返回。這意味著方法只會生成一個比賽對陣,並且無法再繼續執行其他的代碼。


註:FairCycle方法設計為時鐘的模式是為了確保每個參賽球隊都能在錦標賽中與其他球隊公平地對抗一次。該方法的好處包括:

  1. 公平性:通過時鐘的模式(所有球隊對半匹配一次),每支球隊都有機會與其他球隊進行比賽,確保了比賽的公平性。沒有球隊會被遺漏或被偏好,每個球隊都有相同的機會競爭。

  2. 競爭性:FairCycle方法確保了每支球隊都能面對各種對手,包括實力強大的球隊和實力較弱的球隊。這種多樣性的對手使得比賽更具競爭性,增加了球隊之間的激烈程度。

  3. 簡潔性:時鐘的模式使得比賽安排更加簡潔明瞭。每支球隊在每輪比賽中都有一個確定的對手,沒有重覆或遺漏的情況,減少了混亂和錯誤的可能性。

  4. 可預測性:由於時鐘的模式,每支球隊都可以提前知道在每一輪比賽中將要面對的對手。這樣,球隊可以提前制定戰術和策略,更好地準備比賽。

假設有8支球隊參加一個錦標賽,並且FairCycle方法被用來安排比賽。FairCycle方法的時鐘模式將確保每支球隊都能與其他球隊公平地對抗一次。

首先,我們將8支球隊編號為A、B、C、D、E、F、G、H。根據FairCycle方法,比賽的安排如下:

第一輪: A vs B C vs D E vs F G vs H

第二輪: A vs C B vs D E vs G F vs H

第三輪: A vs D B vs C E vs H F vs G

第四輪: A vs E B vs F C vs G D vs H

第五輪: A vs F B vs E C vs H D vs G

第六輪: A vs G B vs H C vs E D vs F

第七輪: A vs H B vs G C vs F D vs E

通過這個例子,我們可以看到每支球隊都與其他球隊公平地對抗了一次。FairCycle方法的時鐘模式確保了每支球隊都有相同的機會競爭,沒有球隊被遺漏或被偏好。這種安排方式確保了比賽的公平性,並且每支球隊都有機會面對各種對手,增加了比賽的競爭性。


測試用例:

 1 using NUnit.Framework;
 2 using System;
 3 using System.Linq;
 4 using System.Collections.Generic;
 5 
 6 namespace Solution {
 7   
 8   [TestFixture]
 9   public class SolutionTest
10   {
11     [Test]
12     public void Test2Teams()
13     {
14       var expected = new []{ new []{(1, 2)} };
15 
16       var actual = Tournament.BuildMatchesTable(2);
17       Assert.That(actual, Has.Length.EqualTo(1), "Should have 1 round");
18       Assert.That(actual[0], Has.Length.EqualTo(1), "The round should have 1 match");
19       if(actual[0][0].Item1>actual[0][0].Item2) (actual[0][0].Item1, actual[0][0].Item2) = (actual[0][0].Item2, actual[0][0].Item1);
20       Assert.AreEqual(expected, actual, "The match should be team 1 vs team 2");
21     }
22     
23     [Test]
24     public void Test4Teams() => TestTeams(4);
25     
26     [Test]
27     public void Test20Teams() => TestTeams(20);
28     
29     [Test]
30     public void TestRandom()
31     {
32       Random rand = new Random();
33       
34       TestTeams(2*(3+rand.Next(3)));
35       TestTeams(2*(6+rand.Next(4)));
36     }
37 
38     public void TestTeams(int numberOfTeams)
39     {
40       List<int> teamsExpected = Enumerable.Range(1, numberOfTeams).ToList();
41       HashSet<(int, int)> matchesExpected = new HashSet<(int, int)>();
42       foreach(var round in TournamentSolution.BuildMatchesTable(numberOfTeams))
43       {
44         foreach(var game in round) matchesExpected.Add(game.Item1>game.Item2?(game.Item2,game.Item1):(game.Item1, game.Item2));
45       }
46 
47       var actual = Tournament.BuildMatchesTable(numberOfTeams);
48       Assert.That(actual, Has.Length.EqualTo(numberOfTeams-1), $"Should have {numberOfTeams-1} rounds");
49       foreach(var round in actual)
50       {
51         List<int> teamsByRound = new List<int>();
52         Assert.That(round, Has.Length.EqualTo(numberOfTeams/2), $"Each round should have {numberOfTeams/2} matches");
53         foreach(var game in round)
54         {
55           Assert.That(game, Is.InstanceOf(typeof((int, int))), "Each match is a tupple of 2 teams");
56           teamsByRound.Add(game.Item1);
57           teamsByRound.Add(game.Item2);
58           Assert.True(matchesExpected.Remove(game.Item1>game.Item2?(game.Item2,game.Item1):(game.Item1, game.Item2)), $"{game} is a duplicate or doesn't exist");
59         }
60         teamsByRound.Sort();
61         Assert.AreEqual(teamsExpected, teamsByRound, "Each round should have matches with every team");
62       }
63       Assert.IsEmpty(matchesExpected, "At least one match isn't scheduled");
64     }
65   }
66 }
67 
68 public class TournamentSolution
69 {
70     public static (int, int)[][] BuildMatchesTable(int numberOfTeams)
71     {
72       List<int> teams = Enumerable.Range(1, numberOfTeams).ToList();
73       int roundsNbr = numberOfTeams-1, gamesNbr = numberOfTeams /2, rotatorID = roundsNbr-1, buffer = 0;
74       (int, int)[][] result = new (int, int)[roundsNbr][];
75 
76       for (int i=0; i<roundsNbr; i++)
77       {
78         result[i] = new (int, int)[gamesNbr];
79         
80         for (int j = 0; j < gamesNbr; j++) result[i][j] = (teams[0 + j], teams[roundsNbr - j]);
81 
82         buffer = teams[rotatorID];
83         teams.RemoveAt(rotatorID);
84         teams.Insert(0, buffer);
85       }
86       
87       return result;
88     }
89 }

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 截至目前(2023年),Java8發佈至今已有9年,2018年9月25日,Oracle發佈了Java11,這是Java8之後的首個LTS版本。那麼從JDK8到JDK11,到底帶來了哪些特性呢?值得我們升級嗎?而且升級過程會遇到哪些問題呢?帶著這些問題,本篇文章將帶來完整的JDK8升級JDK11最全實... ...
  • 用Rust手把手編寫一個Proxy(代理), 動工 項目 ++wmproxy++ gitee 傳送門 github 傳送門 設計流程圖 flowchart LR A[客戶端] -->|Http| B[代理端] --> C[代理服務端] --> D[服務端] B -->|直達| D A -->|Htt ...
  • 在學習C#中的記錄類型時,對出現的Equals和ReferenceEquals得到的不同結果表示不理解,隨即進行相關資料查找。 值類型 == : 比較兩者的“內容”是否相同,即“值”是否一樣Equals:比較兩者的“內容”是否相同,即“值”是否一樣ReferenceEquals:返回false,因為 ...
  • 生成者/消費者概念編程模型 通道是生成者/使用者概念編程模型的實現。 在此編程模型中,生成者非同步生成數據,使用者非同步使用該數據。 換句話說,此模型將數據從一方移交給另一方。 嘗試將通道視為任何其他常見的泛型集合類型,例如 List。 主要區別在於,此集合管理同步,並通過工廠創建選項提供各種消耗模型。 ...
  • 在做某個報表管理功能時,有一個需求:需要根據自定義配置的[周起始日]來統計上周、本周的訂單數據。在C#中並沒有封裝的方法根據我們需要來直接獲取上一周某天到某天、本周某天到某天,所以需要我們自己封裝方法來實現(我們也可以按照這個思路使用其他語言來實現)。 ...
  • 前言 百度AI是指百度公司的人工智慧技術全稱。它採用深度學習技術,包括自然語言處理、語音識別、電腦視覺、知識圖譜等,可應用於各個領域如互聯網、醫療、金融、教育、汽車、物流等。百度AI的發展將幫助人類更好地理解世界和提高生活品質,接下來就通過一個小案例演示實現百度AI在文字和圖像敏感審核應用。 項目 ...
  • ASP.NET Core應用程式現在是一個控制台應用程式,在Windows上直接雙擊啟動,但如果想讓開發完成的ASP.NET Core應用程式開機啟動,可以將ASP.NET Core應用程式修改成Windows服務運行,但這需要額外添加代碼,也可以使用IIS來托管ASP.NET Core應用程式,但 ...
  • swagger介面一多,還是需要分個組比較妥當,以圖文方式看更直觀 定義分組 添加分組 看板展示 兩個分組 我要對v1組進行隱藏,首先先瞭解一下 ApplicationModel ApplicationModel描述了應用中的各種對象和行為,包含Application、Controller、Acti ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...