1

Is there any way to fetch the minimum of a Stream (or List, Set, ArrayList) of DoubleProperty and write it into a DoubleProperty variable?

In particular, I have an ArrayList of JavaFX Rectangles and I want to write the minimum of xProperty() of these Rectangles into a DoubleProperty variable. I tried the following code

import javafx.beans.property.DoubleProperty;
import javafx.scene.shape.Rectangle;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.stream.Stream;
public class Test4 {

    public static void main(String[] args) {

        Random rnd = new Random();

        List<Rectangle> rectangles = new ArrayList<>();

        for (int i = 0; i < 50; i++) {
            rectangles.add(new Rectangle(rnd.nextDouble(), rnd.nextDouble(), 100, 100));
        }

        Optional<DoubleProperty> minX = rectangles.stream().map(Rectangle::xProperty).min();

    }

}

However, the line Stream<DoubleProperty> minX = rectangles.stream().map(Rectangle::xProperty).min(); raises the following error:

'min(java.util.Comparator? super javafx.beans.property.DoubleProperty>)' in 'java.util.stream.Stream' cannot be applied to '()'

Is there anyway to tackle this problem?

Keen Teen
  • 25
  • 4

1 Answers1

3

The specific error you get is because Stream.min() requires a parameter specifying how to order the elements of the stream. In this case, you want to order the DoubleProperty by its wrapped double value, so you can do

Optional<DoubleProperty> minX = rectangles.stream()
    .map(Rectangle::xProperty)
    .min(Comparator.comparing(Double::getValue));

(get a stream of the x properties, and then choose the one with the minimum value)

Equivalently:

Optional<DoubleProperty> minX = rectangles.stream()
    .min(Comparator.comparing(Rectangle::getX))
    .map(Rectangle::xProperty);

(choose the rectangle with the minimum x, and then get its x property).

Note, however, that these probably don't do what you want. The minimum x value will be found at the time that code is executed, and you get the DoubleProperty that wraps that value. If another rectangle with a smaller x value is added to the list, you still have a reference to the original DoubleProperty; similarly if the x-value of the rectangle corresponding to the property you received is changed so it is no longer the minimum, then your property will no longer represent the minimum value.

I'm assuming that what you want is a value which can be observed (you can register listeners with it) and which updates if either the list changes or if the x property of any rectangles in the list change.

You can create a binding that does this. You need to create an observable list with an extractor so that the binding can be invalidated when either the list changes, or the xProperty() of any of the rectangles in the list change.

Then use Bindings.createDoubleBinding() with a function that computes the minimum value and binds to the list:

public static void main(String[] args) {

    Random rnd = new Random();

    // create list with an extractor:
    ObservableList<Rectangle> rectangles =
            FXCollections.observableArrayList(rect -> new Observable[] { rect.xProperty()});


    DoubleBinding minX = Bindings.createDoubleBinding(
            () -> rectangles.stream()
                    .mapToDouble(Rectangle::getX)
                    .min()
                    .orElse(Double.POSITIVE_INFINITY),
            rectangles
    );

    minX.addListener((obs, oldMin, newMin) -> System.out.println("Min X changed from "+oldMin+" to "+newMin));
    
    for (int i = 0; i < 50; i++) {
        rectangles.add(new Rectangle(rnd.nextDouble(), rnd.nextDouble(), 100, 100));
    }
}

The DoubleBinding will probably work for anything you need (like a DoubleProperty it implements ObservableDoubleValue, which provides almost all the API you are likely to need). It you specifically need a property, just do

DoubleProperty minXProperty = new SimpleDoubleProperty();
minXProperty.bind(minX);
James_D
  • 201,275
  • 16
  • 291
  • 322
  • Congratulations on 200K and this very nice answer. – jewelsea Jun 20 '23 at 11:30
  • 1
    @jewelsea Thanks, though I am only at 200K with rounding. A few more days... – James_D Jun 20 '23 at 11:40
  • Thanks for the answer. It works when I change the `xProperty()` of rectangles through e.g. `rect.setX(100);`, but it does not work when I want to indirectly change a rectangle's `xProperty()`, e.g. through `rect.setOnMouseClicked(event->{rect.setX(100);});`. – Keen Teen Jun 20 '23 at 18:27
  • @KeenTeen That doesn't make any sense in isolation. There is no difference at all between what you call "direct" and "indirect"; you are calling the same method in both cases. Something else is probably wrong in the code you are using. – James_D Jun 20 '23 at 20:15
  • I see it. Thank you! – Keen Teen Jun 21 '23 at 06:10