Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Any way to stream a map like "(k,v)" instead of working with (entry)?

Tags:

Basically I look for a way to avoid working with

entry -> entry.getValue

and

entry -> entry.getKey

similar to what Map.forEach() does.

If only I could get a way to work as map.stream().filter((k,v) -> )... and so on

It seems the interface is called BiConsumer. Perhaps with a converter to BiConsumer or a Stream.generate() someway

like image 527
Whimusical Avatar asked Mar 24 '15 17:03

Whimusical


People also ask

What is map streaming?

Stream is an interface and T is the type of stream elements. mapper is a stateless function which is applied to each element and the function returns the new stream. Example 1 : Stream map() function with operation of number * 3 on each element of stream. // Java code for Stream map(Function mapper)

Why are streams not applicable to maps?

Streams cannot directly be created from maps because a map is not a collection.

Why do we use map in Stream?

The map() function is a method in the Stream class that represents a functional programming concept. In simple words, the map() is used to transform one object into other by applying a function. That's why the Stream.


2 Answers

Since this is a repeating question, I will throw a full solution into the ring. It’s a PairStream type that is by default a simple wrapper around an ordinary Stream (though, being an interface, alternatives are possible).

It focuses on providing the convenient intermediate operations and those terminal operations which can not be easily performed by calling one of the methods keys(), values() or entries() to return to a conventional single-element Stream and chain a terminal operation. So for example, PairStream.from(map).filterValue(predicate).keys().findAny() is the straight-forward way to get a key for which the mapped value matches the predicate. filterValue is a convenience intermediate operation and keys turn back to an ordinary Stream allowing an arbitrary terminal operation for the keys.

Some examples

    Map<String,Integer> m=new HashMap<>();
    m.put("foo", 5);
    m.put("bar", 7);
    m.put("baz", 42);
    // {b=49, f=5}
    Map<Character,Integer> m2=PairStream.from(m)
      .mapKey(s->s.charAt(0))
      .toMap(Integer::sum);

    // foo bar
    String str=PairStream.from(m)
      .filterValue(i->i<30)
      .keys().sorted(Comparator.reverseOrder())
      .collect(Collectors.joining(" "));

 

    Map<String,Integer> map=new HashMap<>();
    map.put("muhv~", 26);
    map.put("kfool", 3);
    String str = PairStream.from(map)
      .sortedByValue(Comparator.naturalOrder())
      .flatMapToInt((s,i)->s.codePoints().map(c->c^i))
      .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append)
      .toString();

Here is the complete class (I haven’t tested all operations, however most of it is straight-forward):

import java.util.*;
import java.util.function.*;
import java.util.stream.*;

public interface PairStream<K,V> {
    static <K,V> PairStream<K,V> from(Map<K,V> map) {
        return from(map.entrySet().stream());
    }
    static <K,V> PairStream<K,V> from(Stream<Map.Entry<K,V>> s) {
        return ()->s;
    }
    static <K,V> PairStream<K,V> from(Stream<K> s, Function<? super K, ? extends V> f) {
        return ()->s.map(k->new AbstractMap.SimpleImmutableEntry<>(k, f.apply(k)));
    }

    default PairStream<K,V> distinct() {
        return from(entries().distinct());
    }
    default PairStream<K,V> peek(BiConsumer<? super K, ? super V> action) {
        return from(entries().peek(e->action.accept(e.getKey(), e.getValue())));
    }
    default PairStream<K,V> skip(long n) {
        return from(entries().skip(n));
    }
    default PairStream<K,V> limit(long maxSize) {
        return from(entries().limit(maxSize));
    }
    default PairStream<K,V> filterKey(Predicate<? super K> mapper) {
        return from(entries().filter(e->mapper.test(e.getKey())));
    }
    default PairStream<K,V> filterValue(Predicate<? super V> mapper) {
        return from(entries().filter(e->mapper.test(e.getValue())));
    }
    default PairStream<K,V> filter(BiPredicate<? super K, ? super V> mapper) {
        return from(entries().filter(e->mapper.test(e.getKey(), e.getValue())));
    }
    default <R> PairStream<R,V> mapKey(Function<? super K,? extends R> mapper) {
        return from(entries().map(e->new AbstractMap.SimpleImmutableEntry<>(
            mapper.apply(e.getKey()), e.getValue()
        )));
    }
    default <R> PairStream<K,R> mapValue(Function<? super V,? extends R> mapper) {
        return from(entries().map(e->new AbstractMap.SimpleImmutableEntry<>(
            e.getKey(), mapper.apply(e.getValue())
        )));
    }
    default <R> Stream<R> map(BiFunction<? super K, ? super V,? extends R> mapper) {
        return entries().map(e->mapper.apply(e.getKey(), e.getValue()));
    }
    default DoubleStream mapToDouble(ToDoubleBiFunction<? super K, ? super V> mapper) {
        return entries().mapToDouble(e->mapper.applyAsDouble(e.getKey(), e.getValue()));
    }
    default IntStream mapToInt(ToIntBiFunction<? super K, ? super V> mapper) {
        return entries().mapToInt(e->mapper.applyAsInt(e.getKey(), e.getValue()));
    }
    default LongStream mapToLong(ToLongBiFunction<? super K, ? super V> mapper) {
        return entries().mapToLong(e->mapper.applyAsLong(e.getKey(), e.getValue()));
    }
    default <RK,RV> PairStream<RK,RV> flatMap(
            BiFunction<? super K, ? super V,? extends PairStream<RK,RV>> mapper) {
        return from(entries().flatMap(
            e->mapper.apply(e.getKey(), e.getValue()).entries()));
    }
    default <R> Stream<R> flatMapToObj(
            BiFunction<? super K, ? super V,? extends Stream<R>> mapper) {
        return entries().flatMap(e->mapper.apply(e.getKey(), e.getValue()));
    }
    default DoubleStream flatMapToDouble(
            BiFunction<? super K, ? super V,? extends DoubleStream> mapper) {
        return entries().flatMapToDouble(e->mapper.apply(e.getKey(), e.getValue()));
    }
    default IntStream flatMapToInt(
            BiFunction<? super K, ? super V,? extends IntStream> mapper) {
        return entries().flatMapToInt(e->mapper.apply(e.getKey(), e.getValue()));
    }
    default LongStream flatMapToLong(
            BiFunction<? super K, ? super V,? extends LongStream> mapper) {
        return entries().flatMapToLong(e->mapper.apply(e.getKey(), e.getValue()));
    }
    default PairStream<K,V> sortedByKey(Comparator<? super K> comparator) {
        return from(entries().sorted(Map.Entry.comparingByKey(comparator)));
    }
    default PairStream<K,V> sortedByValue(Comparator<? super V> comparator) {
        return from(entries().sorted(Map.Entry.comparingByValue(comparator)));
    }

    default boolean allMatch(BiPredicate<? super K,? super V> predicate) {
        return entries().allMatch(e->predicate.test(e.getKey(), e.getValue()));
    }
    default boolean anyMatch(BiPredicate<? super K,? super V> predicate) {
        return entries().anyMatch(e->predicate.test(e.getKey(), e.getValue()));
    }
    default boolean noneMatch(BiPredicate<? super K,? super V> predicate) {
        return entries().noneMatch(e->predicate.test(e.getKey(), e.getValue()));
    }
    default long count() {
        return entries().count();
    }

    Stream<Map.Entry<K,V>> entries();
    default Stream<K> keys() {
        return entries().map(Map.Entry::getKey);
    }
    default Stream<V> values() {
        return entries().map(Map.Entry::getValue);
    }
    default Optional<Map.Entry<K,V>> maxByKey(Comparator<? super K> comparator) {
        return entries().max(Map.Entry.comparingByKey(comparator));
    }
    default Optional<Map.Entry<K,V>> maxByValue(Comparator<? super V> comparator) {
        return entries().max(Map.Entry.comparingByValue(comparator));
    }
    default Optional<Map.Entry<K,V>> minByKey(Comparator<? super K> comparator) {
        return entries().min(Map.Entry.comparingByKey(comparator));
    }
    default Optional<Map.Entry<K,V>> minByValue(Comparator<? super V> comparator) {
        return entries().min(Map.Entry.comparingByValue(comparator));
    }
    default void forEach(BiConsumer<? super K, ? super V> action) {
        entries().forEach(e->action.accept(e.getKey(), e.getValue()));
    }
    default void forEachOrdered(BiConsumer<? super K, ? super V> action) {
        entries().forEachOrdered(e->action.accept(e.getKey(), e.getValue()));
    }

    default Map<K,V> toMap() {
        return entries().collect(
            Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
    }
    default Map<K,V> toMap(BinaryOperator<V> valAccum) {
        return entries().collect(
            Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, valAccum));
    }
}
like image 110
Holger Avatar answered Oct 21 '22 16:10

Holger


It's a bit unfortunate that the main way to process a map using streams is to stream the map's entries. That means you have to extracting the entries' keys and values. This is somewhat verbose, but it isn't really all that bad. Still, it might be reasonable to consider some kind of helper methods that will adapt a BiPredicate or BiFunction so that they can be used in the filter or map stages of a stream of map entries. They look like this:

static <K,V> Predicate<Map.Entry<K,V>> p(BiPredicate<? super K, ? super V> bip) {
    return entry -> bip.test(entry.getKey(), entry.getValue());
}

static <K,V,R> Function<Map.Entry<K,V>,R> m(BiFunction<? super K, ? super V, R> bif) {
    return entry -> bif.apply(entry.getKey(), entry.getValue());
}

Once you have these, you can use them to simplify(?) a map entry stream. Let's say you have a Map<String,Integer> and you want to select the entries where the string key's length is greater than the integer value, and then format the key and value into a string. Conventionally you'd do this:

    map.entrySet().stream()
                  .filter(e -> e.getKey().length() > e.getValue())
                  .map(e -> e.getKey() + ":" + e.getValue())
                  .forEach(System.out::println);

With the helper functions above, you could rewrite this pipeline as:

    map.entrySet().stream()
                  .filter(p((k, v) -> k.length() > v))
                  .map(m((k, v) -> k + ":" + v))
                  .forEach(System.out::println);

OK, you save a few characters. Is it worth it?

like image 43
Stuart Marks Avatar answered Oct 21 '22 17:10

Stuart Marks