在Java 8中,如何通过多个属性将一个对象内的列表进行分组?

4

如何通过对象的两个属性对其中的列表进行分组?

我正在使用Drools 7.9.0和Java 8。我有一个Result类,它作为每个匹配的Drools规则的返回值。

import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

@Data
@NoArgsConstructor
public class Result implements Serializable {

    private Integer id;
    private String name;
    private List<Occurrences> occurrences = new ArrayList<>();

    public void addOccurrence(Occurrences occurrence) {
        this.occurrences.add(occurrence);
    }
}

在Drools执行后,我得到了一个List<Result>。将其转换为JSON,它看起来像这样。 当前结果:
[
     {
        name: "Whatever"
        id: 0001,
        occurrences: [{
           (...)
        }]
    },
    {
        name: "Whatever"
        id: 0001,
        occurrences: [{
           (...)
        }]
    },
    {
        name: "Whatever"
        id: 0002,
        occurrences: [{
           (...)
        }]
    },
    {
        name: "Other thing"
        id: 0002,
        occurrences: [{
           (...)
        }]
    }
]

我需要将我的Result列表分组,以便按idname分组ocurrences,像这样。

预期结果:

[
    {
        name: "Whatever"
        id: 0001,
        occurrences: [{
           (...)
        }, {
           (...)
        }]
    },
    {
        name: "Whatever"
        id: 0002,
        occurrences: [{
           (...)
        }]
    },
    {
        name: "Other thing"
        id: 0002,
        occurrences: [{
           (...)
        }]
    }
]


什么是最好的实现方法?我有两个选项:
1.更改Drools规则,使我的 List<Result>已按我需要的方式结构化。也许创建一个类,其中包含addResult方法,该方法检查列表中的idname并将出现次数添加到正确的条目。但这不是理想的,因为它会增加规则内部的复杂性。
2.后处理我的 List<Result>,使其按idname分组occurrences。但我不知道如何以优化和简单的方式实现这一点。
哪种方法是最佳的?
4个回答

3
你可以这样做:
Map<String, List<Result>> resultsWithSameIdAndName = results.stream()
    .collect(Collectors.groupingBy(r -> r.getId() + ":" + r.getName()));

List<Result> mergedResults = resultsWithSameIdAndName.entrySet().stream()
    .map(e -> new Result(Integer.valueOf(e.getKey().split(":")[0]), 
        e.getKey().split(":")[1],
        e.getValue().stream().flatMap(r -> r.getOccurrences().stream())
            .collect(Collectors.toList())))
        .collect(Collectors.toList());

2
一个简单的第二种选项的方法可以是:
Map<String, List<Occurences>> resultsMapped = new HashMap<>();
List<Result> collect = results.forEach(r -> resultsMapped.merge(String.format("%s%d", r.name, r.id), 
            r.occurrences, (currentOccurences, newOccurences) -> {
               currentOccurences.addAll(newOccurences);
               return currentOccurences;
            });

将出现的地点以映射方式存储,键名为名称和ID连接而成的字符串。此键未经过优化。


2
你也可以使用一些流来实现这个功能。这将按ID和名称分组,然后为每个组创建一个新的Result对象,其中包含该组的所有出现次数。最初的回答:你还可以使用一些流来实现这个功能。这将按ID和名称分组,然后为每个组创建一个新的Result对象,其中包含该组的所有出现次数。
Collection<Result> processed = results.stream().collect(Collectors.groupingBy(r -> r.getId() + r.getName(), 
            Collectors.collectingAndThen(Collectors.toList(),
            l -> new Result(l.get(0).getId(), l.get(0).getName(), 
                    l.stream().flatMap(c -> c.getOccurrences().stream()).collect(Collectors.toList()))
    ))).values();

需要在Result类中添加AllArgsConstructor,并添加更多的getter方法,但您可以根据需要进行调整。 在此示例中使用的键不安全。"Original Answer"的翻译是"最初的回答"。

2
我会在Result中添加以下方法:

最初的回答中没有这个方法。
Result addOccurrences (List<Occurrences> occurrences) {
  this.occurrences.addAll (occurrences);
  return this;
}

最初的回答:然后您可以使用流:
并且您可以使用流:
List<Result> collect = results.stream ()
  .collect (Collectors.groupingBy (Result::getId, Collectors.groupingBy (Result::getName, Collectors.toList ())))
  .values ()
  .stream ()
  .map (Map::values)
  .flatMap (Collection::stream)
  .map (Collection::stream)
  .map (resultStream -> resultStream.reduce ((r0, r1) -> r0.addOccurrences (r1.getOccurrences ())))
  .filter (Optional::isPresent)
  .map (Optional::get)
  .collect (Collectors.toList ());

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接