ch.openchvote.utilities.sequence.IntVector Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of utilities Show documentation
Show all versions of utilities Show documentation
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 extends W> 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);
}
}