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

解惑,孜孜不倦,足球,赛程,详解,c#,yield,return,用法 · 浏览次数 : 433

小编点评

1. **测试用例** * `Test2Teams` * `Test4Teams` * `Test20Teams` * `TestRandom` 2. **方法** * `BuildMatchesTable` 3. **排版** * `result`是一个二维数组,每个元素包含两个元素,分别代表 team 1 和 team 2 4. **排版示例** ``` result = new (int, int)[roundsNbr][]; 86 for (int i = 0; i < roundsNbr; i++) 87 result[i] = new (int, int)[gamesNbr]; 88 80 for (int j = 0; j < gamesNbr; j++) result[i][j] = (teams[0 + j], teams[roundsNbr - j]); 89 buffer = teams[rotatorID]; 89 teams.RemoveAt(rotatorID); 89 teams.Insert(0, buffer); ```

正文

在一个知名企业赞助的足球联赛中,有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 }

 

与【解惑】孜孜不倦,用足球赛程详解c#中的yield return用法相似的内容:

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

在一个知名企业赞助的足球联赛中,有256支球队参赛。为了确保比赛的顺利进行,企业指派了小悦负责熬夜加班制定每一个球队的赛程。尽管她对足球的了解并不多,但是她对待工作的认真态度却让人钦佩。 在小悦的努力下,她顺利完成了第一轮、第二轮和第三轮的比赛安排。然而,在大赛开始前的模拟比赛中,她发现了一个严重的

【解惑】介绍.net中的DataTable的AcceptChanges方法

`DataTable.AcceptChanges`方法是一个用于`DataTable`对象的方法,它将所有对`DataTable`进行的更改标记为已接受。这意味着所有新增、修改和删除的行都将被标记为`DataRowState.Unchanged`,并且`DataTable`对象的`HasChange

【解惑】介绍三大数据库的with语句的写法及使用场景

WITH 子句通常被称为 "Common Table Expressions"(CTE),俗称内存临时表,当使用 WITH 语句时,应注意具体的数据库版本和支持情况。以下是对 MySQL、Microsoft SQL Server(MSSQL)和 Oracle 数据库的 WITH 语句用法示例,以及在

【解惑】当处理同一个字段的并发问题时,使用乐观锁来处理库存数量

以下是一个使用乐观锁处理库存数量并发问题的c#示例代码: ```csharp using System; using System.Data; using System.Data.SqlClient; public class InventoryService { private string co

【解惑】时间规划,Linq的Aggregate函数在计算会议重叠时间中的应用

在繁忙的周五,小悦坐在会议室里,面前摆满了各种文件和会议安排表。她今天的工作任务是为公司安排下周的50个小会议,这让她感到有些头疼。但是,她深吸了一口气,决定耐心地一个一个去处理。 首先,小悦仔细地收集了每个会议的相关信息,包括会议的主题、目的、预计参加人数、所需设备和预计的开始和结束时间等。她需要

[转帖]RabbitMQ erlang.cookie解惑

https://www.cnblogs.com/xgtx/articles/6068392.html 在搭建RabbitMQ集群的时候往往会因为.erlang.cookie而报各种错误,网上查资料也会经常说.erlang.cookie会在$home下,或者在/var/lib/rabbitmq下,到底

[转帖] 字符编码解惑

原创:打码日记(微信公众号ID:codelogs),欢迎分享,转载请保留出处。 简介# 现代编程语言都抽象出了String字符串这个概念,注意它是一个高级抽象,但是计算机中实际表示信息时,都是用的字节,所以就需要一种机制,让字符串与字节之间可以相互转换,这种转换机制就是字符编码,如GBK,UTF-8

[转帖]字符编码解惑

https://www.cnblogs.com/codelogs/p/16060234.html 简介# 现代编程语言都抽象出了String字符串这个概念,注意它是一个高级抽象,但是计算机中实际表示信息时,都是用的字节,所以就需要一种机制,让字符串与字节之间可以相互转换,这种转换机制就是字符编码,如

聊一聊如何截获 C# 程序产生的日志

一:背景 1.讲故事 前段时间分析了一个dump,一顿操作之后,我希望用外力来阻止程序内部对某一个com组件的调用,对,就是想借助外力实现,如果用 windbg 的话,可以说非常轻松,但现实情况比较复杂,客户机没有windbg,也不想加入任何的手工配置,希望全自动化来处理。 真的很无理哈。。。不过这

《爆肝整理》保姆级系列教程-玩转Charles抓包神器教程(6)-Charles安卓手机抓包大揭秘

1.简介 Charles和Fiddler一样不但能截获各种浏览器发出的 HTTP 请求,也可以截获各种智能手机发出的HTTP/ HTTPS 请求。 Charles也能截获 Android 和 Windows Phone 等设备发出的 HTTP/HTTPS 请求。 今天宏哥讲解和分享Charles如何