it-swarm.com.ru

Как отобразить на несколько элементов с Java 8 потоков?

У меня есть такой класс:

class MultiDataPoint {
  private DateTime timestamp;
  private Map<String, Number> keyToData;
}

и я хочу произвести для каждого MultiDataPoint

class DataSet {
        public String key;    
        List<DataPoint> dataPoints;
}

class DataPoint{
  DateTime timeStamp;
  Number data;
}

конечно, "ключ" может быть одинаковым для нескольких MultiDataPoints.

Так дано List<MultiDataPoint>, как мне использовать Java 8 потоков для преобразования в List<DataSet>?

Вот как я сейчас делаю преобразование без потоков:

Collection<DataSet> convertMultiDataPointToDataSet(List<MultiDataPoint> multiDataPoints)
{

    Map<String, DataSet> setMap = new HashMap<>();

    multiDataPoints.forEach(pt -> {
        Map<String, Number> data = pt.getData();
        data.entrySet().forEach(e -> {
            String seriesKey = e.getKey();
            DataSet dataSet = setMap.get(seriesKey);
            if (dataSet == null)
            {
                dataSet = new DataSet(seriesKey);
                setMap.put(seriesKey, dataSet);
            }
            dataSet.dataPoints.add(new DataPoint(pt.getTimestamp(), e.getValue()));
        });
    });

    return setMap.values();
}
41
pdeva

Это интересный вопрос, потому что он показывает, что существует множество разных подходов для достижения одного и того же результата. Ниже я покажу три разных реализации.


Методы по умолчанию в Collection Framework: Java 8 добавил некоторые методы к классам коллекций, которые не имеют прямого отношения к Stream API . Используя эти методы, вы можете значительно упростить реализацию непотоковой реализации:

Collection<DataSet> convert(List<MultiDataPoint> multiDataPoints) {
    Map<String, DataSet> result = new HashMap<>();
    multiDataPoints.forEach(pt ->
        pt.keyToData.forEach((key, value) ->
            result.computeIfAbsent(
                key, k -> new DataSet(k, new ArrayList<>()))
            .dataPoints.add(new DataPoint(pt.timestamp, value))));
    return result.values();
}

Stream API с плоской и промежуточной структурой данных: Следующая реализация практически идентична решению, предоставленному Стюартом Марксом. В отличие от его решения, следующая реализация использует анонимный внутренний класс в качестве промежуточной структуры данных.

Collection<DataSet> convert(List<MultiDataPoint> multiDataPoints) {
    return multiDataPoints.stream()
        .flatMap(mdp -> mdp.keyToData.entrySet().stream().map(e ->
            new Object() {
                String key = e.getKey();
                DataPoint dataPoint = new DataPoint(mdp.timestamp, e.getValue());
            }))
        .collect(
            collectingAndThen(
                groupingBy(t -> t.key, mapping(t -> t.dataPoint, toList())),
                m -> m.entrySet().stream().map(e -> new DataSet(e.getKey(), e.getValue())).collect(toList())));
}

Stream API с объединением карт: Вместо выравнивания исходных структур данных вы также можете создать карту для каждой MultiDataPoint , а затем объединить все карты в одну карту с помощью операции сокращения. Код немного проще, чем приведенное выше решение:

Collection<DataSet> convert(List<MultiDataPoint> multiDataPoints) {
    return multiDataPoints.stream()
        .map(mdp -> mdp.keyToData.entrySet().stream()
            .collect(toMap(e -> e.getKey(), e -> asList(new DataPoint(mdp.timestamp, e.getValue())))))
        .reduce(new HashMap<>(), mapMerger())
        .entrySet().stream()
        .map(e -> new DataSet(e.getKey(), e.getValue()))
        .collect(toList());
}

Вы можете найти реализацию слияния карт в классе Collectors . К сожалению, получить доступ к нему извне немного сложно. Ниже приведена альтернативная реализация слияния карт :

<K, V> BinaryOperator<Map<K, List<V>>> mapMerger() {
    return (lhs, rhs) -> {
        Map<K, List<V>> result = new HashMap<>();
        lhs.forEach((key, value) -> result.computeIfAbsent(key, k -> new ArrayList<>()).addAll(value));
        rhs.forEach((key, value) -> result.computeIfAbsent(key, k -> new ArrayList<>()).addAll(value));
        return result;
    };
}
55
nosid

Для этого мне пришлось придумать промежуточную структуру данных:

class KeyDataPoint {
    String key;
    DateTime timestamp;
    Number data;
    // obvious constructor and getters
}

При таком подходе подход состоит в том, чтобы "свести" каждую MultiDataPoint в список троек (отметка времени, ключ, данные) и объединить все такие тройки из списка MultiDataPoint.

Затем мы применяем операцию groupingBy к строковому ключу, чтобы собрать данные для каждого ключа вместе. Обратите внимание, что простое groupingBy приведет к отображению каждого строкового ключа в список соответствующих троек KeyDataPoint. Мы не хотим тройки; нам нужны экземпляры DataPoint, которые представляют собой (временная метка, данные) пары. Для этого мы применяем "нисходящий" сборщик groupingBy, который является операцией mapping, которая создает новое DataPoint, получая правильные значения из тройки KeyDataPoint. Нижестоящий коллектор операции mapping - это просто toList, который собирает объекты DataPoint той же группы в список.

Теперь у нас есть Map<String, List<DataPoint>> и ​​мы хотим преобразовать его в коллекцию объектов DataSet. Мы просто выводим записи на карту и создаем объекты DataSet, собираем их в список и возвращаем.

Код в конечном итоге выглядит так:

Collection<DataSet> convertMultiDataPointToDataSet(List<MultiDataPoint> multiDataPoints) {
    return multiDataPoints.stream()
        .flatMap(mdp -> mdp.getData().entrySet().stream()
                           .map(e -> new KeyDataPoint(e.getKey(), mdp.getTimestamp(), e.getValue())))
        .collect(groupingBy(KeyDataPoint::getKey,
                    mapping(kdp -> new DataPoint(kdp.getTimestamp(), kdp.getData()), toList())))
        .entrySet().stream()
        .map(e -> new DataSet(e.getKey(), e.getValue()))
        .collect(toList());
}

Я взял некоторые свободы с конструкторами и получателями, но я думаю, что они должны быть очевидны.

11
Stuart Marks