c# - 对列表求和,但仅适用于某个点之后的值
问题描述
我正在写一个网页来保存纸牌游戏的分数。到目前为止,让球员得分很容易,但有一个转折点。在任何一轮中,玩家的得分都可以在该轮开始时重置为零。我不想更改任何前几轮的分数,所以我只想在重置之后(包括)获得总和。玩家可能会在游戏中多次重置他们的分数,或者根本不会。
我可以通过查找最后一次(如果有的话)分数重置的多阶段过程来获得正确的分数,然后将所有手牌相加(或所有手牌没有重置) - 请参阅 PlayerGame.GetPlayerScore。
我仍在尝试了解使用 LINQ 做事的更复杂的方法,我想知道是否有一种方法可以使用单个 LINQ 语句来做到这一点?
最小代码:
class Program
{
static void Main(string[] args)
{
PlayerGame playerGame = new PlayerGame();
playerGame.PlayerHands = new List<PlayerHand>
{
new PlayerHand { Round = 1, Score = 10 },
new PlayerHand { Round = 2, Score = 20 },
new PlayerHand { Round = 3, Score = 30 },
new PlayerHand { Round = 4, Score = 40, Reset = true },
new PlayerHand { Round = 5, Score = 50 },
new PlayerHand { Round = 6, Score = 60 }
};
Console.WriteLine($"Players score was {playerGame.GetPlayerScore()}");
Console.ReadLine();
}
}
class PlayerHand
{
public int Round { get; set; }
public int Score { get; set; }
public bool Reset { get; set; } = false;
}
class PlayerGame
{
public List<PlayerHand> PlayerHands { get; set; }
public PlayerGame()
{
PlayerHands = new List<PlayerHand> { };
}
public int GetPlayerScore()
{
// Can all this be simplified to a single LINQ statement?
var ResetIndex = PlayerHands.OrderBy(t => t.Round).LastOrDefault(t => t.Reset == true);
if (ResetIndex != null)
{
return PlayerHands.Where(t => t.Round >= ResetIndex.Round).Sum(t => t.Score);
}
else
{
return PlayerHands.Sum(t => t.Score);
}
}
}
https://dotnetfiddle.net/s5rSqJ
如图所示,玩家的分数应该是 150。即分数在第 4 回合开始时重置,因此总分是第 4、5 和 6 回合的总和。
解决方案
总结几点,
- 回合数是有限的(否则一场非常漫长的比赛!)。当我们在下面讨论反转时,这一观察很重要。
- 回合已经按升序排序(根据评论),因此实际回合数无关紧要
- 如果我们向后求和,我们不必扫描整个列表
因此,我们可以提出一个实现,即O(1)空间(就地,无分配)和O(n)时间(线性,小于重置时列表的大小)。
使用 MoreLinq
var score = hands.ReverseInPlace().TakeUntil(x => x.Reset).Sum(x => x.Score);
其中ReverseInPlace()
以相反的顺序进行迭代,并且MoreEnumerable.TakeUntil()占用并包括具有真值Reset
或序列结束的轮次。
ReverseInPlace
将是一种扩展方法(IEnumerable<>
如果需要,您可以推广到)。
public static class ListExtensions
{
public static IEnumerable<T> ReverseInPlace<T>(this IList<T> source)
{
// add guard checks here, then do...
for (int i=source.Length-1; i != -1; --i)
yield return source[i];
}
}
不使用 MoreLinq
您可以创建一个TakeInReverseUntil
:
public static IEnumerable<T> TakeInReverseUntil<T>(this IList<T> source, Func<T, bool> predicate)
{
// add guard checks here, then do...
for (int i=source.Length-1; i != -1; --i)
{
yield return source[i];
if (predicate(source[i]) yield break;
}
}
给你简化电话
var score = hands.TakeInReverseUntil(x => x.Reset).Sum(x => x.Score);
注意:Enumerable.Reverse()分配一个缓冲区,O(n)空间也是如此,这就是为什么我自己滚动ReverseInPlace
来代替这个答案。
推荐阅读
- python - 如何在循环中创建一个包含我想要的数字的列表?
- java - Bigquery 查询 API - 数组问题
- java - 使用页面工厂设计模式在页面对象中第二次调用 web 元素会给出过时元素异常
- java - 当收集子类的最后一个实例时,是否会收集抽象超类垃圾?
- c++ - 指针越界导致分段错误c ++
- c# - 将 enum 作为函数参数传递,将其作为 int 处理,并返回 enum
- html - 如何通过 $http.get() 从 .json 文件获取数据到 html 文件
- javascript - 单击按钮时更改视图
- python - Spotipy-为什么在获取保存的歌曲时有 50 首歌曲的限制?
- r - 使用R中的for循环填充要列出的数据框