详情

在Java 8 的Lambda(stream)之前,要在Java代码中实现相似SQL中的group by分组聚合功能,还是比较困难的。这之前Java对函数式编程支持不是很好,Scala则把函数式编程发挥到了机制,实现一个group by聚合对Scala来说就是几行代码的事情:val birds = List("Golden Eagle","Gyrfalcon", "American Robin", "Mountain BlueBird", "Mountain-Hawk Eagle")

val groupByFirstLetter = birds.groupby(_.charAt(0))

输出:Map(M -> List(Mountain BlueBird, Mountain-Hawk Eagle), G -> List(Golden Eagle, Gyrfalcon),

A -> List(American Robin))

Java也有少量第三方的函数库来支持,例如Guava的Function,以及functional java这样的库。 但总的来说,内存对Java集合进行GroupBy ,OrderBy, Limit等TopN操作还是比较繁琐。本文实现一个简单的group功能,支持自己设置key以及聚合函数,通过简单的几个类,可以实现SQL都比较难实现的先分组,而后组内排序,最后取组内TopN。

实现

假设我们有这样一个Person类:package me.lin;

class Person {

private String name;

private int age;

private double salary;

public Person(String name, int age, double salary) {

super();

this.name = name;

this.age = age;

this.salary = salary;

}

public String getName() {

return name;

}

public void setName(String name) {

this.name = name;

}

public int getAge() {

return age;

}

public void setAge(int age) {

this.age = age;

}

public double getSalary() {

return salary;

}

public void setSalary(double salary) {

this.salary = salary;

}

public String getNameAndAge() {

return this.getName() + "-" + this.getAge();

}

@Override

public String toString() {

return "Person [name=" + name + ", age=" + age + ", salary=" + salary

+ "]";

}

}

对于一个Person的List,想要根据年龄进行统计,取第一个值,取salary最高值等。实现如下:

聚合操作

定义一个聚合接口,用于对分组后的元素进行聚合操作,类比到MySQL中的count(*) 、sum():package me.lin;

import java.util.List;

/**

*

* 聚合操作

*

* Created by Brandon on 2016/7/21.

*/

public interface Aggregator{

/**

* 每一组的聚合操作

*

* @param key 组别标识key

* @param values 属于该组的元素集合

* @return

*/

Object aggregate(Object key , Listvalues);

}

我们实现几个聚合操作,更复杂的操作支持完全可以自己定义。

CountAggragator:package me.lin;

import java.util.List;

/**

*

* 计数聚合操作

*

* Created by Brandon on 2016/7/21.

*/

public class CountAggregatorimplements Aggregator{

@Override

public Object aggregate(Object key, Listvalues) {

return values.size();

}

}

FisrtAggregator:package me.lin;

import java.util.List;

/**

*

* 取第一个元素

*

* Created by Brandon on 2016/7/21.

*/

public class FirstAggregatorimplements Aggregator{

@Override

public Object aggregate(Object key, Listvalues) {

if ( values.size() >= 1) {

return values.get( 0 );

}else {

return null;

}

}

}

TopNAggregator:package me.lin;

import java.util.ArrayList;

import java.util.Collections;

import java.util.Comparator;

import java.util.List;

/**

*

* 取每组TopN

*

* Created by Brandon on 2016/7/21.

*/

public class TopNAggregatorimplements Aggregator{

private Comparatorcomparator;

private int limit;

public TopNAggregator(Comparatorcomparator, int limit) {

this.limit = limit;

this.comparator = comparator;

}

@Override

public Object aggregate(Object key, Listvalues) {

if (values == null || values.size() == 0) {

return null;

}

ArrayListcopy = new ArrayList<>( values );

Collections.sort(copy, comparator);

int size = values.size();

int toIndex = Math.min(limit, size);

return copy.subList(0, toIndex);

}

}

分组实现

接下来是分组实现,简单起见,采用工具类实现:package me.lin;

import java.lang.reflect.Field;

import java.lang.reflect.InvocationTargetException;

import java.lang.reflect.Method;

import java.util.ArrayList;

import java.util.Collection;

import java.util.Collections;

import java.util.HashMap;

import java.util.Map;

/**

* Collection分组工具类

*/

public class GroupUtils {

/**

* 分组聚合

*

* @param listToDeal 待分组的数据,相当于SQL中的原始表

* @param clazz 带分组数据元素类型

* @param groupBy 分组的属性名称

* @param aggregatorMap 聚合器,key为聚合器名称,作为返回结果中聚合值map中的key

* @param 元素类型Class

* @return

* @throws NoSuchFieldException

* @throws SecurityException

* @throws IllegalArgumentException

* @throws IllegalAccessException

*/

public static Map> groupByProperty(

CollectionlistToDeal, Classclazz, String groupBy,

Map> aggregatorMap) throws NoSuchFieldException,

SecurityException, IllegalArgumentException, IllegalAccessException {

Map> groupResult = new HashMap>();

for (T ele : listToDeal) {

Field field = clazz.getDeclaredField(groupBy);

field.setAccessible(true);

Object key = field.get(ele);

if (!groupResult.containsKey(key)) {

groupResult.put(key, new ArrayList());

}

groupResult.get(key).add(ele);

}

return invokeAggregators(groupResult, aggregatorMap);

}

public static Map> groupByMethod(

CollectionlistToDeal, Classclazz, String groupByMethodName,

Map> aggregatorMap) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {

Map> groupResult = new HashMap>();

for (T ele : listToDeal) {

Method groupByMenthod = clazz.getDeclaredMethod(groupByMethodName);

groupByMenthod.setAccessible(true);

Object key = groupByMenthod.invoke(ele);

if (!groupResult.containsKey(key)) {

groupResult.put(key, new ArrayList());

}

groupResult.get(key).add(ele);

}

return invokeAggregators(groupResult, aggregatorMap);

}

private static Map> invokeAggregators(Map> groupResult, Map> aggregatorMap) {

Map> aggResults = new HashMap<>();

for (Object key : groupResult.keySet()) {

Collectiongroup = groupResult.get(key);

MapaggValues = doInvokeAggregators(key, group, aggregatorMap);

if (aggValues != null && aggValues.size() > 0) {

aggResults.put(key, aggValues);

}

}

return aggResults;

}

private static MapdoInvokeAggregators(Object key, Collectiongroup, Map> aggregatorMap) {

MapaggResults = new HashMap();

if (group != null && group.size() > 0) {

// 调用当前key的每一个聚合函数

for (String aggKey : aggregatorMap.keySet()) {

Aggregatoraggregator = aggregatorMap.get(aggKey);

Object aggResult = aggregator.aggregate(key, Collections.unmodifiableList(new ArrayList(group)));

aggResults.put(aggKey, aggResult);

}

}

return aggResults;

}

}

上述代码中,分组的key可以指定元素的属性,也可以指定元素的方法,通过自己实现复杂方法和聚合函数,可以实现很强大的分组功能。

测试

根据属性分组

下面测试一下根据属性分组:package me.lin;

import java.util.ArrayList;

import java.util.Comparator;

import java.util.HashMap;

import java.util.List;

import java.util.Map;

public class GroupByPropertyTest {

public static void main(String[] args) throws NoSuchFieldException,

SecurityException, IllegalArgumentException, IllegalAccessException {

Listpersons = new ArrayList<>();

persons.add(new Person("Brandon", 15, 5000));

persons.add(new Person("Braney", 15, 15000));

persons.add(new Person("Jack", 10, 5000));

persons.add(new Person("Robin", 10, 500000));

persons.add(new Person("Tony", 10, 1400000));

Map> aggregatorMap = new HashMap<>();

aggregatorMap.put("count", new CountAggregator());

aggregatorMap.put("first", new FirstAggregator());

Comparatorcomparator = new Comparator() {

public int compare(final Person o1, final Person o2) {

double diff = o1.getSalary() - o2.getSalary();

if (diff == 0) {

return 0;

}

return diff > 0 ? -1 : 1;

}

};

aggregatorMap.put("top2", new TopNAggregator( comparator , 2 ));

Map> aggResults = GroupUtils.groupByProperty(persons, Person.class, "age", aggregatorMap);

for (Object key : aggResults.keySet()) {

System.out.println("Key:" + key);

Mapresults = aggResults.get(key);

for (String aggKey : results.keySet()) {

System.out.println(" aggkey->" + results.get(aggKey));

}

}

}

}

输出结果:Key:10

aggkey->3

aggkey->Person [name=Jack, age=10, salary=5000.0]

aggkey->[Person [name=Tony, age=10, salary=1400000.0], Person [name=Robin, age=10, salary=500000.0]]

Key:15

aggkey->2

aggkey->Person [name=Brandon, age=15, salary=5000.0]

aggkey->[Person [name=Braney, age=15, salary=15000.0], Person [name=Brandon, age=15, salary=5000.0]]

根据方法返回值分组

测试根据方法返回值分组:package me.lin;

import java.util.ArrayList;

import java.util.Comparator;

import java.util.HashMap;

import java.util.List;

import java.util.Map;

public class GroupByMethodTest {

public static void main(String[] args) throws Exception {

Listpersons = new ArrayList<>();

persons.add(new Person("Brandon", 15, 5000));

persons.add(new Person("Brandon", 15, 15000));

persons.add(new Person("Jack", 10, 5000));

persons.add(new Person("Robin", 10, 500000));

persons.add(new Person("Tony", 10, 1400000));

Map> aggregatorMap = new HashMap<>();

aggregatorMap.put("count", new CountAggregator());

aggregatorMap.put("first", new FirstAggregator());

Comparatorcomparator = new Comparator() {

public int compare(final Person o1, final Person o2) {

double diff = o1.getSalary() - o2.getSalary();

if (diff == 0) {

return 0;

}

return diff > 0 ? -1 : 1;

}

};

aggregatorMap.put("top2", new TopNAggregator(comparator, 2));

Map> aggResults = GroupUtils.groupByMethod(persons, Person.class, "getNameAndAge", aggregatorMap);

for (Object key : aggResults.keySet()) {

System.out.println("Key:" + key);

Mapresults = aggResults.get(key);

for (String aggKey : results.keySet()) {

System.out.println(" " + aggKey + "->" + results.get(aggKey));

}

}

}

}

测试结果:Key:Robin-10

count->1

first->Person [name=Robin, age=10, salary=500000.0]

top2->[Person [name=Robin, age=10, salary=500000.0]]

Key:Jack-10

count->1

first->Person [name=Jack, age=10, salary=5000.0]

top2->[Person [name=Jack, age=10, salary=5000.0]]

Key:Tony-10

count->1

first->Person [name=Tony, age=10, salary=1400000.0]

top2->[Person [name=Tony, age=10, salary=1400000.0]]

Key:Brandon-15

count->2

first->Person [name=Brandon, age=15, salary=5000.0]

top2->[Person [name=Brandon, age=15, salary=15000.0], Person [name=Brandon, age=15, salary=5000.0]]

以上就是GroupBy的简单实现,假如问题,欢迎指出。欢迎交流。

java group by_Java实现GroupBy/分组TopN功能相关推荐

  1. python dataframe group by_Python DataFrame.groupby()聚合函数,分组级运算

    pandas提供了一个灵活高效的groupby功能,它使你能以一种自然的方式对数据集进行切片.切块.摘要等操作.根据一个或多个键(可以是函数.数组或DataFrame列名)拆分pandas对象.计算分 ...

  2. python处理分组_Python中的groupby分组功能的实例代码

    pandas中的DataFrame中可以根据某个属性的同一值进行聚合分组,可以选单个属性,也可以选多个属性: 代码示例: import pandas as pd A=pd.DataFrame([['B ...

  3. java mongo分组统计_mongodb 分组 topN

    [摘要] MongoDB 对于 TopN 功能的需求使用其 shell 脚本来实现有些复杂,而集算器 SPL 语言,则因其离散性.灵活性恰好能弥补 MongoDB 实现方面的不足.若想了解更多,请前往 ...

  4. java 实现查询近七天数据功能

    java 实现查询近七天数据功能 接上一篇 如何使用echarts表图地址 实现了页面的表图 那么如何对接数据 如何使用 耐心看完!!! 这次就以右下角这一个表图做示范 这个表图的下面是按时间排序的 ...

  5. Python中的groupby分组

    Python中的groupby分组 一.groupby函数 groupby函数功能:对DataFrame进行分组(可单类分组,可多类分组) 需求:按"字段"列对数据data进行分组 ...

  6. 利用bobo-browse 实现lucene的分组统计功能

    bobo-browse 是一用java写的lucene扩展组件,通过它可以很方便在lucene上实现分组统计功能. 可以从http://sna-projects.com/bobo/上下载和查看相关文档 ...

  7. 将查询出来的数据按照一个字段分组且排序过程中,遇到的一些有关group的问题(分组排序应该使用partition by)

    目录 我想要的效果 Group By Group By 语法 Group By 错误示例 重点提醒 功能实现(partition by 分区函数) 以往查询出来的数据想要按照某一个字段分组展示,直接按 ...

  8. mysql分组取出每组地一条数据_基于mysql实现group by取各分组最新一条数据

    基于mysql实现group by取各分组最新一条数据 前言: group by函数后取到的是分组中的第一条数据,但是我们有时候需要取出各分组的最新一条,该怎么实现呢? 本文提供两种实现方式. 一.准 ...

  9. Spark使用RDD实现分组topN(八种方法)

    最近在复习Spark,记录一个使用RDD实现分组topN的方法,一共写了八种,其中有很多地方都是有共性的,我会在代码最后进行总结八种的思路,他们之间的共性以及每一种的优缺点. 以下是样例数据 语文,赵 ...

最新文章

  1. linux系统源码安装教程,linux之源码包安装步骤
  2. 无法在WEB服务器上启动调试,Web 服务器配置不正确
  3. 如果中国要做自己的GPT-3,一定离不开这家公司的算力
  4. 每天一个linux命令(21):find命令之xargs
  5. Java常见容器(Container)关系图
  6. [转]我是如何走进黑客世界的?
  7. Kafka源码分析-序列3 -Producer -Java NIO(Reactor VS Peactor)
  8. 微软是如何使用C#重写C#编译器并将其开源的
  9. 实验五 网络编程与安全
  10. 关于移动安全的一点总结
  11. 【渝粤教育】电大中专跨境电子商务理论与实务 (8)作业 题库
  12. 北理工计算机学院新闻,新闻睇睇睇 | 计算机学院举办第八届ACM/ICPC程序设计竞赛校内选拔赛暨北理工邀请赛...
  13. t分布(Student t distribution)——正态分布的小样本抽样分布
  14. python华氏温度和摄氏温度相互转换
  15. SpringBoot注解校验validation自定义异常返回错误消息给前端
  16. Abp vNext 常见问题
  17. Java对象扁平化的操作
  18. 地图坐标系之间的转换(百度地图、GCJ、WGS84)
  19. java 数组声明并初始化_Java数组的声明与初始化
  20. 实习,内推,校招,社招的区别和联系

热门文章

  1. 施工员培训建筑八大员培训施工员房屋建筑工程现场施工管理剖析
  2. 植物大战僵尸beta贝塔版
  3. 【Java语言】项目实践:人机猜拳游戏(源码)(面向对象方法)
  4. Mac从零搭建开发环境
  5. 谣言检测相关论文阅读笔记:DDGCN: Dual Dynamic Graph Convolutional Networks for Rumor Detection on Social Media
  6. java民宿开源_在线民宿满意度测评项目[开源]
  7. php7.2 安装bcmath扩展
  8. thinkphp多城市房产系统源码程序_Thinkphp5.0内核开发房产网门户系统源码tp5Fangcms仿链家源码模板多城市版...
  9. 视频自动剪辑生成软件王者剪辑的视频评估功能用法总结
  10. PHP程序员玩转算法公开课(第一季)02_单链表在内存中存在形式剖析