问题描述
我有表单数据条目列表,每个DataEntry
包含数据字段,其类型为Map<String, Object>
。
String键是动态字段名称(调查问题),value是针对该特定问题的选择。
在列表之外,如何计算每个字段名称(问题)的唯一答案,我假设这样的事情?
Map<String, LinkedTreeMap<String, Integer>
,其中外部地图键是fieldXXXX,其值映射键(字符串)是唯一答案,键整数是该答案的计数
出于测试目的,从数据库中它们出现为字符串,我映射到DTO,映射器映射到正确的json映射:
DataEntry entry1 = new DataEntry();
entry1.setData("{field9294: '4', field9296: '3', field9319: '5', field9321: '5', field9323: '3', field9325: '3', field9327: '5', field9329: '7'}");
DataEntry entry2 = new DataEntry();
entry2.setData("{field9294: '3', field9296: '2', field9319: '3', field9321: '3', field9323: '5', field9325: '2', field9327: '4', field9329: '4'}");
DataEntry entry3 = new DataEntry();
entry3.setData("{field9294: '5', field9296: '5', field9319: '4', field9321: '4', field9323: '3', field9325: '3', field9327: '4', field9329: '8'}");
List<DataEntry> dataEntries = Arrays.asList(entry1, entry2, entry3);
List<FormDataDTO> dtos = dataEntries.stream().map(mapper::dataEntryToDto).collect(Collectors.toList());
所以dtos列表看起来像这样:
最终目标
让我们取第一个字段9294 ,在3个数据条目中给出3个唯一答案: 4,3,5 。这里所有都应该有1.现在field9327有5,4,4的答案。这里我们算5次,4次两次。
一般的想法是分别绘制每个问题的数据,以便绘制图表并以百分比形式装饰结果。
正如我所看到的那样获取Map<String, LinkedTreeMap<String, Integer>
就足以实现这一点,但是有没有使用花哨的lambda技巧的有效方法,因为我无法弄清楚自己。
为了结果我期待这样的事情:
Map
key: "field9294"
values: "4" -> 1
"3" -> 1
"5" -> 1
key: "field9327"
values: "5" -> 1
"4" -> 2
等等..
提前致谢!
编辑: 全部通过,谢谢你的解决方案!
assertEquals("[3=1, 4=1, 5=1]", answerCountsByField.get("field9294").entrySet().toString());
assertEquals("[2=1, 3=1, 5=1]", answerCountsByField.get("field9296").entrySet().toString());
assertEquals("[3=1, 4=1, 5=1]", answerCountsByField.get("field9319").entrySet().toString());
assertEquals("[3=1, 4=1, 5=1]", answerCountsByField.get("field9321").entrySet().toString());
assertEquals("[3=2, 5=1]", answerCountsByField.get("field9323").entrySet().toString());
assertEquals("[2=1, 3=2]", answerCountsByField.get("field9325").entrySet().toString());
assertEquals("[4=2, 5=1]", answerCountsByField.get("field9327").entrySet().toString());
assertEquals("[4=1, 7=1, 8=1]", answerCountsByField.get("field9329").entrySet().toString());
Edit2: 寻找这种结构的解决方案 。 对于结果我只关心真正的答案,false对于绘图是多余的,因为此结构映射到复选框列表
{"field6696":{"1":true,"2":true},"field7994":{"1":true,"2":false,"3":false,"4":true}}
{"field6696":{"1":false,"2":true},"field7994":{"1":false,"2":true,"3":true}}
{"field6696":{"1":false,"2":true},"field7994":{"1":false,"2":true,"3":false,"4":true}}
{"field6696":{"1":false,"2":true,"3":true},"field7994":{"1":true,"2":true,"3":false}}
{"field6696":{"1":false,"2":true},"field7994":{"1":true,"2":true,"3":true,"4":true}}
1楼
您应该从Stream开始,因此我会避免将FormDataDTO对象收集到List中。 改变这个:
List<FormDataDTO> dtos = dataEntries.stream().map(mapper::dataEntryToDto).collect(Collectors.toList());
就是这样:
Stream<FormDataDTO> dtos = dataEntries.stream().map(mapper::dataEntryToDto);
然后,您可以使用groupingBy
调用来收集它们,该调用本身使用另一个收集器来创建Map值:
Map<String, Map<String, Long>> answerCountsByField =
dtos.flatMap(dto -> dto.getData().entrySet().stream()).collect(
Collectors.groupingBy(e -> e.getKey(),
Collectors.groupingBy(e -> e.getValue(),
Collectors.counting())));
如果你想计数是整数,而非多头,你可以使用collectingAndThen
变换每个长值:
Map<String, Map<String, Integer>> answerCountsByField =
dtos.flatMap(dto -> dto.getData().entrySet().stream()).collect(
Collectors.groupingBy(e -> e.getKey(),
Collectors.groupingBy(e -> e.getValue(),
Collectors.collectingAndThen(
Collectors.counting(), Long::intValue))));
2楼
我现在写了一些东西,这个结构实际上适用于两种情况,因为最终结果可能完全相同,唯一的区别是解析,因为我必须深入一个地图。
我尝试了相同的函数,然后我必须检查entry.getValue()
的类型,因为在一种情况下它是字符串,对于其他情况它是另一个Map,但它看起来真的很糟糕。
我把分组逻辑放到测试中发布在这里,数据包括结构化数据类型,单值和对象。
也许可以在这里暗示任何改进?
@Test
public void multiValueLists_answerCountsByField() throws Exception {
List<DataEntry> entries = new ArrayList<DataEntry>() {
{
add(new DataEntry("{field1000: {1: true, 2: true, 3: true}, field2000: {1: true, 2: true}, field3000: '1', field4000: '1', field5000: '1'}"));
add(new DataEntry("{field1000: {1: true, 2: true, 3: false}, field2000: {1: true, 2: false}, field3000: '1', field4000: '2', field5000: '2'}"));
add(new DataEntry("{field1000: {1: false, 2: true, 3: true}, field2000: {1: true}, field3000: '1', field4000: '2', field5000: '3'}"));
}
};
Stream<FormDataDTO> dtoStream = entries.stream().map(mapper::dataEntryToDto);
Map<String, Map<String, AtomicInteger>> answers = new LinkedTreeMap<>();
dtoStream.forEach(dto -> dto.getData().entrySet()
.forEach(field -> {
answers.putIfAbsent(field.getKey(), new LinkedTreeMap<>());
Map<String, AtomicInteger> values = answers.get(field.getKey());
if (field.getValue() instanceof Map)
((Map<String, Boolean>) field.getValue()).entrySet().stream()
.filter(value -> Boolean.TRUE.equals(value.getValue()))
.forEach(value -> {
values.putIfAbsent(value.getKey(), new AtomicInteger());
values.get(value.getKey()).incrementAndGet();
});
else {
values.putIfAbsent(field.getValue().toString(), new AtomicInteger());
values.get(field.getValue().toString()).incrementAndGet();
}
}));
assertThat(field(answers, "field1000"), is("[1=2, 2=3, 3=2]"));
assertThat(field(answers, "field2000"), is("[1=3, 2=1]"));
assertThat(field(answers, "field3000"), is("[1=3]"));
assertThat(field(answers, "field4000"), is("[1=1, 2=2]"));
assertThat(field(answers, "field5000"), is("[1=1, 2=1, 3=1]"));
}