使用 Streams 实施 Java Pivot table
Implementing Java Pivot table using Streams
几天来我一直在为这个问题苦苦挣扎。我正在尝试使用 Java Streams 创建 Pivot 功能。我只需要执行
SUM、COUNT、MAX、MIN 和 AVERAGE。对于输入,我得到了一个数据透视列索引、一个数据透视行索引数组和要计算的值。
要注意的是数据在 List < List < Object >> 中,其中 Object 可以是 String、Integer 或 Double。但直到运行时我才知道。我必须 return 我的结果为 List < List < Object>>。
我遇到了 MAX/MIN 的问题(我假设 AVERAGE 与 MAX 和 MIN 相似)
为了以多个 table 值为中心,我创建了一个 class 来使用我的第二个 groupingBy
这不会编译,我不确定要比较什么,在哪里将对象转换为 int,或者我是否需要这样做。我想用一个流来完成这一切,但我不确定这是否可能。我做错了什么,或者我可以做不同的事情。提前致谢。
package pivot.test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
public class PivotTest {
List<List<Object>> rows = new ArrayList<List<Object>>();
public PivotTest() throws Exception {
rows.add(Arrays.asList(new Object[]{ "East", "Boy", "Tee", 10, 12.00}));
rows.add(Arrays.asList(new Object[]{ "East", "Boy", "Golf", 15, 20.00}));
rows.add(Arrays.asList(new Object[]{ "East", "Girl", "Tee", 8, 14.00}));
rows.add(Arrays.asList(new Object[]{ "East", "Girl", "Golf", 20, 24.00}));
rows.add(Arrays.asList(new Object[]{ "West", "Boy", "Tee", 5, 12.00}));
rows.add(Arrays.asList(new Object[]{ "West", "Boy", "Golf", 12, 20.00}));
rows.add(Arrays.asList(new Object[]{ "West", "Girl", "Tee", 15, 14.00}));
rows.add(Arrays.asList(new Object[]{ "West", "Girl", "Golf", 10, 24.00}));
}
// Dynamic Max based upon Column, Value to sum, and an array of pivot rows
public void MaxTable(int colIdx, int valueIdx, int... rowIdx) {
Map<Object, Map<Object, Integer>> myList = newRows.stream().collect(
Collectors.groupingBy(r -> ((List<Object>) r).get(colIdx),
Collectors.groupingBy( r -> new PivotColumns(r, rowIdx),
Collectors.collectingAndThen( Collectors.maxBy(Comparator.comparingInt(???)),
r -> ((List<Object>) r).get(valueIdx)))));
System.out.println("Dynamic MAX PIVOT"); System.out.println(myList);
}
public static void main(String[] args) {
try {
PivotTest p = new PivotTest();
System.out.println("\n\nStreams PIVOT with index values inside a List\n");
p.MaxTable(0, 3, new int[] { 2 });
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
class PivotColumns {
ArrayList<Object> columns;
public PivotColumns(
List<Object> objs, int... pRows) {
columns = new ArrayList<Object>();
for (int i = 0; i < pRows.length; i++) {
columns.add(objs.get(pRows[i]));
}
}
public void addObject(Object obj) {
columns.add(obj);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((columns == null) ? 0 : columns.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
PivotColumns other = (PivotColumns) obj;
if (columns == null) {
if (other.columns != null)
return false;
} else if (!columns.equals(other.columns))
return false;
return true;
}
public String toString() {
String s = "";
for (Object obj : columns) {
s += obj + ",";
}
return s.substring(0, s.lastIndexOf(','));
}
}
因为已知所有可能的值(String
、Integer
、Double
)都是 Comparable
,您可以对 Comparable
界面。也不要忘记打开可选的包装。最后,如果我理解正确,结果应该是 Map<Object, Map<Object, Object>> myList
,而不是 Map<Object, Map<Object, Integer>> myList
,因为您的列可能有非整数值:
public void MaxTable(int colIdx, int valueIdx, int... rowIdx) {
Map<Object, Map<Object, Object>> myList = newRows.stream().collect(
Collectors.groupingBy(r -> r.get(colIdx),
Collectors.groupingBy( r -> new PivotColumns(r, rowIdx),
Collectors.collectingAndThen( Collectors.maxBy(
Comparator.comparing(r -> (Comparable<Object>)(((List<Object>) r).get(valueIdx)))),
r -> r.get().get(valueIdx)))));
System.out.println("Dynamic MAX PIVOT"); System.out.println(myList);
}
结果:
> p.MaxTable(0, 3, new int[] { 1 });
{West={Girl=15, Boy=12}, East={Girl=20, Boy=15}}
> p.MaxTable(0, 4, new int[] { 1 });
{West={Girl=24.0, Boy=20.0}, East={Girl=24.0, Boy=20.0}}
如您所见,您可以同时处理 Integer
和 Double
列。甚至 String
也可以处理(按字典顺序选择最大值)。
对于平均值,您可以假设您的列值是数字(Number
class,Integer
或 Double
)并收集到 Double
(整数的平均值也可以是非整数):
public void AverageTable(int colIdx, int valueIdx, int... rowIdx) {
Map<Object, Map<Object, Double>> myList = newRows.stream().collect(
Collectors.groupingBy(r -> r.get(colIdx), Collectors
.groupingBy(r -> new PivotColumns(r, rowIdx),
Collectors.averagingDouble(r -> ((Number) (r
.get(valueIdx))).doubleValue()))));
System.out.println("Dynamic AVG PIVOT"); System.out.println(myList);
}
输出:
> p.AverageTable(0, 3, new int[] { 1 });
{West={Girl=12.5, Boy=8.5}, East={Girl=14.0, Boy=12.5}}
> p.AverageTable(0, 4, new int[] { 1 });
{West={Girl=19.0, Boy=16.0}, East={Girl=19.0, Boy=16.0}}
输入是 List
行,每行是 List
列,列是 String
、Integer
或 Double
,并且不知道要按哪些列和多少列进行分组,也不知道要聚合哪些列和什么类型的列,我建议实施您自己的聚合器。
据推测,所有行的列数都相同,并且某一列的所有值将始终是相同的类型(或null
)。
你想要的基本上是 Java SQL group-by 语句的实现:
SELECT Column1, Column2, ...
, SUM(Column5), MIN(Column5), MAX(Column5), COUNT(Column5)
, SUM(Column6), MIN(Column6), MAX(Column6), COUNT(Column6)
, ...
FROM List<List<Object>>
GROUP BY Column1, Column2, ...
您需要 3 个 class。第一个是 GroupBy
class,它必须将 equals()
和 hashCode()
实现为分组列的组合 equals/hashcode:Column1、Column2、.. .
第二个class是Aggregator
,实际上是两个class实现了一个通用接口,一个class用于聚合Integer
,另一个class用于聚合 Double
。聚合器将获得一个值 (Object
),并将累积 sum/min/max/count 个值。
第三个class是主要的class,你所谓的Pivot
class。应该告知所需的分组列(带类型)和所需的聚合列(带类型),最好使用 builder pattern。然后可以给它数据,并将该数据收集在 HashMap<GroupBy, Aggregator>
中,然后将该结果转换回 return 值所需的格式。
如何调用枢轴的示例class:
List<List<Object>> input = /*constructed elsewhere*/;
List<List<Object>> output = new Pivot()
.addGroupByString(0) // Column1
.addGroupByString(1) // Column2
.addGroupByInteger(2) // Column3 a group by column can be be a number
.addIntegerAggregation(4) // Column5
.addDoubleAggregation(5) // Column6
.process(input);
或者如果您并不总是想要所有聚合,则可以是:
.addIntegerSum(4) // SUM(Column5)
.addDoubleMin(5) // MIN(Column6)
.addDoubleMax(5) // MAX(Column6)
有了这个,Pivot
的实现可以处理任意数量的按列分组和聚合列,使用起来非常直观。
几天来我一直在为这个问题苦苦挣扎。我正在尝试使用 Java Streams 创建 Pivot 功能。我只需要执行 SUM、COUNT、MAX、MIN 和 AVERAGE。对于输入,我得到了一个数据透视列索引、一个数据透视行索引数组和要计算的值。
要注意的是数据在 List < List < Object >> 中,其中 Object 可以是 String、Integer 或 Double。但直到运行时我才知道。我必须 return 我的结果为 List < List < Object>>。
我遇到了 MAX/MIN 的问题(我假设 AVERAGE 与 MAX 和 MIN 相似)
为了以多个 table 值为中心,我创建了一个 class 来使用我的第二个 groupingBy
这不会编译,我不确定要比较什么,在哪里将对象转换为 int,或者我是否需要这样做。我想用一个流来完成这一切,但我不确定这是否可能。我做错了什么,或者我可以做不同的事情。提前致谢。
package pivot.test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
public class PivotTest {
List<List<Object>> rows = new ArrayList<List<Object>>();
public PivotTest() throws Exception {
rows.add(Arrays.asList(new Object[]{ "East", "Boy", "Tee", 10, 12.00}));
rows.add(Arrays.asList(new Object[]{ "East", "Boy", "Golf", 15, 20.00}));
rows.add(Arrays.asList(new Object[]{ "East", "Girl", "Tee", 8, 14.00}));
rows.add(Arrays.asList(new Object[]{ "East", "Girl", "Golf", 20, 24.00}));
rows.add(Arrays.asList(new Object[]{ "West", "Boy", "Tee", 5, 12.00}));
rows.add(Arrays.asList(new Object[]{ "West", "Boy", "Golf", 12, 20.00}));
rows.add(Arrays.asList(new Object[]{ "West", "Girl", "Tee", 15, 14.00}));
rows.add(Arrays.asList(new Object[]{ "West", "Girl", "Golf", 10, 24.00}));
}
// Dynamic Max based upon Column, Value to sum, and an array of pivot rows
public void MaxTable(int colIdx, int valueIdx, int... rowIdx) {
Map<Object, Map<Object, Integer>> myList = newRows.stream().collect(
Collectors.groupingBy(r -> ((List<Object>) r).get(colIdx),
Collectors.groupingBy( r -> new PivotColumns(r, rowIdx),
Collectors.collectingAndThen( Collectors.maxBy(Comparator.comparingInt(???)),
r -> ((List<Object>) r).get(valueIdx)))));
System.out.println("Dynamic MAX PIVOT"); System.out.println(myList);
}
public static void main(String[] args) {
try {
PivotTest p = new PivotTest();
System.out.println("\n\nStreams PIVOT with index values inside a List\n");
p.MaxTable(0, 3, new int[] { 2 });
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
class PivotColumns {
ArrayList<Object> columns;
public PivotColumns(
List<Object> objs, int... pRows) {
columns = new ArrayList<Object>();
for (int i = 0; i < pRows.length; i++) {
columns.add(objs.get(pRows[i]));
}
}
public void addObject(Object obj) {
columns.add(obj);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((columns == null) ? 0 : columns.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
PivotColumns other = (PivotColumns) obj;
if (columns == null) {
if (other.columns != null)
return false;
} else if (!columns.equals(other.columns))
return false;
return true;
}
public String toString() {
String s = "";
for (Object obj : columns) {
s += obj + ",";
}
return s.substring(0, s.lastIndexOf(','));
}
}
因为已知所有可能的值(String
、Integer
、Double
)都是 Comparable
,您可以对 Comparable
界面。也不要忘记打开可选的包装。最后,如果我理解正确,结果应该是 Map<Object, Map<Object, Object>> myList
,而不是 Map<Object, Map<Object, Integer>> myList
,因为您的列可能有非整数值:
public void MaxTable(int colIdx, int valueIdx, int... rowIdx) {
Map<Object, Map<Object, Object>> myList = newRows.stream().collect(
Collectors.groupingBy(r -> r.get(colIdx),
Collectors.groupingBy( r -> new PivotColumns(r, rowIdx),
Collectors.collectingAndThen( Collectors.maxBy(
Comparator.comparing(r -> (Comparable<Object>)(((List<Object>) r).get(valueIdx)))),
r -> r.get().get(valueIdx)))));
System.out.println("Dynamic MAX PIVOT"); System.out.println(myList);
}
结果:
> p.MaxTable(0, 3, new int[] { 1 });
{West={Girl=15, Boy=12}, East={Girl=20, Boy=15}}
> p.MaxTable(0, 4, new int[] { 1 });
{West={Girl=24.0, Boy=20.0}, East={Girl=24.0, Boy=20.0}}
如您所见,您可以同时处理 Integer
和 Double
列。甚至 String
也可以处理(按字典顺序选择最大值)。
对于平均值,您可以假设您的列值是数字(Number
class,Integer
或 Double
)并收集到 Double
(整数的平均值也可以是非整数):
public void AverageTable(int colIdx, int valueIdx, int... rowIdx) {
Map<Object, Map<Object, Double>> myList = newRows.stream().collect(
Collectors.groupingBy(r -> r.get(colIdx), Collectors
.groupingBy(r -> new PivotColumns(r, rowIdx),
Collectors.averagingDouble(r -> ((Number) (r
.get(valueIdx))).doubleValue()))));
System.out.println("Dynamic AVG PIVOT"); System.out.println(myList);
}
输出:
> p.AverageTable(0, 3, new int[] { 1 });
{West={Girl=12.5, Boy=8.5}, East={Girl=14.0, Boy=12.5}}
> p.AverageTable(0, 4, new int[] { 1 });
{West={Girl=19.0, Boy=16.0}, East={Girl=19.0, Boy=16.0}}
输入是 List
行,每行是 List
列,列是 String
、Integer
或 Double
,并且不知道要按哪些列和多少列进行分组,也不知道要聚合哪些列和什么类型的列,我建议实施您自己的聚合器。
据推测,所有行的列数都相同,并且某一列的所有值将始终是相同的类型(或null
)。
你想要的基本上是 Java SQL group-by 语句的实现:
SELECT Column1, Column2, ...
, SUM(Column5), MIN(Column5), MAX(Column5), COUNT(Column5)
, SUM(Column6), MIN(Column6), MAX(Column6), COUNT(Column6)
, ...
FROM List<List<Object>>
GROUP BY Column1, Column2, ...
您需要 3 个 class。第一个是 GroupBy
class,它必须将 equals()
和 hashCode()
实现为分组列的组合 equals/hashcode:Column1、Column2、.. .
第二个class是Aggregator
,实际上是两个class实现了一个通用接口,一个class用于聚合Integer
,另一个class用于聚合 Double
。聚合器将获得一个值 (Object
),并将累积 sum/min/max/count 个值。
第三个class是主要的class,你所谓的Pivot
class。应该告知所需的分组列(带类型)和所需的聚合列(带类型),最好使用 builder pattern。然后可以给它数据,并将该数据收集在 HashMap<GroupBy, Aggregator>
中,然后将该结果转换回 return 值所需的格式。
如何调用枢轴的示例class:
List<List<Object>> input = /*constructed elsewhere*/;
List<List<Object>> output = new Pivot()
.addGroupByString(0) // Column1
.addGroupByString(1) // Column2
.addGroupByInteger(2) // Column3 a group by column can be be a number
.addIntegerAggregation(4) // Column5
.addDoubleAggregation(5) // Column6
.process(input);
或者如果您并不总是想要所有聚合,则可以是:
.addIntegerSum(4) // SUM(Column5)
.addDoubleMin(5) // MIN(Column6)
.addDoubleMax(5) // MAX(Column6)
有了这个,Pivot
的实现可以处理任意数量的按列分组和聚合列,使用起来非常直观。