通过多个属性将对象内的列表分组:Java 8

时间:2019-01-30 16:01:21

标签: java spring-boot java-8 drools

如何通过两个对象属性将一个对象中的列表分组?

我正在使用Drools 7.9.0和Java8。我有一个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的列表进行分组,这样ocurrencesidname分组,就像这样。

预期结果:

[
    {
        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>进行后处理,以便将occurrencesidname分组。但是我不知道如何以优化和简单的方式做到这一点。

做第二个选择的最好方法是什么?

4 个答案:

答案 0 :(得分:2)

您可以这样做

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

答案 1 :(得分: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 :(得分:2)

您也可以为此使用一些流。这将按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();

这需要将AllArgsConstructor和更多的getter添加到Result类,但是您可以根据需要进行调整。 在此示例中,用于分组的密钥并不安全。

答案 3 :(得分: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 ());