5

目前,这只是我很好奇的事情,我没有任何正在处理的代码,但我想知道如何实现这一点......

例如,我有一个应用程序可以跟踪世界上所有足球队的结果。我想要做的是确定任何给定球队最长的“连胜”纪录。

我想我很可能会有这样的某种数据表:

  • MatchDate 日期时间
  • TeamA 字符串
  • TeamB 字符串
  • TeamAGoals int
  • TeamBGoals int

因此,例如,我想做的是找到最长的连胜纪录,TeamA = "My Team"显然这意味着TeamAGoals必须大于TeamBGoals.

正如我所说,这只是举例。对于这样的事情,不同的数据库设计可能会更好。但根本问题是如何计算最长的连续/运行匹配结果。

4

4 回答 4

4

这是一个老问题,但我只需要自己解决同样的问题,并且认为人们可能会对 Rawling 的 LongestStreak 扩展方法的完全 LINQ 实现感兴趣。这使用带有种子和结果选择器的聚合来遍历列表。

    public static int LongestStreak<TSource>(
        this IEnumerable<TSource> source,
        Func<TSource, bool> predicate)
    {
        return source.Aggregate(
            new {Longest = 0, Current = 0},
            (agg, element) => predicate(element) ? 
                new {Longest = Math.Max(agg.Longest, agg.Current + 1), Current = agg.Current + 1} : 
                new {agg.Longest, Current = 0},
            agg => agg.Longest);
    }
于 2015-03-11T08:13:12.493 回答
3

没有开箱即用的 LINQ 方法来计算条纹,因此您需要自定义 LINQy 方法,例如

public static int LongestStreak<TSource>(
    this IEnumerable<TSource> source,
    Func<TSource, bool> predicate)
{
    int longestStreak = 0;
    int currentStreak = 0;
    foreach (TSource s in source)
    {
        if (predicate(s))
            currentStreak++;
        else
        {
            if (currentStreak > longestStreak) longestStreak = currentStreak;
            currentStreak = 0;
        }
    }
    if (currentStreak > longestStreak) longestStreak = currentStreak;
    return longestStreak;
}

然后,要使用它,首先将每个“比赛结果”变成一对“团队结果”。

var teamResults = matches.SelectMany(m => new[] {
        new {
            MatchDate = m.MatchDate,
            Team = m.TeamA,
            Won = m.TeamAGoals > m.TeamBGoals },
        new {
            MatchDate = m.MatchDate,
            Team = m.TeamB,
            Won = m.TeamBGoals > m.TeamAGoals }
    });

按团队对这些进行分组。

var groupedResults = teamResults.GroupBy(r => r.Team);

然后计算条纹。

var streaks = groupedResults.Select(g => new
    {
        Team = g.Key,
        StreakLength = g
            // unnecessary if the matches were ordered originally
            .OrderBy(r => r.MatchDate)
            .LongestStreak(r => r.Won)
    });

如果您只想要最长的连胜,请使用MoreLinq'sMaxBy ; 如果您想要全部订购,您可以使用OrderByDescending(s => s.StreakLength).

或者,如果您想一次性完成此操作,并且假设matches已订购,请使用以下类

class StreakAggregator<TKey>
{
    public Dictionary<TKey, int> Best = new Dictionary<TKey, int>();
    public Dictionary<TKey, int> Current = new Dictionary<TKey, int>();

    public StreakAggregator<TKey> UpdateWith(TKey key, bool success)
    {
        int c = 0;
        Current.TryGetValue(key, out c);
        if (success)
        {
            Current[key] = c + 1;
        }
        else
        {
            int b = 0;
            Best.TryGetValue(key, out b);
            if (c > b)
            {
                Best[key] = c;
            }
            Current[key] = 0;
        }
        return this;
    }

    public StreakAggregator<TKey> Finalise()
    {
        foreach (TKey k in Current.Keys.ToArray())
        {
            UpdateWith(k, false);
        }
        return this;
    }
}

然后你可以做

var streaks = teamResults.Aggregate(
    new StreakAggregator<string>(),
    (a, r) => a.UpdateWith(r.Team, r.Won),
    (a)    => a.Finalise().Best.Select(kvp => 
        new { Team = kvp.Key, StreakLength = kvp.Value }));

OrderBy或任何像以前一样。

于 2012-10-16T11:11:01.480 回答
2

您可以通过单个查询获得团队的所有结果:

var results = from m in Matches
            let homeMatch = m.TeamA == teamName
            let awayMatch = m.TeamB == teamName
            let hasWon = (homeMatch && m.TeamAGoals > m.TeamBGoals) || 
                         (awayMatch && m.TeamBGoals > m.TeamAGoals)
            where homeMatch || awayMatch
            orderby m.MatchDate
            select hasWon;

然后只需简单计算最长条纹:

int longestStreak = 0;
int currentStreak = 0;

foreach (var hasWon in results)
{
    if (hasWon)
    {
        currentStreak++;
        if (currentStreak > longestStreak)
            longestStreak = currentStreak;

        continue;
    }

    currentStreak = 0;
}

您可以按原样使用它、提取到方法或创建 IEnumerable 扩展来计算结果中的最长序列。

于 2012-10-16T12:11:47.883 回答
2

你可以利用string.Split. 像这样的东西:

int longestStreak = 
    string.Concat(results.Select(r => (r.ours > r.theirs) ? "1" : "0"))
          .Split(new[] { '0' })
          .Max(s => s.Length);

或者,更好的是,创建一个Split扩展方法IEnumerable<T>以避免需要通过字符串,如下所示:

public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> items, Predicate<T> p)
{
    while (true)
    {
        items = items.SkipWhile(i => !p(i));
        var trueItems = items.TakeWhile (i => p(i)).ToList();
        if (trueItems.Count > 0)
        {
            yield return trueItems;
            items = items.Skip(trueItems.Count);
        }
        else
        {
            break;
        }
    }   
}

然后你可以简单地这样做:

int longestStreak = results.Split(r => r.ours > r.theirs).Max(g => g.Count());
于 2012-10-16T12:25:52.843 回答