java案例统计界外球进攻威胁次数?

wen java案例 5

Java案例:统计界外球进攻威胁次数

下面是一个完整的Java案例,用于统计足球比赛中界外球进攻产生的威胁次数(如射门、角球、进球等)。

java案例统计界外球进攻威胁次数?

需求分析

界外球(Throw-in)进攻威胁通常指:

  • 界外球掷出后 N 秒内(如15秒)形成的射门
  • 界外球掷出后形成的角球、进球
  • 界外球直接造成对方解围失误

这里我们模拟一段比赛事件流,识别"界外球 → 后续威胁事件"的关联。

核心代码

事件模型

import java.time.LocalTime;
import java.util.*;
// 事件类型
enum EventType {
    THROW_IN,      // 界外球
    SHOT,          // 射门
    SHOT_ON_TARGET,// 射正
    GOAL,          // 进球
    CORNER,        // 角球
    FOUL           // 犯规
}
// 比赛事件
class MatchEvent {
    private final int minute;          // 比赛分钟
    private final int second;          // 秒
    private final EventType type;
    private final String team;         // 进攻方
    private final String detail;
    public MatchEvent(int minute, int second, EventType type, String team, String detail) {
        this.minute = minute;
        this.second = second;
        this.type = type;
        this.team = team;
        this.detail = detail;
    }
    public int getAbsoluteSeconds() { return minute * 60 + second; }
    public EventType getType() { return type; }
    public String getTeam() { return team; }
    public String getDetail() { return detail; }
    @Override
    public String toString() {
        return String.format("%02d:%02d %s [%s] %s",
                minute, second, team, type, detail);
    }
}

威胁统计器

public class ThrowInThreatAnalyzer {
    /** 界外球后多长时间内的事件算作威胁(秒) */
    private static final int THREAT_WINDOW_SECONDS = 15;
    /** 视为威胁的事件类型 */
    private static final Set<EventType> THREAT_TYPES = EnumSet.of(
            EventType.SHOT,
            EventType.SHOT_ON_TARGET,
            EventType.GOAL,
            EventType.CORNER
    );
    private final List<MatchEvent> events;
    public ThrowInThreatAnalyzer(List<MatchEvent> events) {
        this.events = new ArrayList<>(events);
        // 按时间排序
        this.events.sort(Comparator.comparingInt(MatchEvent::getAbsoluteSeconds));
    }
    /**
     * 统计每个队伍的界外球进攻威胁次数
     */
    public Map<String, Integer> countThreatsByTeam() {
        Map<String, Integer> result = new HashMap<>();
        for (int i = 0; i < events.size(); i++) {
            MatchEvent throwIn = events.get(i);
            if (throwIn.getType() != EventType.THROW_IN) continue;
            String team = throwIn.getTeam();
            int windowStart = throwIn.getAbsoluteSeconds();
            int windowEnd = windowStart + THREAT_WINDOW_SECONDS;
            // 向后查找威胁事件
            for (int j = i + 1; j < events.size(); j++) {
                MatchEvent next = events.get(j);
                if (next.getAbsoluteSeconds() > windowEnd) break;
                // 只统计进攻方自己造成的威胁
                if (!next.getTeam().equals(team)) continue;
                if (THREAT_TYPES.contains(next.getType())) {
                    result.merge(team, 1, Integer::sum);
                    break; // 一次界外球只算一次威胁
                }
            }
        }
        return result;
    }
    /**
     * 详细输出每次威胁
     */
    public void reportThreats() {
        for (int i = 0; i < events.size(); i++) {
            MatchEvent throwIn = events.get(i);
            if (throwIn.getType() != EventType.THROW_IN) continue;
            String team = throwIn.getTeam();
            int windowEnd = throwIn.getAbsoluteSeconds() + THREAT_WINDOW_SECONDS;
            for (int j = i + 1; j < events.size(); j++) {
                MatchEvent next = events.get(j);
                if (next.getAbsoluteSeconds() > windowEnd) break;
                if (!next.getTeam().equals(team)) continue;
                if (THREAT_TYPES.contains(next.getType())) {
                    System.out.printf("✅ %s 界外球(%02d:%02d) → 威胁 (%s %02d:%02d, %s)%n",
                            team, throwIn.getAbsoluteSeconds() / 60,
                            throwIn.getAbsoluteSeconds() % 60,
                            next.getType(), next.getAbsoluteSeconds() / 60,
                            next.getAbsoluteSeconds() % 60, next.getDetail());
                    break;
                }
            }
        }
    }
    /**
     * 算出威胁转化率 = 威胁次数 / 界外球总数
     */
    public Map<String, Double> conversionRate() {
        Map<String, Integer> threatCount = countThreatsByTeam();
        Map<String, Integer> throwInCount = new HashMap<>();
        for (MatchEvent e : events) {
            if (e.getType() == EventType.THROW_IN) {
                throwInCount.merge(e.getTeam(), 1, Integer::sum);
            }
        }
        Map<String, Double> rate = new HashMap<>();
        for (Map.Entry<String, Integer> entry : throwInCount.entrySet()) {
            String team = entry.getKey();
            int threats = threatCount.getOrDefault(team, 0);
            rate.put(team, threats * 100.0 / entry.getValue());
        }
        return rate;
    }
}

测试入口

public class Main {
    public static void main(String[] args) {
        List<MatchEvent> events = new ArrayList<>();
        // 模拟一场比赛事件流
        events.add(new MatchEvent(10, 15, EventType.THROW_IN, "中国队", "左侧边线"));
        events.add(new MatchEvent(10, 22, EventType.SHOT, "中国队", "禁区内抽射"));
        events.add(new MatchEvent(10, 30, EventType.CORNER, "中国队", "射门被挡出"));
        events.add(new MatchEvent(20, 5, EventType.THROW_IN, "中国队", "右侧边线"));
        events.add(new MatchEvent(20, 40, EventType.SHOT, "中国队", "远射")); // 超过15秒,不算
        events.add(new MatchEvent(32, 10, EventType.THROW_IN, "日本队", "右侧边线"));
        events.add(new MatchEvent(32, 18, EventType.GOAL, "日本队", "头球破门"));
        events.add(new MatchEvent(45, 0, EventType.THROW_IN, "日本队", "左侧边线"));
        events.add(new MatchEvent(45, 10, EventType.FOUL, "日本队", "对方犯规"));
        // 事件流结束
        events.add(new MatchEvent(60, 0, EventType.THROW_IN, "中国队", "左侧边线"));
        events.add(new MatchEvent(60, 8, EventType.SHOT_ON_TARGET, "中国队", "射正被扑"));
        ThrowInThreatAnalyzer analyzer = new ThrowInThreatAnalyzer(events);
        System.out.println("====== 界外球进攻威胁明细 ======");
        analyzer.reportThreats();
        System.out.println("\n====== 各队威胁次数 ======");
        analyzer.countThreatsByTeam().forEach((k, v) ->
                System.out.println(k + ": " + v + " 次"));
        System.out.println("\n====== 各队界外球威胁转化率 ======");
        analyzer.conversionRate().forEach((k, v) ->
                System.out.printf("%s: %.1f%%%n", k, v));
    }
}

运行结果

====== 界外球进攻威胁明细 ======
✅ 中国队 界外球(10:15) → 威胁 (SHOT 10:22, 禁区内抽射)
✅ 日本队 界外球(32:10) → 威胁 (GOAL 32:18, 头球破门)
✅ 中国队 界外球(60:00) → 威胁 (SHOT_ON_TARGET 60:08, 射正被扑)
====== 各队威胁次数 ======
中国队: 2 次
日本队: 1 次
====== 各队界外球威胁转化率 ======
中国队: 66.7%
日本队: 50.0%

设计要点

要点 说明
时间窗口 用 THREAT_WINDOW_SECONDS 可配置(默认15秒),便于分析不同战术风格
进攻方判定 只统计"同队"后续事件,防止对方反击误判
一次界外球一次威胁 用 break 避免单次界外球被多次计数
可扩展 可加入 xG(预期进球)加权、区域过滤、对手强度等维度
事件模型解耦 MatchEvent 与统计逻辑分离,方便接入真实数据源(如Opta、Wyscout)

扩展建议

  1. xG加权威胁:把 SHOT 按射门位置的 xG 值加权,威胁次数变成"威胁值"。
  2. 时间窗口:可分别统计 5s / 10s / 15s 窗口内的威胁,做曲线对比。
  3. 按场区统计:前场界外球威胁概率通常远高于后场,可细分统计。
  4. 数据接入:将读取事件流的部分改成从 CSV / JSON / 数据库读取,直接套用于真实比赛数据。

如果你有具体的数据格式(CSV/JSON)或想统计特定赛制的界外球威胁,可以告诉我,我再给你改成对应版本。

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

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