如何使用Java 8流过滤具有相同id的对象的最大出现次数



我需要找出收到最多评论的newsId

我已经创建了News类,其中我创建了方法变量,构造函数,getter和setter。

我已经创建了Main类,在java 8中使用流编写逻辑。

我在实现Predicate接口过滤掉News对象列表中计数最多的newsId时感到震惊。

public class News {
int newsId;
String postByUser;
String commentByUser;
String comment;
public News(int newsId, String postByUser, String commentByUser, String comment) {
this.newsId = newsId;
this.postByUser = postByUser;
this.commentByUser = commentByUser;
this.comment = comment;
}
public int getNewsId() {
return newsId;
}
public void setNewsId(int newsId) {
this.newsId = newsId;
}
public String getPostByUser() {
return postByUser;
}
public void setPostByUser(String postByUser) {
this.postByUser = postByUser;
}
public String getCommentByUser() {
return commentByUser;
}
public void setCommentByUser(String commentByUser) {
this.commentByUser = commentByUser;
}
public String getComment() {
return comment;
}
public void setComment(String comment) {
this.comment = comment;
}
}
class Main {
static List < News > news = Arrays.asList(
new News(1, "fb_Userpost", "fb_Usercomment", "comment1"),
new News(2, "insta_userpost", "insta_usercomment", "comment2"),
new News(1, "whatsapp_userpost", "whatsapp_usercomment", "comment3"),
new News(1, "whatsapp_userpost", "whatsapp_usercomment", "comment3"),
new News(3, "whatsapp_userpost", "whatsapp_usercomment", "comment3")
);
public static void main(String args[]) {
//   Predicate<News> pr = s -> s
news.stream()
.filter(pr)
.collect(Collectors.toList())
.forEach(s - > System.out.println(s.getNewsId()));
}

Result - a single newsId

我需要找出收到最多评论的newsId

你不能通过单独使用filter()来实现它。并且根本没有必要使用filter()操作。

查找最频繁的新闻,你需要积累数据。方法collect()应该负责这个,而不是filter()

最明显的选择是创建一个中间Map,它将包含每个newsId的计数。要做到这一点,您可以使用收集器groupingBy()counting()的组合。

然后你可以在map表项上创建一个流,并使用max()作为终端操作选择值最高的表项。

public static void main(String args[]) {

news.stream()
.collect(Collectors.groupingBy( // creating an intermediate Map<Integer, Long>
News::getNewsId,            // map's key
Collectors.counting()       // value
))
.entrySet().stream()               // creating a stream over the map's entries
.max(Map.Entry.comparingByValue()) // picking the entry with the highest value -> result: Optional<Map.Entry<Integer, Long>>
.map(Map.Entry::getKey)            // transforming the optional result Optional<Integer> 
.ifPresent(System.out::println);   // printing the result if optional is not empty
}
对于您的示例数据,这段代码将产生输出1

Result -具有最高频率的newsId的列表

为了解决几个newsId出现次数相同的情况,您可以构建一个自定义收集器

最初的想法与上面描述的相同,但是代替了max()操作,这次我们将在map条目流上应用collect(),并且将提供一个自定义收集器作为参数。要创建自定义收集器,可以使用静态方法Collector.of()

下面提供的自定义收集器背后的逻辑如下:

  • supplier-中间结果(map条目)被存储在Queue.

  • accumulator-如果下一个流元素队列中的第一个元素具有相同的频率计数(映射条目的值),或者队列为空,则将其添加到队列中。如果下一个元素计数较低,它将被忽略。如果计数较高,则清理队列,并添加下一个元素

  • combiner在并行执行流时获得的两个队列将使用与上述accumulator几乎相同的逻辑组合在一起。

  • finisher-该函数将map条目的队列转换为newsId列表。

注意这样的实现只允许对条目集进行一次迭代,并且这种性能优势是其复杂性的理由。

public static void main(String args[]) {

news.stream()
.collect(Collectors.groupingBy(
News::getNewsId,
Collectors.counting()
))
.entrySet().stream()
.collect(Collector.of(
ArrayDeque::new,
(Queue<Map.Entry<Integer, Long>> queue, Map.Entry<Integer, Long> entry) -> {
if (queue.isEmpty() || queue.element().getValue().equals(entry.getValue())) {
queue.add(entry);
} else if (queue.element().getValue() < entry.getValue()) {
queue.clear();
queue.add(entry);
}
},
(left, right) -> {
if (left.isEmpty() || !right.isEmpty()
&& right.element().getValue() > left.element().getValue())
return right;
if (right.isEmpty() || left.element().getValue() > right.element().getValue())
return left;

left.addAll(right);
return left;
},
queue -> queue.stream().map(Map.Entry::getKey).collect(Collectors.toList())
))
.forEach(System.out::println);
}
static List<News> news = Arrays.asList( // News `1` & `2` are the most frequent
new News(1, "fb_Userpost", "fb_Usercomment", "comment1"),
new News(2, "insta_userpost", "insta_usercomment", "comment2"),
new News(2, "insta_userpost", "insta_usercomment", "comment2"),
new News(2, "insta_userpost", "insta_usercomment", "comment2"),
new News(1, "whatsapp_userpost", "whatsapp_usercomment", "comment3"),
new News(1, "whatsapp_userpost", "whatsapp_usercomment", "comment3"),
new News(3, "whatsapp_userpost", "whatsapp_usercomment", "comment3")
);

输出:

1
2

这是另一个只使用单个流通道的解决方案,虽然速度方面可能比Alexander的解决方案稍慢(我没有对其进行基准测试),但它要短得多。

import static java.util.function.Function.identity;
import static java.util.stream.Collectors.*;
news.stream()
.map(News::getNewsId)
.collect(
teeing(
groupingBy(identity(), counting()),
collectingAndThen(groupingBy(identity(), counting()), map -> Collections.max(map.values())),
(frequencyMap, max) -> {
frequencyMap.values().removeIf(v -> v != max.longValue());
return frequencyMap.keySet();
}
)
).forEach(System.out::println);

首先,计算每个newsId被引用的次数。然后,找出最大计数。最后,只保留那些具有最大计数的标识符。

Map<Integer, Long> countByNewsId = news.stream()
.map(News::getNewsId)
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
Long max = countByNewsId.values().stream().max(Long::compareTo).orElse(null);
countByNewsId.values().removeIf(Predicate.isEqual(max).negate());
Set<Integer> maxCommentedNewsIds = countByNewsId.keySet();

最新更新