machine learning – A simple clusterness measure of data in one dimension using Java – follow-up 2

(See the previous version here.)

This time, I have encorporated all the suggestions made by Marc. Also, I changed the type of points from double to Number. My newest version follows:

com.github.coderodde.codereview.notepad.ClusternessMeasure

package com.github.coderodde.codereview.notepad;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;

/**
 * This class implements a method for computing clusterness measure (CM). CM
 * asks for a set of points {@code x_1, x_2, ..., x_n}, and it returns a 
 * number within {@code (0, 1)}. The idea behind CM is that it returns 0 when 
 * all the data points are equal and 1 whenever all adjacent points are 
 * equidistant.
 * 
 * @author Rodion "rodde" Efremov
 * @version 1.618 (Jun 11, 2019)
 * @since 1.6 (Feb 21, 2019)
 */
public final class ClusternessMeasure {

    /**
     * Computes the clusterness measure of the input points.
     * @param points the points to process.
     * @return the number within range {@code (0, 1)}, describing how clustered
     * the input points are.
     */
    public static double computeClusternessMeasure(Collection<Number> points) {
        return computeClusternessMeasure(
                new SortedMeasurementPoints(
                        Objects.requireNonNull(points, "points is null")));
    }

    private static double computeClusternessMeasure(
            SortedMeasurementPoints points) {

        if (points.isEmpty()) {
            throw new IllegalArgumentException("points is empty");
        }

        double minimumPoint = points.get(0).doubleValue();
        double maximumPoint = 
                points.get(points.indexOfLastEntry()).doubleValue();

        double range = maximumPoint - minimumPoint;

        if (range == 0.0) {
            // Once here, all data points are equal and so CM must be 1.0.
            return 1.0;
        }

        double expectedDifference = range / points.indexOfLastEntry();
        double sum = 0.0;

        for (int i = 0; i < points.indexOfLastEntry(); i++) {
            double currentDifference = 
                    points.get(i + 1).doubleValue() - 
                    points.get(i).doubleValue();

            sum += Math.min(
                    Math.abs(expectedDifference - currentDifference), 
                             expectedDifference);
        }

        return sum / range;
    }

    private static final class SortedMeasurementPoints
            implements Iterable<Number> {

        private final List<Number> points;

        public SortedMeasurementPoints(Collection<Number> points) {
            List<Number> processedPoints = new ArrayList<>(points.size());

            for (Number point : points) {
                if (point == null) {
                    NullPointerException cause = 
                            new NullPointerException("null points detected");

                    throw new IllegalArgumentException(cause);
                }

                processedPoints.add(point);
            }

            processedPoints.sort(
                    (a, b) -> Double.compare(
                            a.doubleValue(), 
                            b.doubleValue()));    

            this.points = processedPoints;
        }

        public Number get(int index) {
            return points.get(index);
        }

        public int indexOfLastEntry() {
            return points.size() - 1;
        }

        public boolean isEmpty() {
            return points.isEmpty();
        }

        // For the sake of debugging:
        public Iterator<Number> iterator() {
            return points.iterator();
        }
    }    
}

com.github.coderodde.codereview.notepad.ClusternessMeasureTest

package com.github.coderodde.codereview.notepad;

import static com.github.coderodde.codereview.notepad.ClusternessMeasure.computeClusternessMeasure;
import java.util.Arrays;
import static junit.framework.Assert.assertEquals;
import org.junit.Test;

/**
 * This unit test class tests the 
 * {@link com.github.coderodde.codereview.notepad.ClusternessMeasure}.
 * 
 * @author Rodion "rodde" Efremov
 * @version 1.6 (Jun 11, 2021)
 * @since 1.6 (Jun 11, 2021)
 */
public class ClusternessMeasureTest {

    private static final double DELTA = 1e-6;

    @Test(expected = NullPointerException.class)
    public void throwsOnNullPoints() {
        computeClusternessMeasure(null);
    }

    @Test(expected = IllegalArgumentException.class)
    public void throwsOnEmptyPoints() {
        computeClusternessMeasure(Arrays.asList());
    }

    @Test(expected = IllegalArgumentException.class)
    public void throwsOnNullPoint() {
        computeClusternessMeasure(Arrays.asList(1.0, null, 3.0f));
    }

    @Test
    public void computationOnSinglePoint() {
        double measure = computeClusternessMeasure(Arrays.asList(1));
        double expected = 1.0;
        assertEquals(expected, measure, DELTA);
    }

    @Test
    public void computationOnTwoPoints() {
        double measure = computeClusternessMeasure(Arrays.asList(5, 3));
        double expected = 0;
        assertEquals(expected, measure, DELTA);
    }

    @Test
    public void computeOnThreeEquidistantPoints() {
        double measure = computeClusternessMeasure(Arrays.asList(3, 1, 5));
        double expected = 0.0;
        assertEquals(expected, measure, DELTA);
    }

    @Test
    public void computeOnEqualPoints() {
        double measure = computeClusternessMeasure(
                Arrays.asList(4, 4f, 4.0, 4));

        double expected = 1.0;
        assertEquals(expected, measure, DELTA);
    }

    @Test
    public void computeOnSkewedDataPoints() {
        double measure = computeClusternessMeasure(Arrays.asList(1, 2.8, 3));
        double expected = 0.7999999999999;
        assertEquals(expected, measure, DELTA);
    }
}

Critique request

As always, I would be glad to receive any comments.