WeightSelection.java

/*
 * Copyright (C) 2016 Alberto Irurueta Carro (alberto@irurueta.com)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.irurueta.numerical.robust;

import com.irurueta.sorting.Sorter;
import com.irurueta.sorting.SortingException;

import java.util.Arrays;

/**
 * Class containing the selection that was made on a weighted algorithm.
 * This is used internally by weighted estimators.
 */
public class WeightSelection {

    /**
     * Array indicating which correspondences have been selected (i.e. have
     * a true value), and which ones hasn't (have a false value).
     */
    private boolean[] selected;

    /**
     * Number of correspondences that have been selected.
     */
    private int numSelected;

    /**
     * Constructor.
     */
    private WeightSelection() {
    }

    /**
     * Returns array indicating which correspondences have been selected
     * (i.e. have a true value), and which ones hasn't (have a false value).
     *
     * @return array indicating which correspondences have been selected.
     */
    public boolean[] getSelected() {
        return selected;
    }

    /**
     * Sets array indicating which correspondences have been selected (i.e.
     * have a true value), and which ones hasn't (have a false value).
     *
     * @param selected array indicating which correspondences have been
     *                 selected.
     */
    public void setSelected(final boolean[] selected) {
        this.selected = selected;
    }

    /**
     * Returns number of correspondences that have been selected.
     *
     * @return number of correspondences that have been selected.
     */
    public int getNumSelected() {
        return numSelected;
    }

    /**
     * Sets number of correspondences that have been selected.
     *
     * @param numSelected number of correspondences that have been selected.
     */
    public void setNumSelected(final int numSelected) {
        this.numSelected = numSelected;
    }

    /**
     * Selects correspondences based on provided weights and creates a
     * weight selection instance.
     *
     * @param weights     weights. The larger its value the more important a
     *                    correspondence is.
     * @param sortWeights indicates whether weights must be sorted so that
     *                    largest weights are taken into account first.
     * @param maxPoints   maximum number of correspondences to pick
     * @return instance containing the selection that was made.
     * @throws SortingException if weights couldn't be sorted.
     */
    public static WeightSelection selectWeights(final double[] weights, final boolean sortWeights, final int maxPoints)
            throws SortingException {

        final var length = weights.length;

        // instantiate selected array with all its values as unselected
        var selected = new boolean[length];
        int numSelected;

        if (sortWeights) {
            // sort weights

            // copy weights because this array will be sorted
            final var weightsCopy = Arrays.copyOf(weights, length);
            final var sorter = Sorter.<Double>create();
            // array that will contain original indices in ascending order of
            // weights after sorting
            final var indices = sorter.sortWithIndices(weightsCopy);

            // traverse indices array from the greatest position which corresponds
            // to the greatest weight value after sorting in decreasing order
            // up to maxPoints positions
            var counter = 0;
            for (var i = length - 1; i >= 0; i--) {
                selected[indices[i]] = true;
                counter++;
                if (counter >= maxPoints) {
                    break;
                }
            }
            numSelected = counter;
        } else {
            // weights aren't sorted
            if (length < maxPoints) {
                // we select all points
                Arrays.fill(selected, true);
                numSelected = length;
            } else {
                // weights aren't sorted so we pick the first maxPoints
                for (var i = 0; i < maxPoints; i++) {
                    selected[i] = true;
                }
                numSelected = maxPoints;
            }
        }

        final var selection = new WeightSelection();
        selection.setSelected(selected);
        selection.setNumSelected(numSelected);
        return selection;
    }
}