How to Keep the Iteration Order of a List When Using Collections.Tomap() on a Stream

How do I keep the iteration order of a List when using Collections.toMap() on a stream?

The 2-parameter version of Collectors.toMap() uses a HashMap:

public static <T, K, U> Collector<T, ?, Map<K,U>> toMap(
Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper)
{
return toMap(keyMapper, valueMapper, throwingMerger(), HashMap::new);
}

To use the 4-parameter version, you can replace:

Collectors.toMap(Function.identity(), String::length)

with:

Collectors.toMap(
Function.identity(),
String::length,
(u, v) -> {
throw new IllegalStateException(String.format("Duplicate key %s", u));
},
LinkedHashMap::new
)

Or to make it a bit cleaner, write a new toLinkedMap() method and use that:

public class MoreCollectors
{
public static <T, K, U> Collector<T, ?, Map<K,U>> toLinkedMap(
Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper)
{
return Collectors.toMap(
keyMapper,
valueMapper,
(u, v) -> {
throw new IllegalStateException(String.format("Duplicate key %s", u));
},
LinkedHashMap::new
);
}
}

How to ensure order of processing in java8 streams?

You are asking the wrong question. You are asking about sequential vs. parallel whereas you want to process items in order, so you have to ask about ordering. If you have an ordered stream and perform operations which guarantee to maintain the order, it doesn’t matter whether the stream is processed in parallel or sequential; the implementation will maintain the order.

The ordered property is distinct from parallel vs sequential. E.g. if you call stream() on a HashSet the stream will be unordered while calling stream() on a List returns an ordered stream. Note that you can call unordered() to release the ordering contract and potentially increase performance. Once the stream has no ordering there is no way to reestablish the ordering. (The only way to turn an unordered stream into an ordered is to call sorted, however, the resulting order is not necessarily the original order).

See also the “Ordering” section of the java.util.stream package documentation.

In order to ensure maintenance of ordering throughout an entire stream operation, you have to study the documentation of the stream’s source, all intermediate operations and the terminal operation for whether they maintain the order or not (or whether the source has an ordering in the first place).

This can be very subtle, e.g. Stream.iterate(T,UnaryOperator) creates an ordered stream while Stream.generate(Supplier) creates an unordered stream. Note that you also made a common mistake in your question as forEach does not maintain the ordering. You have to use forEachOrdered if you want to process the stream’s elements in a guaranteed order.

So if your list in your question is indeed a java.util.List, its stream() method will return an ordered stream and filter will not change the ordering. So if you call list.stream().filter() .forEachOrdered(), all elements will be processed sequentially in order, whereas for list.parallelStream().filter().forEachOrdered() the elements might be processed in parallel (e.g. by the filter) but the terminal action will still be called in order (which obviously will reduce the benefit of parallel execution).

If you, for example, use an operation like

List<…> result=inputList.parallelStream().map(…).filter(…).collect(Collectors.toList());

the entire operation might benefit from parallel execution but the resulting list will always be in the right order, regardless of whether you use a parallel or sequential stream.

Java stream map and collect - order of resulting container

Yes, you can expect this even if you are using parallel stream as long as you did not explicitly convert it into unordered() mode.

The ordering never changes in sequential mode, but may change in parallel mode. The stream becomes unordered either:

  • If you explicitly turn it into unordered mode via unordered() call
  • If the stream source reports that it's unordered (for example, HashSet stream is unordered as order is implementation dependent and you cannot rely on it)
  • If you are using unordered terminal operation (for example, forEach() operation or collecting to unordered collector like toSet())

In your case none of these conditions met, thus your stream is ordered.

Why is it not in the order of key when converting key to uppercase even though I use LinkedHashMap

The fact that the input List contains LinkedHashMaps doesn't mean that the output will contain LinkedHashMaps.

If you want LinkedHashMaps, you must request them explicitly in the toMap() call:

static List<Map<String,Object>>  convertKeyCase (List<Map<String,Object>> list,int...s) {

return list.stream()
.map(m -> m.entrySet()
.stream()
.sorted(Map.Entry.comparingByKey())
.collect(Collectors.toMap(p -> s.length == 0 || s[0]==1 ? p.getKey().toUpperCase():p.getKey().toLowerCase(),
Map.Entry::getValue,
(v1,v2)->v1,
LinkedHashMap::new)))
.collect(Collectors.toList());

}

This changes the output to:

[{DATE=2021-06-03, TEMP=17-29°C, WEATHER=1, WIND=2-3}, {DATE=2021-06-04, TEMP=17-30°C, WEATHER=1, WIND=3-4}]
[{date=2021-06-03, temp=17-29°C, weather=1, wind=2-3}, {date=2021-06-04, temp=17-30°C, weather=1, wind=3-4}]

How to group element of the Stream in order to pick a single element based on its poperties

To achieve that, you can group the movies by year and by actor's name creating a nested map. And then process its entries.

It can be done with Collectors.groupingBy(). You need to pass as the first argument a function (Movie::getYear) that extracts a year, and that will be the key of the map.

As a downstream collector of the groupingBy you need to apply flatMapping to extract actors from a movie object. Note, flatMapping expect as an argument a function takes a stream element (movie object) and returns a Stream (of actor's names).

And then a downstream collector of the flatMapping you need to apply groupingBy again to create a nested map that will group movies by actor. Since only a number of movies is needed, counting() has to be used as a downstream collector to reduce movie objects mapped to each actor to a number of these objects.

With that will be created an intermediate map of type Map<String, Map<String, Long>> that represent the total count of movies by actor for each year.

The next step is to create a stream over the entry set and flatten the nested map (count of movies by actor) by wrapping every entry in the map with a new composed entry of type Map.Entry<String, Map.Entry<String, Long>> that will contain a year, actor's name and a count.

And the last step is to find the entry with the maximum count.

For that operationmax() has to applied on the stream. It expects a Comparator and returns a result wrapped by an Optional. I assume that resume is expected to be present, therefore the code below is a fail-fast implementation that will raise a NuSuchElementExeption in case if the stream source is empty (no result will be found).

Comparator is defined by using the static method comparingLong(). It will produce a comparator based on the count of movies.

public static void main(String[] args) {
List<Movie> movies =
List.of(new Movie("The Avengers", "2011", List.of("Tom","Chris","Robert")),
new Movie("Sherlock Holmes", "2011", List.of("Robert","Harris","Murphy")),
new Movie("Spiderman", "2002", List.of("Tobey","William","Kirsten")));

Map.Entry<String, Map.Entry<String, Long>> result =
movies.stream()
.collect(Collectors.groupingBy(Movie::getYear,
Collectors.flatMapping(movie -> movie.getActorList().stream(),
Collectors.groupingBy(Function.identity(),
Collectors.counting()))))
.entrySet().stream()
.flatMap(entryYear -> entryYear.getValue().entrySet().stream()
.map(entryActor -> Map.entry(entryYear.getKey(),
Map.entry(entryActor.getKey(),
entryActor.getValue()))))
.max(Comparator.comparingLong(entry -> entry.getValue().getValue()))
.orElseThrow(); // NoSuchElementExeption will be thrown is the result wasn't found (if stream is empty)

System.out.println(result);
}

Output

2011=Robert=2

Stream doesn't preserve the order after grouping

Keep in mind, Collectors#groupingBy(Function) will return a HashMap, which does not guarantee order. If you want the ordering to be in the order that the aggregate identity (your i % 2 == 0 result) shows up, then you can use LinkedHashMap:

.collect(Collectors.groupingBy(i -> i % 2 == 0, LinkedHashMap::new, Collectors.toList()))

Would return a LinkedHashMap<Boolean, List<SeatedTicketAssignment>> (since your collector is grouped by a boolean). Additionally, since the list utilized by the collector is an ArrayList, it should preserve the iteration order of the stream relative to the list.

How do I efficiently iterate over each entry in a Java Map?

Map<String, String> map = ...
for (Map.Entry<String, String> entry : map.entrySet()) {
System.out.println(entry.getKey() + "/" + entry.getValue());
}

On Java 10+:

for (var entry : map.entrySet()) {
System.out.println(entry.getKey() + "/" + entry.getValue());
}


Related Topics



Leave a reply



Submit