All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ch.openchvote.utilities.sequence.IntVector Maven / Gradle / Ivy

Go to download

This module provides a collection of utility classes, especially for various mathematical concepts.

The newest version!
/*
 * Copyright (C) 2024 Berner Fachhochschule https://e-voting.bfh.ch
 *
 *  - This program is free software: you can redistribute it and/or modify                           -
 *  - it under the terms of the GNU Affero General Public License as published by                    -
 *  - the Free Software Foundation, either version 3 of the License, or                              -
 *  - (at your option) any later version.                                                            -
 *  -                                                                                                -
 *  - This program is distributed in the hope that it will be useful,                                -
 *  - but WITHOUT ANY WARRANTY; without even the implied warranty of                                 -
 *  - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the                                   -
 *  - GNU General Public License for more details.                                                   -
 *  -                                                                                                -
 *  - You should have received a copy of the GNU Affero General Public License                       -
 *  - along with this program. If not, see .                           -
 */
package ch.openchvote.utilities.sequence;

import ch.openchvote.utilities.UtilityException;
import ch.openchvote.utilities.sequence.internal.MutableIntVector;
import ch.openchvote.utilities.set.IntSet;
import ch.openchvote.utilities.tools.Hashable;
import ch.openchvote.utilities.tools.IntBiPredicate;

import java.util.Arrays;
import java.util.function.IntFunction;
import java.util.function.IntUnaryOperator;
import java.util.stream.Collectors;

import static ch.openchvote.utilities.UtilityException.Type.*;

/**
 * Objects of this class represent immutable vectors of {@code int} values of a fixed length {@code n}. Their elements
 * are indexed from {@code minIndex} (usually {@code 1}) to {@code maxIndex} (usually {@code n}). Since this class is
 * abstract, it does not offer public constructors. New instances of this class are created using the static method
 * {@link IntVector#of(int...)} or the static member class {@link Builder}. During the building process, values can be
 * added to or placed into an initially empty vector containing zeros. After the building process, accessing the values
 * in the resulting vector is restricted to read-only. The class implements the {@link IntSequence} and
 * {@link Comparable} interfaces.
 */
public abstract sealed class IntVector extends Hashable implements IntSequence, Comparable permits MutableIntVector, IntVector.NonSealed {

    // the purpose of this protected non-sealed inner class is to allow anonymous subclasses exclusively within this package
    static protected non-sealed abstract class NonSealed extends IntVector {

        protected NonSealed(int minIndex, int maxIndex) {
            super(minIndex, maxIndex);
        }

    }

    // the minimal/maximal indices
    protected final int minIndex;
    protected final int maxIndex;

    // protected constructor for internal use in subclasses
    protected IntVector(int minIndex, int maxIndex) {
        this.minIndex = minIndex;
        this.maxIndex = maxIndex;
    }

    /**
     * Returns the minimal index of this array.
     *
     * @return The minimal index
     */
    public int getMinIndex() {
        return this.minIndex;
    }

    /**
     * Returns the maximal index of this array.
     *
     * @return The maximal index
     */
    public int getMaxIndex() {
        return this.maxIndex;
    }

    /**
     * Creates a new vector of type {@code W} by applying a function to each {@code int} value of the vector. By
     * returning a wrapper object which performs the mapping lazily, this method runs ins constant time.
     *
     * @param function The function that maps the values of the vector
     * @param       The type of the returned vector
     * @return A vector containing all mapped values
     */
    public  Vector mapToObj(IntFunction function) {
        return new Vector.NonSealed<>(this.minIndex, this.maxIndex) {
            @Override
            public W getValue(int index) {
                return function.apply(IntVector.this.getValue(index));
            }
        };
    }

    /**
     * Creates a new {@code IntVector} by applying a function to each {@code int} value of the vector. By returning a
     * wrapper object which performs the mapping lazily, this method runs ins constant time.
     *
     * @param function The function that maps the values of the vector
     * @return A vector containing all mapped values
     */
    public IntVector map(IntUnaryOperator function) {
        return new IntVector.NonSealed(this.minIndex, this.maxIndex) {
            @Override
            public int getValue(int index) {
                return function.applyAsInt(IntVector.this.getValue(index));
            }
        };
    }

    /**
     * Creates a new vector by selecting the values specified by the given {@code indexSet}. By taking the given indices
     * in ascending order, the order of the values in the returned vector corresponds to the order in the original
     * vector. The length of the returned vector is equal to the size of {@code indexSet}, and indexing starts at 1.
     *
     * @param indexSet The indices of the values to be selected
     * @return A new vector containing the selected values
     */
    public IntVector select(IntSet indexSet) {
        if (indexSet == null) {
            throw new UtilityException(NULL_POINTER, IntVector.class);
        }
        var indexArray = this.getIndices().toStream().filter(indexSet::contains).toArray();
        return new IntVector.NonSealed(1, indexArray.length) {
            @Override
            public int getValue(int index) {
                return IntVector.this.getValue(indexArray[index - 1]);
            }
        };
    }

    /**
     * Creates a new int vector by selecting the values specified by the given {@code indexVector}. The length of the
     * resulting vector is therefore equal to the length of the index vector, and indexing starts at 1.
     *
     * @param indexVector The given index vector
     * @return A new vector containing the selected values
     */
    public IntVector expand(IntVector indexVector) {
        if (indexVector == null)
            throw new UtilityException(NULL_POINTER, IntVector.class);
        return indexVector.map(this::getValue);
    }

    /**
     * Computes the vector product (dot product) {@code this•other} of two int vectors of equal length. The returned
     * value is the sum of the products of all pairs of values. An exception is thrown if the vectors are not equally
     * long.
     *
     * @param other The other int vector
     * @return The vector product of the two vectors
     */
    public int multiply(IntVector other) {
        return ch.openchvote.utilities.tools.Math.intSumProd(this, other);
    }

    /**
     * Computes the Hadamard product {@code this ○ other} of two int vectors of equal length. The resulting vector is
     * obtained through element-wise multiplication. An exception is thrown if the vectors are not equally long.
     *
     * @param other The other int vector
     * @return The vector product of the two vectors
     */
    public IntVector times(IntVector other) {
        if (other == null || this.minIndex != other.minIndex || this.maxIndex != other.maxIndex)
            throw new UtilityException(INDEX_OUT_OF_BOUNDS, IntVector.class, this, other);
        return new IntVector.NonSealed(this.minIndex, this.maxIndex) {
            @Override
            public int getValue(int index) {
                return IntVector.this.getValue(index) * other.getValue(index);
            }
        };
    }

    /**
     * Returns a new integer vector containing the same values sorted in ascending order.
     *
     * @return The sorted vector
     */
    public IntVector sort() {
        var builder = new IntVector.Builder(this.minIndex, this.maxIndex);
        this.toStream().sorted().forEach(builder::add);
        return builder.build();
    }

    @Override
    public int compareTo(IntVector other) {
        int c;
        // compare length
        c = this.getLength() - other.getLength();
        if (c != 0) return c;

        // compare minIndex
        c = this.minIndex - other.minIndex;
        if (c != 0) return c;

        // compare values
        for (int i : this.getIndices()) {
            c = this.getValue(i) - other.getValue(i);
            if (c != 0) return c;
        }
        // equality
        return 0;
    }

    @Override
    public boolean equals(Object object) {
        if (this == object) return true;
        if (object instanceof IntVector other) {
            if (this.minIndex != other.minIndex) return false;
            if (this.maxIndex != other.maxIndex) return false;
            return this.getIndices().toStream().allMatch(index -> this.getValue(index) == other.getValue(index));
        }
        return false;
    }

    @Override
    public int hashCode() {
        int initValue = 0;
        initValue = 31 * initValue + this.minIndex;
        initValue = 31 * initValue + this.maxIndex;
        return this.toStream().reduce(initValue, (result, hash) -> 31 * result + hash);
    }

    @Override
    public String toString() {
        return this.toStream().mapToObj(Integer::toString).collect(Collectors.joining(",", "[", "]"));
    }

    /**
     * Checks if applying the given predicate pairwise to the elements of two equally long int vectors returns
     * {@code true} for all pairs. An exception is thrown if the vectors are not of the same length.
     *
     * @param intVector1 The first int vector
     * @param intVector2 The second int vector
     * @param predicate  The given predicate
     * @return {@code true}, if the predicate returns {@code true} for all pairs, {@code false} otherwise
     */
    static public boolean allMatch(IntVector intVector1, IntVector intVector2, IntBiPredicate predicate) {
        if (intVector1.getMinIndex() != intVector2.getMinIndex() || intVector2.getMaxIndex() != intVector2.getMaxIndex()) {
            throw new UtilityException(INVALID_PARAMETERS, IntSequence.class, intVector1, intVector2);
        }
        return intVector1.getIndices().toStream().allMatch(index -> predicate.test(intVector1.getValue(index), intVector2.getValue(index)));
    }

    /**
     * Checks if applying the given predicate pairwise to the elements of two equally long int vectors returns
     * {@code true} for at least one pair. An exception is thrown if the vectors are not of the same length.
     *
     * @param intVector1 The first int vector
     * @param intVector2 The second int vector
     * @param predicate  The given predicate
     * @return {@code true}, if the predicate returns {@code true} for at least one pair, {@code false} otherwise
     */
    static public boolean anyMatch(IntVector intVector1, IntVector intVector2, IntBiPredicate predicate) {
        if (intVector1.getMinIndex() != intVector2.getMinIndex() || intVector2.getMaxIndex() != intVector2.getMaxIndex()) {
            throw new UtilityException(INVALID_PARAMETERS, IntSequence.class, intVector1, intVector2);
        }
        return intVector1.getIndices().toStream().anyMatch(index -> predicate.test(intVector1.getValue(index), intVector2.getValue(index)));
    }

    /**
     * This static builder class is the main tool for constructing vectors from scratch. If the length of the vector to
     * construct is defined at the beginning of the building process, then the values are initially all set to
     * {@code null}. If the length is not specified, then the vector grows when new values are added. In both cases,
     * values can be added either incrementally or in arbitrary order. At the end of the building process, the vector
     * can be built exactly once. The builder class is threadsafe and the resulting vector is immutable.
     */
    static public class Builder implements ch.openchvote.utilities.tools.IntBuilder {

        // flag that determines whether the vector has already been built or not
        private boolean built;
        // flag that determines whether the vector length is fixed or not during building process
        private final boolean growable;
        // the first and the last valid index
        private int minIndex;
        private int maxIndex;
        // an array for storing the added values during the building process
        private int[] values;
        // an internal index counter for adding values incrementally
        private int indexCounter;

        /**
         * Constructs a vector builder for a vector with undetermined length. The length of the vector grows
         * automatically to the necessary length when values are added. Indexing starts at 1.
         */
        public Builder() {
            this.built = false;
            this.growable = true;
            this.minIndex = 1;
            this.maxIndex = 0;
            this.values = new int[0];
            this.indexCounter = 0;
        }

        /**
         * Constructs a vector builder for a vector of fixed length {@code length}. Indexing starts from 1 and goes up
         * to {@code length}.
         *
         * @param length The length of the vector to construct
         */
        public Builder(int length) {
            this(1, length);
        }

        /**
         * Constructs a vector builder for a vector of fixed length {@code maxIndex-minIndex+1}. Indexing starts from
         * {@code minIndex} and goes up {@code maxIndex}.
         *
         * @param minIndex The minimal index
         * @param maxIndex The maximal index
         */
        public Builder(int minIndex, int maxIndex) {
            if (minIndex < 0 || minIndex > maxIndex + 1) {
                throw new UtilityException(INVALID_PARAMETERS, IntVector.Builder.class, "Invalid minimal or maximal indices");
            }
            this.built = false;
            this.growable = false;
            this.minIndex = minIndex;
            this.maxIndex = maxIndex;
            this.values = new int[this.maxIndex - this.minIndex + 1];
            this.indexCounter = 0;
        }

        /**
         * Fills up the integer vector with a single value. In case of a vector with undetermined length, the current
         * length remains unchanged.
         *
         * @param value The value used for filling up the vector
         * @return The vector builder itself
         */
        public IntVector.Builder fill(int value) {
            synchronized (this) {
                if (this.built) {
                    throw new UtilityException(ALREADY_BUILT, IntVector.Builder.class);
                }
                for (int i : IntSet.range(this.minIndex, this.maxIndex)) {
                    this.set(i, value);
                }
                return this;
            }
        }

        /**
         * Sets the given value at the given index. If the vector length is undetermined, increases the length if
         * necessary. The vector builder object itself is returned to allow pipeline notation when multiple values are
         * added to the vector.
         *
         * @param index The index of the value to be added
         * @param value The value to be added
         * @return The vector builder itself
         */
        public IntVector.Builder set(int index, int value) {
            synchronized (this) {
                if (this.built) {
                    throw new UtilityException(ALREADY_BUILT, IntVector.Builder.class);
                }
                if (!this.growable && (index < this.minIndex || index > this.maxIndex)) {
                    throw new UtilityException(INDEX_OUT_OF_BOUNDS, IntVector.Builder.class, "Index smaller than minIndex or larger than maxIndex");
                }
                if (this.growable) {
                    this.minIndex = Math.min(this.minIndex, index);
                    this.maxIndex = Math.max(this.maxIndex, index);
                }
                int length = this.maxIndex - this.minIndex + 1;
                int arrayLength = this.values.length;
                while (length > arrayLength) {
                    arrayLength = 2 * arrayLength + 1;
                }
                if (arrayLength > this.values.length) {
                    this.values = Arrays.copyOf(this.values, arrayLength);
                }
                this.values[index - this.minIndex] = value;
                return this;
            }
        }

        /**
         * Sets the next value in the vector and increases the internal index counter by 1. If the vector length is
         * undetermined, increases the length if necessary. The vector builder object itself is returned to allow
         * pipeline notation when multiple values are added to the vector.
         *
         * @param value The value to be added
         * @return The vector builder itself
         */
        @Override
        public IntVector.Builder add(int value) {
            synchronized (this) {
                int currentIndex;
                currentIndex = this.minIndex + this.indexCounter;
                this.indexCounter = this.indexCounter + 1;
                return this.set(currentIndex, value);
            }
        }

        @Override
        public IntVector build() {
            synchronized (this) {
                if (this.built) {
                    throw new UtilityException(ALREADY_BUILT, IntVector.Builder.class);
                }
                this.built = true;
                return new MutableIntVector(this.values, this.minIndex, this.maxIndex);
            }
        }

    }

    /**
     * This static method allows constructing new vectors from a given vararg array of integers. The indexing in the
     * newly created vector starts from 1. The given vararg array of integers is copied into a new {@code int[]} to
     * ensure immutability of the returned vector.
     *
     * @param ints The given (array of) integers
     * @return The {@code IntVector} constructed from the given integers
     */
    static public IntVector of(int... ints) {
        if (ints == null) {
            throw new UtilityException(NULL_POINTER, IntVector.class);
        }
        var copiedArray = Arrays.copyOf(ints, ints.length);
        return new MutableIntVector(copiedArray);
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy