Java 8复杂的自定义收集器

时间:2015-12-22 13:42:53

标签: java java-8 java-stream

我有一个对象流,我想按以下方式收集。

我们说我们正在处理论坛帖子:

class Post {
    private Date time;
    private Data data
}

我想创建一个按期间对帖子进行分组的列表。如果X分钟没有帖子,请创建新组。

class PostsGroup{
    List<Post> posts = new ArrayList<> ();
}

我希望得到一个List<PostGroups>,其中包含按时间间隔分组的帖子。

示例:间隔10分钟。

帖子:

[{time:x, data:{}}, {time:x + 3, data:{}} , {time:x+ 12, data:{}, {time:x + 45, data:{}}}]

我想获得一个帖子组列表:

[
 {posts : [{time:x, data:{}}, {time:x + 3, data:{}}, {time:x+ 12, data:{}]]},
{posts : [{time:x+ 45, data:{}]}
]
  • 注意第一组持续到X + 22.然后在X + 45收到一个新帖子。

这可能吗?

2 个答案:

答案 0 :(得分:2)

使用groupRuns库的StreamEx方法可以轻松解决此问题:

long MAX_INTERVAL = TimeUnit.MINUTES.toMillis(10);
StreamEx.of(posts)
        .groupRuns((p1, p2) -> p2.time.getTime() - p1.time.getTime() <= MAX_INTERVAL)
        .map(PostsGroup::new)
        .toList();

我假设你有一个构造函数

class PostsGroup {
    private List<Post> posts;

    public PostsGroup(List<Post> posts) {
        this.posts = posts;
    }
}

StreamEx.groupRuns方法采用BiPredicate,它应用于两个相邻的输入元素,如果必须将它们组合在一起,则返回true。此方法创建列表流,其中每个列表代表该组。这种方法很懒惰,并行使用并行流。

答案 1 :(得分:0)

您需要在流条目之间保留状态,并为自己编写分组分类器。这样的事情将是一个良好的开端。

class Post {

    private final long time;
    private final String data;

    public Post(long time, String data) {
        this.time = time;
        this.data = data;
    }

    @Override
    public String toString() {
        return "Post{" + "time=" + time + ", data=" + data + '}';
    }

}

public void test() {
    System.out.println("Hello");
    long t = 0;
    List<Post> posts = Arrays.asList(
            new Post(t, "One"),
            new Post(t + 1000, "Two"),
            new Post(t + 10000, "Three")
    );
    // Group every 5 seconds.
    Map<Long, List<Post>> gouped = posts
            .stream()
            .collect(Collectors.groupingBy(new ClassifyByTimeBetween(5000)));
    gouped.entrySet().stream().forEach((e) -> {
        System.out.println(e.getKey() + " -> " + e.getValue());
    });

}

class ClassifyByTimeBetween implements Function<Post, Long> {

    final long delay;
    long currentGroupBy = -1;
    long lastDateSeen = -1;

    public ClassifyByTimeBetween(long delay) {
        this.delay = delay;
    }

    @Override
    public Long apply(Post p) {
        if (lastDateSeen >= 0) {
            if (p.time > lastDateSeen + delay) {
                // Grab this one.
                currentGroupBy = p.time;
            }
        } else {
            // First time - start there.
            currentGroupBy = p.time;
        }
        lastDateSeen = p.time;
        return currentGroupBy;
    }

}