【问题标题】:Accumulating count of answers using Java lambdas使用 Java lambdas 累积答案计数
【发布时间】:2015-07-28 02:14:37
【问题描述】:

我有表单数据条目列表,每个DataEntry 包含Map<String, Object> 类型的数据字段。字符串键是动态字段名称(调查问题),值是针对该特定问题回答的选项。

在列表之外,我如何才能获得每个字段名称(问题)的唯一答案的计数,我假设是这样的? Map<String, LinkedTreeMap<String, Integer>,其中外层映射键为字段XXXX,其值映射键(字符串)为唯一答案,键整数为该答案的计数

出于测试目的,它们从数据库中以字符串形式出现,我将其映射到 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 列表如下所示:

最终目标

让我们取第一个字段 field9294,在 3 个数据条目中给出了 3 个唯一答案:4、3、5。这里的计数都应该是 1。 现在 field9327 有答案 5、4、4。这里我们数 5 一次,4 两次。

一般的想法是分别绘制每个问题的数据,以便我可以绘制图表并将结果装饰为例如百分比。

正如我所见,获得Map&lt;String, LinkedTreeMap&lt;String, Integer&gt; 就足以实现这一目标,但是有没有使用花哨的 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());

编辑2: 也在寻找这种结构的解决方案。对于结果,我只关心真正的答案,因为此结构映射到复选框列表,所以对于绘图来说 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}}

【问题讨论】:

  • 您可能正在这里寻找减少操作或自定义收集器
  • 我尝试了许多不同的方法,但缺乏所有功能的知识,无法找到合适的解决方案。我可能会感谢一些示例代码的帮助。

标签: java lambda


【解决方案1】:

您应该从 Stream 开始,因此我会避免将 FormDataDTO 对象收集到列表中。改变这个:

List<FormDataDTO> dtos = dataEntries.stream().map(mapper::dataEntryToDto).collect(Collectors.toList());

仅此:

Stream<FormDataDTO> dtos = dataEntries.stream().map(mapper::dataEntryToDto);

然后您可以使用groupingBy 调用来收集它们,该调用本身使用另一个收集器来创建地图值:

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))));

【讨论】:

  • 哇,这似乎真的有效,我什至没有接近。非常感谢:)
  • @Vaelyr 似乎......如果我是你,我会担心的;尝试了解它的作用:p
  • 我学习和玩了一下,不用担心,现在清楚了:)再次感谢。
  • @fge 我倾向于同意。我个人会使用老式的循环,所以我会在一个月后看到我能理解的代码。
  • @VGR 我赞同你的评论:p 有一种说法是代码是 WORM(一次编写,多次阅读),我全心全意地订阅 ;)
【解决方案2】:

我现在写了一些东西,该结构实际​​上适用于这两种情况,因为最终结果可能完全相同,唯一的区别是解析,因为我必须深入一张地图。我对函数进行了同样的尝试,然后我必须检查 entry.getValue() 的类型,因为在一种情况下它是 String,而在另一种情况下它是另一个 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]"));
  }

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-05-02
    • 1970-01-01
    • 2017-04-17
    • 2019-07-03
    • 2020-11-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多