使用 java 流找到最大的 3 家商店
find the largest 3 shops using java stream
我有一个商店对象列表,这些对象按所拥有的商品分组。
class Shop{
String shopName;
String item;
int size;
...}
如何获得每件商品最大的 3 家商店(或 n 家最大的商店)的列表?
IE。假设我有
Shop("Walmart", "Hammer", 100);
Shop("Target", "Scissor", 30);
Shop("Walgreens", "Hammer", 300);
Shop("Glens", "Hammer", 500);
Shop("Walmart", "Scissor", 75);
Shop("Toms", "Hammer", 150);
我想要 return 按项目分组的前 3 家商店的列表。
我对项目进行了分组,但我不确定如何遍历给定的地图或条目集...
public class Shop {
int size;
String item;
String name;
public Shop(int size, String item, String name){
this.size = size;
this.item = item;
this.name = name;
}
//Return a list of the top 3 largest shops by item
public static void main(){
List<Shop> shops = new LinkedList<Shop>();
Comparator<Shop> shopComparator = new Comparator<Shop>(){
@Override
public int compare(Shop f1, Shop f2) {
return f1.getSize() < f2.getSize() ? 1 : -1;
}
};
shops.stream().collect(groupingBy(Shop::getItem))
.entrySet()
.stream()
.filter(entry -> entry.getValue().stream().map )
.forEach(item -> item.getValue())//Stuck here
;
}
}
那么,您可以采取以下步骤:
使用 groupingBy(Shop::getItem)
,您可以创建一个按项目排序的地图,因此您的结果将是 Map<String, List<Shop>>
,其中列表包含具有该项目的所有商店。
现在我们需要对 List<Shop>
进行倒序排序,因此列表的顶部项目是规模最大的商店。为了做到这一点,我们可以使用 collectingAndThen
作为 下游收集器 到 groupingBy
.
Collectors.collectingAndThen(Collectors.toList(), finisherFunction);
我们的整理函数应该对列表进行排序:
list -> {
Collections.sort(list, Comparator.comparing(Shop::size).reversed());
return list;
}
这将导致 Map<String, List<Shop>>
,其中列表排序,最大的排在前面。
现在我们唯一需要做的就是将列表大小限制为 3。我们可以使用 subList
。我认为如果列表包含的项目少于 3 个,subList
会抛出异常,因此我们需要使用 Math.min(3, list.size())
来考虑这一点。
list -> {
Collections.sort(list, Comparator.comparing(Shop::size).reversed());
return list.subList(0, Math.min(3, list.size()));
}
整个代码如下所示:
shops.stream()
.collect(groupingBy(Shop::item, Collectors.collectingAndThen(Collectors.toList(), list -> {
Collections.sort(list, Comparator.comparing(Shop::size).reversed());
return list.subList(0, Math.min(3, list.size()));
})));
您可以创建一个小的 class 来自动执行此操作,而不是 'manually' 对列表进行排序并将其限制为 3 个 - 在添加元素时对列表进行限制和排序。
不像MC Emperor那么花哨,但它似乎有效。
我从你已经做过的部分开始:
shops.stream().collect(Collectors.groupingBy(Shop::getItem))
.entrySet().stream().map(entry -> {
entry.setValue(entry.getValue().stream()
.sorted(Comparator.comparingInt(s->-s.size))
.limit(3) // only keep top 3
.collect(Collectors.toList()));
return entry;
}).forEach(item -> {
System.out.println(item.getKey()+":"+item.getValue());
});
您可以将 groupingBy
与 limit
一起使用以获得所需的结果:
import static java.util.stream.Collectors.*;
// Define the sort logic. reversed() applies asc order (Default is desc)
Comparator<Shop> sortBySize = Comparator.comparingInt(Shop::getSize).reversed();
int limit = 3; // top n items
var itemToTopNShopsMap = list.stream().collect(
collectingAndThen(groupingBy(Shop::getItem),
itemToShopsMap -> getTopNShops(sortBySize, itemToShopsMap, limit)));
static Map<String, List<Shop>> getTopNShops(Comparator<Shop> sortBy, Map<String, List<Shop>> inMap, int limit) {
var returningMap = new HashMap<String, List<Shop>>();
for (var i : inMap.entrySet()) {
returningMap.put(i.getKey(), i.getValue().stream().sorted(sortBy).limit(Long.valueOf(limit)).collect(toList()));
}
return returningMap;
}
我们采取了以下步骤:
- 按 'item'
对列表进行分组
- 对于每个分组,即从项目到商店条目列表,我们按照预定义的排序逻辑对商店列表进行排序,并收集 (
limit
) 前 n 个结果。
注:
在静态方法 getTopNShops
中,避免了源映射的突变。我们可以将此方法编写为流,但流版本的可读性可能不如 foreach
循环。
关于流,您可以了解的最重要的一点是,无论以何种标准衡量,它们在本质上并不比同等方法“更好”。有时,它们使代码更具可读性,有时则不然。使用它们来阐明您的代码,并在它们混淆代码时避免使用它们。
在这种情况下,通过为此目的使用收集器,您的代码将更具可读性。编写自己的代码相当容易,如果您真的想更好地理解流,我建议您将其作为简单的学习练习。
在这里,我使用 StreamEx 库中的 MoreCollectors.greatest()
:
Comparator<Shop> bySize = Comparator.comparingInt(Shop::getSize);
Map<String, List<Shop>> biggestByItem
= shops.stream().collect(groupingBy(Shop::getItem, greatest(bySize, 3)));
这不是更好,因为它更短,或者因为它更快并且使用常量内存;它更好,因为复杂性被排除在代码之外,并隐藏在解释行为的有意义的名称后面。您已经编写(或引用)了一个具有清晰行为的可重用收集器,而不是在您的应用程序中散布需要独立读取、测试和维护的复杂管道。
正如我提到的,理解 Collector
的各个部分如何协同工作有一点学习曲线,但值得研究。这是类似收集器的可能实现:
public static <T> Collector<T, ?, List<T>> top(int limit, Comparator<? super T> order) {
if (limit < 1) throw new IndexOutOfBoundsException(limit);
Objects.requireNonNull(order);
Supplier<Queue<T>> supplier = () -> new PriorityQueue<>(order);
BiConsumer<Queue<T>, T> accumulator = (q, e) -> collect(order, limit, q, e);
BinaryOperator<Queue<T>> combiner = (q1, q2) -> {
q2.forEach(e -> collect(order, limit, q1, e));
return q1;
};
Function<Queue<T>, List<T>> finisher = q -> {
List<T> list = new ArrayList<>(q);
Collections.reverse(list);
return list;
};
return Collector.of(supplier, accumulator, combiner, finisher, Collector.Characteristics.UNORDERED);
}
private static <T> void collect(Comparator<? super T> order, int limit, Queue<T> q, T e) {
if (q.size() < limit) {
q.add(e);
} else if (order.compare(e, q.peek()) > 0) {
q.remove();
q.add(e);
}
}
给定这个工厂,创建其他给你bottom(3, bySize)
等的工厂是微不足道的
您可能对此 及其答案感兴趣。
我有一个商店对象列表,这些对象按所拥有的商品分组。
class Shop{
String shopName;
String item;
int size;
...}
如何获得每件商品最大的 3 家商店(或 n 家最大的商店)的列表? IE。假设我有
Shop("Walmart", "Hammer", 100);
Shop("Target", "Scissor", 30);
Shop("Walgreens", "Hammer", 300);
Shop("Glens", "Hammer", 500);
Shop("Walmart", "Scissor", 75);
Shop("Toms", "Hammer", 150);
我想要 return 按项目分组的前 3 家商店的列表。 我对项目进行了分组,但我不确定如何遍历给定的地图或条目集...
public class Shop {
int size;
String item;
String name;
public Shop(int size, String item, String name){
this.size = size;
this.item = item;
this.name = name;
}
//Return a list of the top 3 largest shops by item
public static void main(){
List<Shop> shops = new LinkedList<Shop>();
Comparator<Shop> shopComparator = new Comparator<Shop>(){
@Override
public int compare(Shop f1, Shop f2) {
return f1.getSize() < f2.getSize() ? 1 : -1;
}
};
shops.stream().collect(groupingBy(Shop::getItem))
.entrySet()
.stream()
.filter(entry -> entry.getValue().stream().map )
.forEach(item -> item.getValue())//Stuck here
;
}
}
那么,您可以采取以下步骤:
使用
groupingBy(Shop::getItem)
,您可以创建一个按项目排序的地图,因此您的结果将是Map<String, List<Shop>>
,其中列表包含具有该项目的所有商店。现在我们需要对
List<Shop>
进行倒序排序,因此列表的顶部项目是规模最大的商店。为了做到这一点,我们可以使用collectingAndThen
作为 下游收集器 到groupingBy
.Collectors.collectingAndThen(Collectors.toList(), finisherFunction);
我们的整理函数应该对列表进行排序:
list -> { Collections.sort(list, Comparator.comparing(Shop::size).reversed()); return list; }
这将导致
Map<String, List<Shop>>
,其中列表排序,最大的排在前面。现在我们唯一需要做的就是将列表大小限制为 3。我们可以使用
subList
。我认为如果列表包含的项目少于 3 个,subList
会抛出异常,因此我们需要使用Math.min(3, list.size())
来考虑这一点。list -> { Collections.sort(list, Comparator.comparing(Shop::size).reversed()); return list.subList(0, Math.min(3, list.size())); }
整个代码如下所示:
shops.stream()
.collect(groupingBy(Shop::item, Collectors.collectingAndThen(Collectors.toList(), list -> {
Collections.sort(list, Comparator.comparing(Shop::size).reversed());
return list.subList(0, Math.min(3, list.size()));
})));
您可以创建一个小的 class 来自动执行此操作,而不是 'manually' 对列表进行排序并将其限制为 3 个 - 在添加元素时对列表进行限制和排序。
不像MC Emperor那么花哨,但它似乎有效。 我从你已经做过的部分开始:
shops.stream().collect(Collectors.groupingBy(Shop::getItem))
.entrySet().stream().map(entry -> {
entry.setValue(entry.getValue().stream()
.sorted(Comparator.comparingInt(s->-s.size))
.limit(3) // only keep top 3
.collect(Collectors.toList()));
return entry;
}).forEach(item -> {
System.out.println(item.getKey()+":"+item.getValue());
});
您可以将 groupingBy
与 limit
一起使用以获得所需的结果:
import static java.util.stream.Collectors.*;
// Define the sort logic. reversed() applies asc order (Default is desc)
Comparator<Shop> sortBySize = Comparator.comparingInt(Shop::getSize).reversed();
int limit = 3; // top n items
var itemToTopNShopsMap = list.stream().collect(
collectingAndThen(groupingBy(Shop::getItem),
itemToShopsMap -> getTopNShops(sortBySize, itemToShopsMap, limit)));
static Map<String, List<Shop>> getTopNShops(Comparator<Shop> sortBy, Map<String, List<Shop>> inMap, int limit) {
var returningMap = new HashMap<String, List<Shop>>();
for (var i : inMap.entrySet()) {
returningMap.put(i.getKey(), i.getValue().stream().sorted(sortBy).limit(Long.valueOf(limit)).collect(toList()));
}
return returningMap;
}
我们采取了以下步骤:
- 按 'item' 对列表进行分组
- 对于每个分组,即从项目到商店条目列表,我们按照预定义的排序逻辑对商店列表进行排序,并收集 (
limit
) 前 n 个结果。
注:
在静态方法 getTopNShops
中,避免了源映射的突变。我们可以将此方法编写为流,但流版本的可读性可能不如 foreach
循环。
关于流,您可以了解的最重要的一点是,无论以何种标准衡量,它们在本质上并不比同等方法“更好”。有时,它们使代码更具可读性,有时则不然。使用它们来阐明您的代码,并在它们混淆代码时避免使用它们。
在这种情况下,通过为此目的使用收集器,您的代码将更具可读性。编写自己的代码相当容易,如果您真的想更好地理解流,我建议您将其作为简单的学习练习。
在这里,我使用 StreamEx 库中的 MoreCollectors.greatest()
:
Comparator<Shop> bySize = Comparator.comparingInt(Shop::getSize);
Map<String, List<Shop>> biggestByItem
= shops.stream().collect(groupingBy(Shop::getItem, greatest(bySize, 3)));
这不是更好,因为它更短,或者因为它更快并且使用常量内存;它更好,因为复杂性被排除在代码之外,并隐藏在解释行为的有意义的名称后面。您已经编写(或引用)了一个具有清晰行为的可重用收集器,而不是在您的应用程序中散布需要独立读取、测试和维护的复杂管道。
正如我提到的,理解 Collector
的各个部分如何协同工作有一点学习曲线,但值得研究。这是类似收集器的可能实现:
public static <T> Collector<T, ?, List<T>> top(int limit, Comparator<? super T> order) {
if (limit < 1) throw new IndexOutOfBoundsException(limit);
Objects.requireNonNull(order);
Supplier<Queue<T>> supplier = () -> new PriorityQueue<>(order);
BiConsumer<Queue<T>, T> accumulator = (q, e) -> collect(order, limit, q, e);
BinaryOperator<Queue<T>> combiner = (q1, q2) -> {
q2.forEach(e -> collect(order, limit, q1, e));
return q1;
};
Function<Queue<T>, List<T>> finisher = q -> {
List<T> list = new ArrayList<>(q);
Collections.reverse(list);
return list;
};
return Collector.of(supplier, accumulator, combiner, finisher, Collector.Characteristics.UNORDERED);
}
private static <T> void collect(Comparator<? super T> order, int limit, Queue<T> q, T e) {
if (q.size() < limit) {
q.add(e);
} else if (order.compare(e, q.peek()) > 0) {
q.remove();
q.add(e);
}
}
给定这个工厂,创建其他给你bottom(3, bySize)
等的工厂是微不足道的
您可能对此