Java 案例:统计长短传比例分布
下面给出一个完整的 Java 案例,演示如何统计长传/短传比例的分布情况,这在足球数据分析、体育统计等场景中很常见。

需求说明
- 输入:一批传球记录(传球距离,单位:米)
- 规则:短传 < 30m ≤ 长传(阈值可配置)
- 输出:
- 长传、短传各自数量
- 各自占比
- 按"比例区间"分段统计的分布(长传占比 0-20%、20-40% ...)
基础版本:整体长短传比例
import java.util.*;
import java.util.stream.Collectors;
public class PassStatsBasic {
// 传球记录
static class Pass {
String playerId;
double distance; // 米
public Pass(String playerId, double distance) {
this.playerId = playerId;
this.distance = distance;
}
}
private static final double LONG_PASS_THRESHOLD = 30.0;
public static void main(String[] args) {
List<Pass> passes = List.of(
new Pass("P1", 12),
new Pass("P1", 45),
new Pass("P2", 8),
new Pass("P2", 33),
new Pass("P2", 60),
new Pass("P3", 25),
new Pass("P3", 28),
new Pass("P3", 70),
new Pass("P4", 15),
new Pass("P4", 18)
);
long total = passes.size();
long longCount = passes.stream()
.filter(p -> p.distance >= LONG_PASS_THRESHOLD)
.count();
long shortCount = total - longCount;
System.out.printf("总传球数: %d%n", total);
System.out.printf("长传: %d (%.2f%%)%n", longCount, 100.0 * longCount / total);
System.out.printf("短传: %d (%.2f%%)%n", shortCount, 100.0 * shortCount / total);
System.out.printf("长传/短传 比例: %.2f%n", (double) longCount / shortCount);
}
}
输出示例:
总传球数: 10
长传: 4 (40.00%)
短传: 6 (60.00%)
长传/短传 比例: 0.67
进阶版本:按球员统计长短传比例分布
import java.util.*;
import java.util.stream.Collectors;
public class PassStatsByPlayer {
static class Pass {
String playerId;
double distance;
Pass(String playerId, double distance) {
this.playerId = playerId;
this.distance = distance;
}
}
static class PlayerPassStats {
String playerId;
long total;
long longCount;
long shortCount;
double longRatio; // 长传占比
PlayerPassStats(String playerId, long total, long longCount, long shortCount) {
this.playerId = playerId;
this.total = total;
this.longCount = longCount;
this.shortCount = shortCount;
this.longRatio = total == 0 ? 0 : 100.0 * longCount / total;
}
}
private static final double THRESHOLD = 30.0;
public static void main(String[] args) {
List<Pass> passes = List.of(
new Pass("P1", 12), new Pass("P1", 45), new Pass("P1", 50),
new Pass("P2", 8), new Pass("P2", 33), new Pass("P2", 60),
new Pass("P3", 25), new Pass("P3", 28), new Pass("P3", 70),
new Pass("P4", 15), new Pass("P4", 18)
);
// 1. 按球员分组统计
Map<String, List<Pass>> byPlayer = passes.stream()
.collect(Collectors.groupingBy(p -> p.playerId));
List<PlayerPassStats> statsList = new ArrayList<>();
for (Map.Entry<String, List<Pass>> e : byPlayer.entrySet()) {
long total = e.getValue().size();
long longCnt = e.getValue().stream()
.filter(p -> p.distance >= THRESHOLD).count();
statsList.add(new PlayerPassStats(e.getKey(), total, longCnt, total - longCnt));
}
// 2. 打印明细
System.out.println("========== 球员长短传明细 ==========");
statsList.sort(Comparator.comparing(p -> p.playerId));
for (PlayerPassStats s : statsList) {
System.out.printf("%s | 总:%d | 长:%d | 短:%d | 长传占比:%.2f%%%n",
s.playerId, s.total, s.longCount, s.shortCount, s.longRatio);
}
// 3. 按"长传占比区间"做分布
// 区间划分: [0,20) [20,40) [40,60) [60,80) [80,100]
Map<String, Long> ratioDistribution = new LinkedHashMap<>();
ratioDistribution.put("0%~20%", 0L);
ratioDistribution.put("20%~40%", 0L);
ratioDistribution.put("40%~60%", 0L);
ratioDistribution.put("60%~80%", 0L);
ratioDistribution.put("80%~100%", 0L);
for (PlayerPassStats s : statsList) {
int idx = (int) (s.longRatio / 20);
if (idx >= 5) idx = 4;
String key = new ArrayList<>(ratioDistribution.keySet()).get(idx);
ratioDistribution.put(key, ratioDistribution.get(key) + 1);
}
System.out.println("\n========== 长传占比分布 ==========");
long playerTotal = statsList.size();
for (Map.Entry<String, Long> e : ratioDistribution.entrySet()) {
System.out.printf("%-10s : %d 人 (%.1f%%)%n",
e.getKey(), e.getValue(),
playerTotal == 0 ? 0 : 100.0 * e.getValue() / playerTotal);
}
}
}
输出示例:
========== 球员长短传明细 ==========
P1 | 总:3 | 长:2 | 短:1 | 长传占比:66.67%
P2 | 总:3 | 长:2 | 短:1 | 长传占比:66.67%
P3 | 总:3 | 长:1 | 短:2 | 长传占比:33.33%
P4 | 总:2 | 长:0 | 短:2 | 长传占比:0.00%
========== 长传占比分布 ==========
0%~20% : 1 人 (25.0%)
20%~40% : 1 人 (25.0%)
40%~60% : 0 人 (0.0%)
60%~80% : 2 人 (50.0%)
80%~100% : 0 人 (0.0%)
通用化:可配置分箱统计(推荐)
把"分箱逻辑"抽出来,成为通用工具方法,方便复用。
import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;
public class PassStatsUtil {
/**
* 按布尔分组统计(长传 vs 短传)
*/
public static class RatioResult {
public long longCount;
public long shortCount;
public double longRatio() {
long t = longCount + shortCount;
return t == 0 ? 0 : 100.0 * longCount / t;
}
}
/**
* 按分箱统计一批数值落入每个区间的频次
* @param values 数据
* @param binSize 区间宽度(20 表示 0-20, 20-40...)
* @param max 最大值上限(100)
*/
public static Map<String, Long> histogram(List<Double> values, int binSize, int max) {
Map<String, Long> result = new LinkedHashMap<>();
for (int i = 0; i < max; i += binSize) {
result.put(i + "%~" + (i + binSize) + "%", 0L);
}
for (double v : values) {
int idx = (int) (v / binSize);
if (idx * binSize >= max) idx = max / binSize - 1;
String key = (idx * binSize) + "%~" + ((idx + 1) * binSize) + "%";
result.put(key, result.getOrDefault(key, 0L) + 1);
}
return result;
}
/**
* 计算单个球员的长短传统计
*/
public static RatioResult compute(List<Double> distances, double threshold) {
RatioResult r = new RatioResult();
for (double d : distances) {
if (d >= threshold) r.longCount++;
else r.shortCount++;
}
return r;
}
public static void main(String[] args) {
double threshold = 30.0;
// 模拟:3 名球员的传球距离
Map<String, List<Double>> data = new LinkedHashMap<>();
data.put("P1", List.of(12.0, 45.0, 50.0, 22.0));
data.put("P2", List.of(8.0, 33.0, 60.0, 70.0, 15.0));
data.put("P3", List.of(25.0, 28.0, 20.0, 18.0, 22.0, 19.0));
// 每人长传占比
List<Double> ratios = new ArrayList<>();
System.out.println("========== 每人长传占比 ==========");
for (Map.Entry<String, List<Double>> e : data.entrySet()) {
RatioResult r = compute(e.getValue(), threshold);
ratios.add(r.longRatio());
System.out.printf("%s : 长%d 短%d → 长传占比 %.2f%%%n",
e.getKey(), r.longCount, r.shortCount, r.longRatio());
}
// 分布统计(20% 一档)
System.out.println("\n========== 长传占比分布(20%一档) ==========");
Map<String, Long> hist = histogram(ratios, 20, 100);
long total = ratios.size();
hist.forEach((k, v) ->
System.out.printf("%-12s : %d (%.1f%%)%n",
k, v, 100.0 * v / total));
}
}
输出示例:
========== 每人长传占比 ==========
P1 : 长2 短2 → 长传占比 50.00%
P2 : 长3 短2 → 长传占比 60.00%
P3 : 长0 短6 → 长传占比 0.00%
========== 长传占比分布(20%一档) ==========
0%~20% : 1 (33.3%)
20%~40% : 0 (0.0%)
40%~60% : 2 (66.7%)
60%~80% : 0 (0.0%)
80%~100% : 0 (0.0%)
关键点总结
| 关注点 | 做法 |
|---|---|
| 长短传定义 | 阈值可配置(默认 30m),>= threshold 为长传 |
| 整体比例 | longCount / total * 100 |
| 按人统计 | Collectors.groupingBy(playerId) |
| 分布统计 | 把"占比"再按区间分箱(如 20% 一档) |
| 通用化 | 抽出 histogram(values, binSize, max) 方法 |
| 健壮性 | 处理 total == 0、idx 越界等边界情况 |
如果你有具体的场景(足球赛事件数据、按比赛/按半场分组、按传球成功率联合统计、导出到 CSV/图表),告诉我,我可以把这个案例细化成贴合你数据结构(JSON / 数据库 / Excel)的版本。