java案例统计马赛回旋使用频率如何?

wen java案例 3

用Java统计"马赛回旋"使用频率的案例分析

"马赛回旋"(Roulette/Marseille Turn)是足球中的经典过人动作,下面我给出一个完整可运行的Java案例,演示如何统计该动作的使用频率。

java案例统计马赛回旋使用频率如何?

需求分析

假设我们有一份比赛事件数据(球员、时间、动作类型),需要统计:

  • 每名球员使用"马赛回旋"的次数
  • 全队/全场使用频率(次/分钟)
  • 按位置或时间段分布

领域模型

import java.time.LocalTime;
import java.util.*;
// 动作类型枚举
enum ActionType {
    MARSEILLE_ROULETTE,  // 马赛回旋
    PASS, SHOT, DRIBBLE, TACKLE
}
// 比赛事件
record MatchEvent(String player, ActionType action, LocalTime time, int minute) {}
// 球员信息
record Player(String name, String position) {}  // position: FW/MF/DF

统计服务

import java.util.*;
import java.util.stream.*;
public class RouletteStatsService {
    private final List<MatchEvent> events;
    private final List<Player> players;
    private final int matchDurationMinutes;
    public RouletteStatsService(List<MatchEvent> events,
                                List<Player> players,
                                int matchDurationMinutes) {
        this.events = events;
        this.players = players;
        this.matchDurationMinutes = matchDurationMinutes;
    }
    /** 1. 每名球员使用马赛回旋次数 */
    public Map<String, Long> countByPlayer() {
        return events.stream()
                .filter(e -> e.action() == ActionType.MARSEILLE_ROULETTE)
                .collect(Collectors.groupingBy(
                        MatchEvent::player,
                        Collectors.counting()));
    }
    /** 2. 全场总次数与频率(次/分钟) */
    public double overallFrequencyPerMinute() {
        long total = events.stream()
                .filter(e -> e.action() == ActionType.MARSEILLE_ROULETTE)
                .count();
        return total / (double) matchDurationMinutes;
    }
    /** 3. 按位置聚合(需要 join 球员信息) */
    public Map<String, Long> countByPosition() {
        Map<String, String> nameToPosition = players.stream()
                .collect(Collectors.toMap(Player::name, Player::position));
        return events.stream()
                .filter(e -> e.action() == ActionType.MARSEILLE_ROULETTE)
                .collect(Collectors.groupingBy(
                        e -> nameToPosition.getOrDefault(e.player(), "UNKNOWN"),
                        Collectors.counting()));
    }
    /** 4. 按时间段分布(每15分钟一档) */
    public Map<String, Long> countByTimeBucket() {
        return events.stream()
                .filter(e -> e.action() == ActionType.MARSEILLE_ROULETTE)
                .collect(Collectors.groupingBy(
                        e -> {
                            int bucket = e.minute() / 15;
                            int start = bucket * 15;
                            return start + "-" + (start + 15) + "min";
                        },
                        TreeMap::new,
                        Collectors.counting()));
    }
    /** 5. 使用频率最高的 Top-N 球员 */
    public List<Map.Entry<String, Long>> topPlayers(int n) {
        return countByPlayer().entrySet().stream()
                .sorted(Map.Entry.<String, Long>comparingByValue().reversed())
                .limit(n)
                .toList();
    }
}

测试与演示

import java.time.LocalTime;
import java.util.*;
public class Main {
    public static void main(String[] args) {
        List<Player> players = List.of(
                new Player("Messi",  "FW"),
                new Player("Zidane", "MF"),
                new Player("Neymar", "FW"),
                new Player("Pirlo",  "MF"),
                new Player("Cannavaro", "DF")
        );
        List<MatchEvent> events = List.of(
                new MatchEvent("Zidane", ActionType.MARSEILLE_ROULETTE, LocalTime.of(0,12), 12),
                new MatchEvent("Zidane", ActionType.MARSEILLE_ROULETTE, LocalTime.of(0,25), 25),
                new MatchEvent("Messi",  ActionType.MARSEILLE_ROULETTE, LocalTime.of(0,45), 45),
                new MatchEvent("Neymar", ActionType.MARSEILLE_ROULETTE, LocalTime.of(1,3), 63),
                new MatchEvent("Messi",  ActionType.MARSEILLE_ROULETTE, LocalTime.of(1,20), 80),
                new MatchEvent("Messi",  ActionType.MARSEILLE_ROULETTE, LocalTime.of(1,30), 90),
                new MatchEvent("Pirlo",  ActionType.PASS,   LocalTime.of(0,30), 30),
                new MatchEvent("Neymar", ActionType.SHOT,   LocalTime.of(0,40), 40)
        );
        var service = new RouletteStatsService(events, players, 90);
        System.out.println("== 球员使用次数 ==");
        service.countByPlayer().forEach((p, c) ->
                System.out.printf("%-8s : %d%n", p, c));
        System.out.printf("%n全场频率: %.3f 次/分钟%n",
                service.overallFrequencyPerMinute());
        System.out.println("\n== 按位置分布 ==");
        service.countByPosition().forEach((pos, c) ->
                System.out.printf("%-8s : %d%n", pos, c));
        System.out.println("\n== 时间段分布 ==");
        service.countByTimeBucket().forEach((k, v) ->
                System.out.printf("%-12s : %d%n", k, v));
        System.out.println("\n== Top 2 球员 ==");
        service.topPlayers(2).forEach(e ->
                System.out.printf("%-8s : %d%n", e.getKey(), e.getValue()));
    }
}

预期输出

== 球员使用次数 ==
Messi    : 3
Neymar   : 1
Zidane   : 2
全场频率: 0.067 次/分钟
== 按位置分布 ==
FW       : 4
MF       : 2
== 时间段分布 ==
0-15min      : 1
15-30min     : 1
30-45min     : 0
45-60min     : 1
60-75min     : 1
75-90min     : 2
== Top 2 球员 ==
Messi    : 3
Zidane   : 2

扩展建议

扩展点 实现思路
成功率统计 每个 MatchEvent 加 success 字段,counting 前按成功过滤
对手维度 事件加 opponent 字段,用二级 groupingBy
数据来源 从 CSV/JSON 或 Opta/StatsBomb API 加载 → List<MatchEvent>
持久化 用 JPA 存储,@Query("... where action = 'MARSEILLE_ROULETTE' group by player")
可视化 导出为 Map/JSON,前端用 Chart.js 画柱状图
大数据量 用 parallelStream 或迁移到 Spark/Flink 做流式统计

关键设计要点

  1. record + 枚举:数据不可变、语义清晰,避免魔法字符串
  2. Stream 分组:groupingBy + counting 是频率统计的黄金组合
  3. 关注点分离:领域模型、统计服务、入口解耦,便于单元测试
  4. 持续统计:若是实时场景,可将 List 换成 ConcurrentHashMap<String, LongAdder>,边比赛边累加

如需适配真实足球数据源(如 StatsBomb 的 events.json)或增加实时流式统计(如 Kafka + Flink),可以继续告诉我,我给出对应版本。

上一篇java案例认为这次单刀球处理得如何?

下一篇当前分类已是最新一篇

抱歉,评论功能暂时关闭!

友情链接攻略窝 · 礼包窝 · 配置窝 · 换算窝 · 算法窝 · 查文窝 · 手册窝 · 问答窝 · 全文搜 · 手册库
相关站点礼包窝 · 查文窝