Home > Software design >  Java 8 stream API to convert List to TreeMap
Java 8 stream API to convert List to TreeMap

Time:10-31

Hi i am trying to convert the following code using java 8 stream API.

private Point3d findClosestNodeToParentStartNode1(List<Point3d> points,Point3d parentStartVertex)
    {
        
        TreeMap<Double, Point3d> distanceMap = new TreeMap<Double, Point3d>();
        for (Point3d point : points) {
            distanceMap.put(parentStartVertex.distanceTo(point), point);
        }
        return distanceMap.firstEntry().getValue();
    }

I am trying to do something like

Map<Double, Point3d> result =  points.stream().collect(Collectors.toMap(parentStartVertex.distanceTo(point->point) , point -> point));
TreeMap<Double, Point3d> distanceMap = new TreeMap<>(result);
return distanceMap.firstEntry().getValue();

CodePudding user response:

Your code won't even compile as the key function is wrong. You also have an intermediate step which isn't needed, you can directly use the toMap function which takes 4 arguments and return a TreeMap directly.

Something like this should work

TreeMap <Double, Point3d> distanceMap = points.stream().collect(
Collectors.toMap(
  parentStartVertex::distanceTo,
  Function.identity(),
  (k1, k2) -> k2,
  TreeMap::new));
return distanceMap.firstEntry().getValue();

For what is worth I don't think there is anything wrong with the forEach approach and in this case it might even be easier to read (and might even be faster due to less overhead).

TreeMap<Double, Point3d> distanceMap = new TreeMap<Double, Point3d>();
points.forEach( point -> distanceMap.put(parentStartVertex.distanceTo(point), point));
return distanceMap.firstEntry().getValue();

CodePudding user response:

If you want to find the smallest distance (min) between the target parentStartVertex and the individual points in the list, then you can use Stream#min or Collections#min with a custom Comparator comparing based on the distance between parentStartVertex and the individual items. Pick one of the solutions below:

/**
 * Returns null in case the list `points` is empty
 */
private Point3d findClosestNodeToParentStartNode1(List<Point3d> points, Point3d parentStartVertex) {
        return points.stream()
                .min(Comparator.comparingDouble(parentStartVertex::distanceTo))
                .orElse(null);
}
/**
 * Throws NoSuchElementException in case the list `points` is empty
 */
private Point3d findClosestNodeToParentStartNode1(List<Point3d> points, Point3d parentStartVertex) {
        return Collections.min(
                points,
                Comparator.comparingDouble(parentStartVertex::distanceTo));
}

CodePudding user response:

EDIT: the solution is not correct as explained in the comment, still by reading the implementation of toMap() with two arguments in the source code the solution becomes rather obvious with a few minor adjustments.

The default Map Implementation the function will use is HashMap, you just need to provide the map implementation you need as last argument like:

 Map<Double, Point3d> result =  points.stream().collect(Collectors.toMap(parentStartVertex.distanceTo(point->point) , point -> point, Treemap::new));

If you chek out the source code you'll see that the toMap method you used just calls the overload method beneath it with the defaul HashMap implementation:

    /**
     * Returns a {@code Collector} that accumulates elements into a
     * {@code Map} whose keys and values are the result of applying the provided
     * mapping functions to the input elements.
     *
     * <p>If the mapped keys contains duplicates (according to
     * {@link Object#equals(Object)}), an {@code IllegalStateException} is
     * thrown when the collection operation is performed.  If the mapped keys
     * may have duplicates, use {@link #toMap(Function, Function, BinaryOperator)}
     * instead.
     *
     * @apiNote
     * It is common for either the key or the value to be the input elements.
     * In this case, the utility method
     * {@link java.util.function.Function#identity()} may be helpful.
     * For example, the following produces a {@code Map} mapping
     * students to their grade point average:
     * <pre>{@code
     *     Map<Student, Double> studentToGPA
     *         students.stream().collect(toMap(Functions.identity(),
     *                                         student -> computeGPA(student)));
     * }</pre>
     * And the following produces a {@code Map} mapping a unique identifier to
     * students:
     * <pre>{@code
     *     Map<String, Student> studentIdToStudent
     *         students.stream().collect(toMap(Student::getId,
     *                                         Functions.identity());
     * }</pre>
     *
     * @implNote
     * The returned {@code Collector} is not concurrent.  For parallel stream
     * pipelines, the {@code combiner} function operates by merging the keys
     * from one map into another, which can be an expensive operation.  If it is
     * not required that results are inserted into the {@code Map} in encounter
     * order, using {@link #toConcurrentMap(Function, Function)}
     * may offer better parallel performance.
     *
     * @param <T> the type of the input elements
     * @param <K> the output type of the key mapping function
     * @param <U> the output type of the value mapping function
     * @param keyMapper a mapping function to produce keys
     * @param valueMapper a mapping function to produce values
     * @return a {@code Collector} which collects elements into a {@code Map}
     * whose keys and values are the result of applying mapping functions to
     * the input elements
     *
     * @see #toMap(Function, Function, BinaryOperator)
     * @see #toMap(Function, Function, BinaryOperator, Supplier)
     * @see #toConcurrentMap(Function, Function)
     */
    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);
    }
    /**
     * Returns a {@code Collector} that accumulates elements into a
     * {@code Map} whose keys and values are the result of applying the provided
     * mapping functions to the input elements.
     *
     * <p>If the mapped
     * keys contains duplicates (according to {@link Object#equals(Object)}),
     * the value mapping function is applied to each equal element, and the
     * results are merged using the provided merging function.  The {@code Map}
     * is created by a provided supplier function.
     *
     * @implNote
     * The returned {@code Collector} is not concurrent.  For parallel stream
     * pipelines, the {@code combiner} function operates by merging the keys
     * from one map into another, which can be an expensive operation.  If it is
     * not required that results are merged into the {@code Map} in encounter
     * order, using {@link #toConcurrentMap(Function, Function, BinaryOperator, Supplier)}
     * may offer better parallel performance.
     *
     * @param <T> the type of the input elements
     * @param <K> the output type of the key mapping function
     * @param <U> the output type of the value mapping function
     * @param <M> the type of the resulting {@code Map}
     * @param keyMapper a mapping function to produce keys
     * @param valueMapper a mapping function to produce values
     * @param mergeFunction a merge function, used to resolve collisions between
     *                      values associated with the same key, as supplied
     *                      to {@link Map#merge(Object, Object, BiFunction)}
     * @param mapSupplier a function which returns a new, empty {@code Map} into
     *                    which the results will be inserted
     * @return a {@code Collector} which collects elements into a {@code Map}
     * whose keys are the result of applying a key mapping function to the input
     * elements, and whose values are the result of applying a value mapping
     * function to all input elements equal to the key and combining them
     * using the merge function
     *
     * @see #toMap(Function, Function)
     * @see #toMap(Function, Function, BinaryOperator)
     * @see #toConcurrentMap(Function, Function, BinaryOperator, Supplier)
     */
    public static <T, K, U, M extends Map<K, U>>
    Collector<T, ?, M> toMap(Function<? super T, ? extends K> keyMapper,
                                Function<? super T, ? extends U> valueMapper,
                                BinaryOperator<U> mergeFunction,
                                Supplier<M> mapSupplier) {
        BiConsumer<M, T> accumulator
                = (map, element) -> map.merge(keyMapper.apply(element),
                                              valueMapper.apply(element), mergeFunction);
        return new CollectorImpl<>(mapSupplier, accumulator, mapMerger(mergeFunction), CH_ID);
    }
  • Related