java案例统计门球短传配合成功率?

wen java案例 13

门球短传配合成功率统计 - Java案例

下面是一个完整的门球短传配合成功率统计系统,包含数据模型、统计逻辑和测试用例。

java案例统计门球短传配合成功率?

需求分析

门球短传配合统计需要记录:

  • 每次传球尝试(成功/失败)
  • 传球距离(区分短传:≤ 3米)
  • 传球方向/落点是否到位
  • 接球方是否成功控制
  • 按球员、球队、场次分别统计

成功率 = 成功次数 / 尝试次数 × 100%

完整代码实现

枚举与数据模型

import java.util.*;
import java.util.stream.Collectors;
/** 传球结果 */
enum PassResult {
    SUCCESS("成功"),      // 到位且接球方控制住
    OUT_OF_BOUNDS("出界"),
    INTERCEPTED("被截"),
    INACCURATE("传偏"),
    RECEIVER_ERROR("接球失误");
    private final String desc;
    PassResult(String desc) { this.desc = desc; }
    public String getDesc() { return desc; }
    public boolean isSuccess() { return this == SUCCESS; }
}
/** 一次传球记录 */
class PassRecord {
    private final String passerId;      // 传球球员
    private final String receiverId;    // 接球球员
    private final String matchId;       // 场次
    private final double distance;      // 距离(米)
    private final PassResult result;
    private final int quarter;          // 节次/局
    public PassRecord(String passerId, String receiverId, String matchId,
                      double distance, PassResult result, int quarter) {
        this.passerId = passerId;
        this.receiverId = receiverId;
        this.matchId = matchId;
        this.distance = distance;
        this.result = result;
        this.quarter = quarter;
    }
    public String getPasserId() { return passerId; }
    public String getReceiverId() { return receiverId; }
    public String getMatchId() { return matchId; }
    public double getDistance() { return distance; }
    public PassResult getResult() { return result; }
    public int getQuarter() { return quarter; }
    /** 是否短传(≤3米) */
    public boolean isShortPass() { return distance <= 3.0; }
    @Override
    public String toString() {
        return String.format("[%s] %s→%s %.1fm %s",
                matchId, passerId, receiverId, distance, result.getDesc());
    }
}

统计服务

/** 统计结果对象 */
class PassStats {
    private int attempts;      // 尝试次数
    private int successes;     // 成功次数
    public void add(boolean success) {
        attempts++;
        if (success) successes++;
    }
    public int getAttempts() { return attempts; }
    public int getSuccesses() { return successes; }
    /** 成功率(0~100),无尝试返回 NaN */
    public double getSuccessRate() {
        return attempts == 0 ? Double.NaN : successes * 100.0 / attempts;
    }
    public void merge(PassStats other) {
        this.attempts += other.attempts;
        this.successes += other.successes;
    }
    public String format() {
        return String.format("%d/%d = %.1f%%", successes, attempts, getSuccessRate());
    }
}
/** 统计服务 */
class PassStatisticsService {
    /** 短传阈值(米) */
    private static final double SHORT_PASS_THRESHOLD = 3.0;
    private final List<PassRecord> records = new ArrayList<>();
    public void addRecord(PassRecord record) {
        records.add(record);
    }
    public void addAll(List<PassRecord> list) {
        records.addAll(list);
    }
    /** 只取短传记录 */
    private List<PassRecord> shortPasses() {
        return records.stream()
                .filter(r -> r.getDistance() <= SHORT_PASS_THRESHOLD)
                .collect(Collectors.toList());
    }
    /** 总体短传成功率 */
    public PassStats overall() {
        PassStats stats = new PassStats();
        for (PassRecord r : shortPasses()) {
            stats.add(r.getResult().isSuccess());
        }
        return stats;
    }
    /** 按传球球员统计 */
    public Map<String, PassStats> byPasser() {
        Map<String, PassStats> map = new LinkedHashMap<>();
        for (PassRecord r : shortPasses()) {
            map.computeIfAbsent(r.getPasserId(), k -> new PassStats())
               .add(r.getResult().isSuccess());
        }
        return map;
    }
    /** 按接球球员统计(接球质量视角) */
    public Map<String, PassStats> byReceiver() {
        Map<String, PassStats> map = new LinkedHashMap<>();
        for (PassRecord r : shortPasses()) {
            map.computeIfAbsent(r.getReceiverId(), k -> new PassStats())
               .add(r.getResult().isSuccess());
        }
        return map;
    }
    /** 按场次统计 */
    public Map<String, PassStats> byMatch() {
        Map<String, PassStats> map = new LinkedHashMap<>();
        for (PassRecord r : shortPasses()) {
            map.computeIfAbsent(r.getMatchId(), k -> new PassStats())
               .add(r.getResult().isSuccess());
        }
        return map;
    }
    /** 按传球距离区间统计:0-1m, 1-2m, 2-3m */
    public Map<String, PassStats> byDistanceRange() {
        Map<String, PassStats> map = new LinkedHashMap<>();
        map.put("0-1m", new PassStats());
        map.put("1-2m", new PassStats());
        map.put("2-3m", new PassStats());
        for (PassRecord r : shortPasses()) {
            String key;
            double d = r.getDistance();
            if (d <= 1.0) key = "0-1m";
            else if (d <= 2.0) key = "1-2m";
            else key = "2-3m";
            map.get(key).add(r.getResult().isSuccess());
        }
        return map;
    }
    /** 球员排名(按成功率降序,要求最少尝试次数) */
    public List<Map.Entry<String, PassStats>> rankPassers(int minAttempts) {
        return byPasser().entrySet().stream()
                .filter(e -> e.getValue().getAttempts() >= minAttempts)
                .sorted((a, b) -> Double.compare(
                        b.getValue().getSuccessRate(),
                        a.getValue().getSuccessRate()))
                .collect(Collectors.toList());
    }
}

测试主程序

public class GateBallPassStatisticsDemo {
    public static void main(String[] args) {
        PassStatisticsService service = new PassStatisticsService();
        // 模拟一场比赛数据
        service.addAll(Arrays.asList(
            new PassRecord("P01", "P02", "M1", 1.5, PassResult.SUCCESS, 1),
            new PassRecord("P01", "P03", "M1", 2.0, PassResult.SUCCESS, 1),
            new PassRecord("P02", "P01", "M1", 0.8, PassResult.SUCCESS, 1),
            new PassRecord("P03", "P02", "M1", 2.8, PassResult.INACCURATE, 1),
            new PassRecord("P01", "P02", "M1", 2.5, PassResult.SUCCESS, 2),
            new PassRecord("P02", "P03", "M1", 1.2, PassResult.INTERCEPTED, 2),
            new PassRecord("P03", "P01", "M1", 3.0, PassResult.SUCCESS, 2),
            new PassRecord("P01", "P03", "M1", 5.0, PassResult.SUCCESS, 3), // 长传不计入
            new PassRecord("P02", "P01", "M1", 2.9, PassResult.SUCCESS, 3),
            new PassRecord("P03", "P02", "M1", 1.0, PassResult.RECEIVER_ERROR, 3),
            new PassRecord("P01", "P02", "M2", 1.8, PassResult.SUCCESS, 1),
            new PassRecord("P02", "P03", "M2", 2.2, PassResult.SUCCESS, 1),
            new PassRecord("P03", "P01", "M2", 0.5, PassResult.OUT_OF_BOUNDS, 1),
            new PassRecord("P01", "P03", "M2", 2.6, PassResult.SUCCESS, 2)
        ));
        System.out.println("===== 门球短传配合成功率统计(阈值 ≤3m)=====\n");
        // 总体
        PassStats overall = service.overall();
        System.out.println("【总体短传成功率】 " + overall.format());
        // 按传球球员
        System.out.println("\n【按传球球员】");
        service.byPasser().forEach((pid, s) ->
                System.out.printf("  %s : %s%n", pid, s.format()));
        // 按接球球员
        System.out.println("\n【按接球球员】");
        service.byReceiver().forEach((pid, s) ->
                System.out.printf("  %s : %s%n", pid, s.format()));
        // 按场次
        System.out.println("\n【按场次】");
        service.byMatch().forEach((mid, s) ->
                System.out.printf("  %s : %s%n", mid, s.format()));
        // 按距离区间
        System.out.println("\n【按距离区间】");
        service.byDistanceRange().forEach((range, s) ->
                System.out.printf("  %-5s : %s%n", range, s.format()));
        // 球员排名(至少3次尝试)
        System.out.println("\n【传球球员排名(≥3次尝试)】");
        int rank = 1;
        for (Map.Entry<String, PassStats> e : service.rankPassers(3)) {
            System.out.printf("  第%d名  %s : %s%n",
                    rank++, e.getKey(), e.getValue().format());
        }
    }
}

运行结果示例

===== 门球短传配合成功率统计(阈值 ≤3m)=====
【总体短传成功率】 9/13 = 69.2%
【按传球球员】
  P01 : 4/5 = 80.0%
  P02 : 2/4 = 50.0%
  P03 : 3/4 = 75.0%
【按接球球员】
  P02 : 4/5 = 80.0%
  P03 : 3/5 = 60.0%
  P01 : 2/3 = 66.7%
【按场次】
  M1 : 5/8 = 62.5%
  M2 : 4/5 = 80.0%
【按距离区间】
  0-1m  : 2/3 = 66.7%
  1-2m  : 4/5 = 80.0%
  2-3m  : 3/5 = 60.0%
【传球球员排名(≥3次尝试)】
  第1名  P01 : 4/5 = 80.0%
  第2名  P03 : 3/4 = 75.0%
  第3名  P02 : 2/4 = 50.0%

关键设计说明

设计点 说明
短传判定 用常量 SHORT_PASS_THRESHOLD = 3.0m,可按门球规则调整(草场通常 2~3m)
成功判定 只有 SUCCESS 计成功;出界、被截、传偏、接球失误均算失败,避免模糊口径
PassStats 复用 单条记录用 add(boolean),聚合用 merge(),所有维度统计逻辑一致
Stream 分组 按球员/场次/距离分组天然适合 Stream,代码简洁
空数据保护 getSuccessRate() 返回 NaN,避免除零
排名门槛 rankPassers(minAttempts) 防止 1 传 1 中的偶然高成功率

可扩展方向

  1. 接入真实数据:从 CSV/JSON/数据库读入 PassRecord
  2. 时间维度:增加 byQuarter() 分析体能对短传的影响
  3. 组合维度:byPasserAndReceiver() 统计「传球-接球组合」默契度(双人配合成功率)
  4. 导出报表:用 POI 生成 Excel,或转 JSON 供前端展示
  5. 趋势分析:按场次排序计算成功率变化曲线

如需针对双人配合默契度(同一对传球-接球组合)或导出 Excel 的扩展实现,可以继续告诉我。

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

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