【问题标题】:find the largest 3 shops using java stream使用 java 流找到最大的 3 家商店
【发布时间】:2021-10-21 14:47:52
【问题描述】:

我有一个按商品分组的商店物品列表。

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);

我想返回按商品分组的前 3 家商店的列表。 我对项目进行了分组,但我不确定如何遍历给定的 Map 或条目集...

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
            ;
  }

}

【问题讨论】:

  • 前 3 名是什么意思?你想得到特定的物品吗?或所有项目?,你能显示预期的输出吗?
  • 这是一项需要使用Stream 的学校作业吗?当然可以,但对于大型数据集,使用堆比我想到的用于流的方法更有效,并且可能更具可读性。
  • @erickson 这不是学校作业 - 只是一个个人项目,并试图更好地掌握如何有效地使用流。
  • 我对代码有点困惑, .stream().map 似乎是不完整的代码,也许是为了调用 map()?
  • @Gimby - 这就是我卡住的地方。我不确定如何处理 groupingBy 中的 Map 对象

标签: java java-8 java-stream


【解决方案1】:

您可以了解的关于流的最重要的一点是,无论如何,它们本质上并不比等效方法“更好”。有时,它们使代码更具可读性,有时则不那么可读。使用它们来澄清您的代码,并在它们混淆代码时避免它们。

在这种情况下,通过为此目的使用收集器,您的代码将更具可读性。自己编写代码相当容易,如果您真的想更好地理解流,我建议将其作为一个简单的学习练习。

这里,我使用的是 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)等的工厂是微不足道的。

您可能对此related question 及其答案感兴趣。

【讨论】:

  • 天啊!哇!心态崩了!感谢埃里克森的洞察力。我什至都不知道这是可能的——向你致敬
【解决方案2】:

嗯,你可以采取以下步骤:

  • 使用groupingBy(Shop::getItem),您可以创建按商品排序的地图,因此您的结果将是Map&lt;String, List&lt;Shop&gt;&gt;,其中列表包含该商品的所有商店。

  • 现在我们需要对List&lt;Shop&gt; 进行倒序排序,所以列表的顶部项目是最大尺寸的商店。为了做到这一点,我们可以使用collectingAndThen 作为groupingBy下游收集器

    Collectors.collectingAndThen(Collectors.toList(), finisherFunction);
    

    我们的 finisher 函数应该对列表进行排序:

    list -> {
        Collections.sort(list, Comparator.comparing(Shop::size).reversed());
        return list;
    }
    

    这将产生一个Map&lt;String, List&lt;Shop&gt;&gt;,其中列表已排序,最大大小在前。

  • 现在我们唯一需要做的就是将列表大小限制为 3。我们可以使用 subList。我认为subList 会在列表包含少于 3 个项目时抛出异常,因此我们需要使用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()));
    })));

Online demo


您可以创建一个自动执行此操作的小类,而不是“手动”对列表进行排序并将其限制为 3 - 在添加元素时对列表进行限制和排序。

【讨论】:

  • 是的,我至少会将代码迁移到一个单独的类或方法中 - 只是使用流 atm。我不知道你可以用“collectingAndThen”做到这一点
  • 在返回列表行中,我得到“lambda 表达式中的返回类型错误:List 无法转换为 List”。
  • 好吧,如果您将结果存储在 Map&lt;String, List&lt;Shop&gt;&gt; 变量中,那么它应该工作。
【解决方案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());
    });

【讨论】:

    【解决方案4】:

    您可以使用groupingBylimit 来获得所需的结果:

    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;
    }
    
    

    我们采取了以下步骤:

    1. 按“项目”对列表进行分组
    2. 对于每个分组,即条目到商店列表条目,我们通过预定义的排序逻辑对商店列表进行排序,并收集 (limit) 前 n 个结果。

    注意:
    在静态方法getTopNShops 中,避免了源映射的突变。我们可以将此方法编写为流,但流版本的可读性可能不如foreach 循环。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-11-21
      • 1970-01-01
      • 2016-12-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-12-16
      相关资源
      最近更新 更多