Search code examples
javahashmapjava-streammax

How use stream max for a List of Maps? ArrayList<HashMap<String, Object>>


I have an ArrayList of HashMaps and each HashMap looks like:

{"Start":"A", "End":"B","Length":5}

I want to find the one that has the longest length, or maybe more than one, they all have the same length equal to the max length.

Trying to use stream, how should I do it?

ArrayList<HashMap<String, Object>> resultslist = new ArrayList<HashMap<String, Object>>();
ArrayList<HashMap<String, Object>> finalresult = resultslist.stream().max()

Solution

  • Use the Power of Objects

    hashmap looks like {"Start":"A", "End":"B","Length":5}

    You are misusing Maps, it definitely should be a custom object, with attributes having proper types instead of storing them as java.lang.Object in the map.

    For example, that how it might look like, if we implemented as a Java 16 record:

    public record Foo(String start, String end,int length) {}
    

    Now things would be simple, instead of a nested collection you would have a list of Foo.

    Find a single Max element

    To find a Foo with maximum length you can use either Stream.max() or Collections.max(), both expect an instance of Comparator as an argument.

    List<Foo> foos = // intializing the list
    
    // with Stream API
    Foo max = foos.stream()
        .max(Comparator.comparingInt(Foo::length)) // produces Optional<Foo>
        .orElseThrow();
    
    // using Collections.max()
    Foo max = Collections.max(foos, Comparator.comparingInt(Foo::length));
    

    Find a group of elements

    If you want to obtain a Collection of objects having the largest value of length, then it would require a bit more effort.

    For that can group the data by length into an intermediate Map by using Collector groupingBy(), then create a stream over map entries and pick the entry with the highest key using Stream.max() (like it has been done above):

    List<Foo> foos = // intializing the list
            
    List<Foo> max = foos.stream()
        .collect(Collectors.groupingBy(Foo::length)) // Map<Integer, List<Foo>>
        .entrySet().stream()             // Stream<Map.Entry<Integer, List<Foo>>>
        .max(Map.Entry.comparingByKey()) // Optional<Map.Entry<Integer, Foo>>
        .map(Map.Entry::getValue)        // Optional<List<Foo>>
        .orElse(Collections.emptyList());
    

    Alternatively, it can be done without creating an intermediate Map and the second stream over its entries.

    To achieve that we can use the three-args version of Stream.collect() and accumulate stream elements into a list that would be returned as the result of the stream execution:

    List<Foo> foos = // intializing the list
            
    List<Foo> max = foos.stream()
        .collect(
            ArrayList::new,
            (List<Foo> l, Foo f) -> {
                if (!l.isEmpty() && l.get(0).length() < f.length()) l.clear();
                if (l.isEmpty() || l.get(0).length() == f.length()) l.add(f);
            },
            (l, r) -> {
                if (l.get(0).length() < r.get(0).length()) l.clear();
                if (l.isEmpty() || l.get(0).length() == r.get(0).length()) l.addAll(r);
            }
        );
    

    Sidenote: you might also want to learn What does it mean to "program to an interface"?