使用流式API在列表中搜索值并保持搜索位置(标准化测量数据)

时间:2018-07-10 19:22:35

标签: java lambda java-8 java-stream

我得到测量数据列表。该列表中的一个条目包含一个timstamp和数据本身。每15分钟就有一个条目或多或少-但是可能还会丢失数据点或抖动很大。 我需要建立一个标准化的数据列表,每15分钟就有一个准确的条目。作为数据,我可以进行之前的测量。

输入:

A    B         C     D  E                       F
|----|---------|-----|--|-----------------------|--> t

输出:

|----|----|----|----|----|----|----|----|----|----|--> t
A    B    B    C    C    E    E    E    E    E    F

如何用Java 8中的流以优雅而有效的方式实现这一点? 它不能是data.stream().filter([...]).findFirst(),因为可能有很多数据点-始终从头开始搜索就太昂贵了。 我对输入数据已经对齐15分钟进行了相同的测试,所以我能够做到

public NormalizedData normalizeData(List<MeasurementData> data, Instant t) {
    return data.stream()
        .filter(d -> Objects.equals(d.getTimestamp().getEpochSecond(), t.getEpochSecond()))
        .map(d -> new NormalizedData(t, d))
        .findFirst()
        .orElse(...);
}

对于所有Instant t来说太慢了。

有什么主意吗?我们应该能够以某种方式将搜索位置存储在流中,并在下一轮继续。或完全不同的方法。 如果存在第三方标准库兼容标准流(例如StreamEx)的解决方案,那么这也是一个选择。

2 个答案:

答案 0 :(得分:3)

下面是一些示例代码,以显示如何实现填充丢失的数据点。

下面的许多代码不是必需的,因为它们只是设置数据以显示代码实际的工作方式。

代码要做的是使用Stream API的收集功能,并将最后收集的data.frame( x = rnorm(1000), spoke = factor(sample(1:6, 1000, replace=T)) ) %>% ggplot(aes(x = spoke, fill=spoke, y = x)) + geom_violin() + coord_polar() + theme( plot.background = element_rect(fill = "darkblue"), panel.background = element_rect(fill = "lightblue", colour = "lightblue")) 与当前的DataPoint进行比较,如果两者之间的时间戳差异大于15分钟,则插入一个新条目。

从测试数据中可以看到,C和D以及E和F之间相差30分钟。这意味着将复制C和E的数据。

代码

DataPoint

输出

private static final long FIFTEEN_MINS_IN_MILLI_SECONDS = 900_000L;

public static void main(String[] args) {
    //This is just to get some realistic times
    long now = System.currentTimeMillis();
    List<DataPoint> data = getDataPoints(now);

    ArrayList<DataPoint> newDataPoints = data.stream().collect(Collector.of(
            ArrayList<DataPoint>::new,
            (ArrayList<DataPoint> dataPoints, DataPoint nextDataPoint) -> {
                if (!dataPoints.isEmpty()) {
                    addPointIfRequired(dataPoints, nextDataPoint);
                }

                dataPoints.add(nextDataPoint);
            },
            (dataPoints, dataPoints2) -> {
                if (dataPoints.isEmpty()) return dataPoints2;

                if (!dataPoints2.isEmpty()) {
                    addPointIfRequired(dataPoints, dataPoints2.get(0));
                    dataPoints.addAll(dataPoints2);
                }

                return dataPoints;
            }
    ));

    newDataPoints.forEach(System.out::println);
}

private static void addPointIfRequired(ArrayList<DataPoint> dataPoints, DataPoint nextDataPoint) {
    DataPoint previousDataPoint = dataPoints.get(dataPoints.size() - 1);
    long timestampDiff = nextDataPoint.timestamp - previousDataPoint.timestamp;

    if (timestampDiff > FIFTEEN_MINS_IN_MILLI_SECONDS) {
        long fifteenMinIncrement = previousDataPoint.timestamp + FIFTEEN_MINS_IN_MILLI_SECONDS;
        DataPoint newEntry = new DataPoint(previousDataPoint.data, fifteenMinIncrement);
        dataPoints.add(newEntry);
    }
}

private static List<DataPoint> getDataPoints(long now) {
    return Arrays.asList(
            //initial time
            new DataPoint("A", now),
            //15 minute increment
            new DataPoint("B", now + FIFTEEN_MINS_IN_MILLI_SECONDS),
            //15 minute increment
            new DataPoint("C", now + (FIFTEEN_MINS_IN_MILLI_SECONDS * 2)),
            //30 minute increment
            new DataPoint("D", now + (FIFTEEN_MINS_IN_MILLI_SECONDS * 4)),
            //15 minute increment
            new DataPoint("E", now + (FIFTEEN_MINS_IN_MILLI_SECONDS * 5)),
            //30 minute increment
            new DataPoint("F", now + (FIFTEEN_MINS_IN_MILLI_SECONDS * 7))
    );
}

private static class DataPoint {
    private final String data;
    private final long timestamp;

    private DataPoint(String data, long timestamp) {
        this.data = data;
        this.timestamp = timestamp;
    }

    @Override
    public String toString() {
        return data + " " + Instant.ofEpochMilli(timestamp);
    }
}

答案 1 :(得分:0)

如果我正确理解OP,以下是StreamEx进行的尝试:

// assume the data is sorted by time
final List<Pair<Integer, String>> data = N.asList(Pair.of(1, "A"), Pair.of(16, "B"), Pair.of(46, "C"),
    Pair.of(60, "D"), Pair.of(76, "E"), Pair.of(151, "F"));
final int startTime = data.get(0).left();
final int interval = 15;

final Map<Integer, Pair<Integer, String>> map = StreamEx.of(data).filter(p -> (p.left() - startTime) % interval == 0).toMap(p -> p.left());

IntStreamEx.rangeClosed(startTime, data.get(data.size() - 1).left(), interval) 
      .forEach(t -> map.computeIfAbsent(t, k -> Pair.of(t, map.get(t - interval).right())));

final List<Pair<Integer, String>> result = StreamEx.of(map).sortedBy(e -> e.getKey()).map(e -> e.getValue()).toList();

System.out.println(result.stream().map(p -> p.right).collect(Collectors.joining("--")));