如何使用LINQ计算最长条纹?

时间:2012-10-16 10:40:26

标签: c# linq

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

让我们举例来说,我有一个应用程序可以追踪世界上所有足球队的成绩。我希望能够确定任何特定球队最长的“胜利”连胜。

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

  • MatchDate datetime
  • TeamA字符串
  • TeamB字符串
  • TeamAGoals int
  • TeamBGoals int

所以我想要做的就是找到最长的连胜点TeamA = "My Team",显然这意味着TeamAGoals必须大于TeamBGoals

正如我所说,这只是一个例子。对于这样的事情,对于不同的DB设计可能更好。但根本问题是如何计算匹配结果的最长条纹/连续运行。

4 个答案:

答案 0 :(得分:4)

现在这是一个老问题,但我必须自己解决同样的问题,并认为人们可能对Rawling的LongestStreak扩展方法的完全LINQ实现感兴趣。这使用带有种子和结果选择器的Aggregate来运行列表。

    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);
    }

答案 1 :(得分: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's MaxBy;如果您想要全部订购,可以使用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或之前的任何内容。

答案 2 :(得分: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;
}

您可以按原样使用它,提取到方法,或c reate IEnumerable extension来计算结果中的最长序列。

答案 3 :(得分: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());