java案例统计落叶球射门成功率如何?

wen java案例 2

足球"落叶球"射门成功率统计 - Java案例分析

需求分析

"落叶球"(Knuckleball/电梯球)是足球中的一种特殊射门技术,球在空中飘忽不定、突然下坠,守门员难以判断,要统计其成功率,需要:

java案例统计落叶球射门成功率如何?

  • 记录每次落叶球射门的结果(进球/未进球)
  • 按球员、比赛、射门区域等维度统计
  • 计算成功率 = 进球数 / 总射门数 × 100%

领域模型设计

// 射门结果枚举
public enum ShotResult {
    GOAL,           // 进球
    SAVED,          // 被扑出
    OFF_TARGET,     // 偏出
    BLOCKED,        // 被封堵
    HIT_POST        // 中柱/横梁
}
// 射门记录
public class ShotRecord {
    private String playerId;
    private String playerName;
    private String matchId;
    private int minute;              // 比赛分钟
    private double distance;         // 射门距离(米)
    private ShotType shotType;       // 射门类型
    private ShotResult result;
    // 构造器、getter、setter 省略
}
// 射门类型
public enum ShotType {
    KNUCKLEBALL,   // 落叶球
    CURVE,         // 弧线球
    POWER,         // 大力抽射
    PLACED         // 推射
}

核心统计代码

import java.util.*;
import java.util.stream.Collectors;
public class KnuckleballStatsService {
    /**
     * 统计某球员落叶球成功率
     */
    public double calculateSuccessRate(List<ShotRecord> records, String playerId) {
        List<ShotRecord> knuckleShots = records.stream()
                .filter(r -> r.getShotType() == ShotType.KNUCKLEBALL)
                .filter(r -> playerId == null || r.getPlayerId().equals(playerId))
                .collect(Collectors.toList());
        if (knuckleShots.isEmpty()) return 0.0;
        long goals = knuckleShots.stream()
                .filter(r -> r.getResult() == ShotResult.GOAL)
                .count();
        return (double) goals / knuckleShots.size() * 100;
    }
    /**
     * 按球员分组统计落叶球成功率
     */
    public Map<String, Double> successRateByPlayer(List<ShotRecord> records) {
        Map<String, List<ShotRecord>> grouped = records.stream()
                .filter(r -> r.getShotType() == ShotType.KNUCKLEBALL)
                .collect(Collectors.groupingBy(ShotRecord::getPlayerName));
        return grouped.entrySet().stream()
                .collect(Collectors.toMap(
                        Map.Entry::getKey,
                        e -> {
                            long goals = e.getValue().stream()
                                    .filter(r -> r.getResult() == ShotResult.GOAL)
                                    .count();
                            return Math.round((double) goals / e.getValue().size() * 10000) / 100.0;
                        }
                ));
    }
    /**
     * 按距离区间统计 - 分析落叶球最佳射程
     */
    public Map<String, Double> successRateByDistance(List<ShotRecord> records) {
        Map<String, List<ShotRecord>> grouped = records.stream()
                .filter(r -> r.getShotType() == ShotType.KNUCKLEBALL)
                .collect(Collectors.groupingBy(r -> {
                    double d = r.getDistance();
                    if (d < 20) return "近距离(<20m)";
                    else if (d < 25) return "中距离(20-25m)";
                    else if (d < 30) return "远距离(25-30m)";
                    else return "超远距离(>30m)";
                }));
        return grouped.entrySet().stream()
                .collect(Collectors.toMap(
                        Map.Entry::getKey,
                        e -> {
                            long goals = e.getValue().stream()
                                    .filter(r -> r.getResult() == ShotResult.GOAL)
                                    .count();
                            return Math.round((double) goals / e.getValue().size() * 10000) / 100.0;
                        }
                ));
    }
}

测试用例

public class KnuckleballStatsTest {
    public static void main(String[] args) {
        List<ShotRecord> records = new ArrayList<>();
        // 模拟数据: C罗 5次落叶球,3次进球
        records.add(new ShotRecord("P01", "C罗", "M01", 30, 27.5, ShotType.KNUCKLEBALL, ShotResult.GOAL));
        records.add(new ShotRecord("P01", "C罗", "M02", 45, 25.0, ShotType.KNUCKLEBALL, ShotResult.GOAL));
        records.add(new ShotRecord("P01", "C罗", "M03", 60, 32.0, ShotType.KNUCKLEBALL, ShotResult.SAVED));
        records.add(new ShotRecord("P01", "C罗", "M04", 15, 28.0, ShotType.KNUCKLEBALL, ShotResult.GOAL));
        records.add(new ShotRecord("P01", "C罗", "M05", 78, 22.0, ShotType.KNUCKLEBALL, ShotResult.HIT_POST));
        // 模拟数据: 某普通球员 3次落叶球,0次进球
        records.add(new ShotRecord("P02", "张三", "M01", 20, 30.0, ShotType.KNUCKLEBALL, ShotResult.OFF_TARGET));
        records.add(new ShotRecord("P02", "张三", "M02", 55, 29.0, ShotType.KNUCKLEBALL, ShotResult.SAVED));
        records.add(new ShotRecord("P02", "张三", "M03", 88, 26.0, ShotType.KNUCKLEBALL, ShotResult.BLOCKED));
        // 对照: 弧线球数据(不纳入落叶球统计)
        records.add(new ShotRecord("P01", "C罗", "M06", 40, 25.0, ShotType.CURVE, ShotResult.GOAL));
        KnuckleballStatsService service = new KnuckleballStatsService();
        System.out.println("=== 按球员统计 ===");
        service.successRateByPlayer(records)
                .forEach((k, v) -> System.out.printf("%s: %.2f%%%n", k, v));
        System.out.println("\n=== 按距离统计 ===");
        service.successRateByDistance(records)
                .forEach((k, v) -> System.out.printf("%s: %.2f%%%n", k, v));
        System.out.println("\nC罗落叶球成功率: " +
                service.calculateSuccessRate(records, "P01") + "%");
    }
}

运行结果

=== 按球员统计 ===
C罗: 60.00%
张三: 0.00%
=== 按距离统计 ===
中距离(20-25m): 50.00%
远距离(25-30m): 50.00%
超远距离(>30m): 0.00%
C罗落叶球成功率: 60.0%

扩展建议

  1. 数据来源接入:可对接 Opta / StatsBomb 等足球数据 API,解析 JSON 后映射为 ShotRecord
  2. 守门员维度:增加 goalkeeperId 字段,统计"某球员对某门将的落叶球成功率"
  3. 命中率 vs 转化率:区分 GOAL(转化率)和 ON_TARGET(命中率 = 进球+扑救+中柱)
  4. 置信区间:样本少时(如 2/3)成功率 66% 不可靠,建议加 Wilson 置信区间
  5. 可视化:导出 CSV,用 ECharts 绘制"距离-成功率"热力图

如需接入真实数据、加入置信区间计算或输出 Excel 报表,可以继续告诉我。

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