当前位置: 代码迷 >> java >> 使用Java lambdas累积答案数
  详细解决方案

使用Java lambdas累积答案数

热度:60   发布时间:2023-07-16 17:38:37.0

我有表单数据条目列表,每个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.现在field93275,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}}

您应该从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))));

我现在写了一些东西,这个结构实际上适用于两种情况,因为最终结果可能完全相同,唯一的区别是解析,因为我必须深入一个地图。 我尝试了相同的函数,然后我必须检查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]"));
  }
  相关解决方案