简体   繁体   English

如何在保留订单的同时将List <P>中的元素分组到Map <K,List <V >>?

[英]How do you group elements in a List<P> to a Map<K, List<V>> while retaining order?

I have a List of Google PlaceSummary objects taken from the Google Places API. 我有一个从Google Places API中获取的Google PlaceSummary对象列表。 I'd like to collect and group them by their Google Place ID, but also retain the order of the elements. 我想通过他们的Google商家信息ID收集和分组,但也保留元素的顺序。 What I thought would work would be: 我认为会起作用的是:

Map<String, List<PlaceSummary>> placesGroupedByPlaceId =
            places.stream()
                  .collect(Collectors.groupingBy(
                          PlaceSummary::getPlaceId,
                          LinkedHashMap::new,
                          Collectors.mapping(PlaceSummary::getPlaceId, toList())
                  ));

But it won't even compile. 但它甚至不会编译。 It looks like it should according to the Java API documentation on Collectors . 它看起来应该根据收集器上Java API文档

Previously I had this code: 以前我有这个代码:

    Map<String, List<PlaceSummary>> placesGroupedByPlaceId = places.stream()
            .collect(Collectors.groupingBy(PlaceSummary::getPlaceId));

However standard .collect() on the Streams API does not retain the order of elements in the subsequent HashMap (obviously since HashMap s are unordered). 但是,Streams API上的标准.collect()不保留后续HashMap中元素的顺序(显然,因为HashMap是无序的)。 I wish for the output to be a LinkedHashMap so that the Map is sorted by the insertion order of each bucket. 我希望输出是LinkedHashMap以便按照每个存储桶的插入顺序对Map进行排序。

However, the solution I suggested doesn't compile. 但是,我建议的解决方案不能编译。 Firstly, it doesn't recognise the PlaceSummary::getPlaceId since it says it's not a function - even though I know it is. 首先,它不识别PlaceSummary::getPlaceId因为它说它不是一个函数 - 即使我知道它是。 Secondly, it says I cannot convert LinkedHashMap<Object, Object> into M. M is supposed to be a generic collection, so it should be accepted. 其次,它说我不能将LinkedHashMap<Object, Object>转换为M.M应该是一个通用集合,所以它应该被接受。

How do I convert the List into a LinkedHashMap using the Java Stream API? 如何使用Java Stream API将List转换为LinkedHashMap Is there a succinct way to do it? 有简洁的方法吗? If it's too difficult to understand I may just resort to old school pre-Java 8 methods. 如果它太难理解我可能只是采用旧学前Java 8方法。

I noticed that there is another Stack Overflow answer on converting List to LinkedHashMap , but this doesn't have a solution I want as I need to collect 'this' the object I'm specifically iterating over. 我注意到在将List转换为LinkedHashMap时还有另一个Stack Overflow答案 ,但是这没有我想要的解决方案,因为我需要收集'this'我正在迭代的对象。

You're really close to what you want: 你真的很接近你想要的:

Map<String, List<PlaceSummary>> placesGroupedByPlaceId =
            places.stream()
                  .collect(Collectors.groupingBy(
                          PlaceSummary::getPlaceId,
                          LinkedHashMap::new,
                          Collectors.mapping(Function.identity(), Collectors.toList())
                  ));

In the Collectors.mapping method, you need to give the PlaceSummary instance and not the place ID. Collectors.mapping方法中,您需要提供PlaceSummary实例而不是地点ID。 In the code above, I used Function.identity() : this collector is used to build the values so we need to accumulate the places themselves (and not their ID). 在上面的代码中,我使用了Function.identity() :这个收集器用于构建值,因此我们需要累积位置本身(而不是它们的ID)。

Note that it is possible to write directly Collectors.toList() instead of Collectors.mapping(Function.identity(), Collectors.toList()) . 请注意,可以直接编写Collectors.toList()而不是Collectors.mapping(Function.identity(), Collectors.toList())

The code you have so far does not compile because it is in fact creating a Map<String, List<String>> : you are accumulating the IDs for each ID (which is quite weird). 到目前为止你的代码没有编译,因为它实际上是在创建一个Map<String, List<String>> :你正在为每个ID累积ID(这很奇怪)。


You could write this as a generic method: 您可以将其写为通用方法:

private static <K, V> Map<K, List<V>> groupByOrdered(List<V> list, Function<V, K> keyFunction) {
    return list.stream()
                .collect(Collectors.groupingBy(
                    keyFunction,
                    LinkedHashMap::new,
                    Collectors.toList()
                ));
}

and use it like this: 并像这样使用它:

Map<String, List<PlaceSummary>> placesGroupedById = groupByOrdered(places, PlaceSummary::getPlaceId);

I think you got a little confused about the final collector. 我觉得你对最后的收藏家有点困惑。 It merely represents what needs to be in each map value. 它仅代表每个地图值中需要的内容。 There is no need to have a secondary mapping collector, as you just want a list of the original objects. 不需要二级mapping收集器,因为您只需要一个原始对象的列表。

    Map<String, List<PlaceSummary>> placesGroupedByPlaceId =
          places.stream()
                .collect(Collectors.groupingBy(PlaceSummary::getPlaceId,
                                               LinkedHashMap::new,
                                               Collectors.toList()));
/**
 * I have written this code more generic, if you want then you can group based on any * 
 * instance variable , id, name etc via passing method reference.
**/

class Student {
    private int id;
    private String name;
    public Student(int id, String name) {this.id = id;this.name = name;}
    /**
     * @return the id
     */
    public int getId() {return id;}
    /**
     * @param id
     *            the id to set
     */
    public void setId(int id) {this.id = id;}
    /**
     * @return the name
     */
    public String getName() {return name;}
    /**
     * @param name
     *            the name to set
     */
    public void setName(String name) {this.name = name;}
}

public class StudentMain {

    public static void main(String[] args) {

        List<Student> list = new ArrayList<>();
        list.add(new Student(1, "Amit"));
        list.add(new Student(2, "Sumit"));
        list.add(new Student(1, "Ram"));
        list.add(new Student(2, "Shyam"));
        list.add(new Student(3, "Amit"));
        list.add(new Student(4, "Pankaj"));

        Map<?, List<Student>> studentById = groupByStudentId(list,
                Student::getId);
        System.out.println(studentById);

       Map<?, List<Student>> studentByName = groupByStudentId(list,
                Student::getName);
        System.out.println(studentByName);

    }

    private static <K, V> Map<?, List<V>> groupByStudentId(List<V> list,
            Function<V, K> keyFunction) {
        return list.stream().collect(
                Collectors.groupingBy(keyFunction, HashMap::new,
                        Collectors.toList()));
    }
}

If you need a grouping while mantaining the order and apply a function(Reduction) perhaps counting i use something like this. 如果你需要在保持订单的同时进行分组并应用一个函数(减少),或许可以计算我使用这样的东西。

final Map<Integer,Long>map=stream.collect(Collectors.groupingBy(function
   ,LinkedHashMap::new
   ,Collectors.collectingAndThen(Collectors.counting(),Function.identity()))
 )

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM