MultidimensionalCounter.java
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.commons.numbers.arrays;
import java.util.Arrays;
/**
* Converter between unidimensional storage structure and multidimensional
* conceptual structure.
* This utility will convert from indices in a multidimensional structure
* to the corresponding index in a one-dimensional array. For example,
* assuming that the ranges (in 3 dimensions) of indices are 2, 4 and 3,
* the following correspondences, between 3-tuples indices and unidimensional
* indices, will hold:
* <ul>
* <li>(0, 0, 0) corresponds to 0</li>
* <li>(0, 0, 1) corresponds to 1</li>
* <li>(0, 0, 2) corresponds to 2</li>
* <li>(0, 1, 0) corresponds to 3</li>
* <li>...</li>
* <li>(1, 0, 0) corresponds to 12</li>
* <li>...</li>
* <li>(1, 3, 2) corresponds to 23</li>
* </ul>
*/
public final class MultidimensionalCounter {
/**
* Number of dimensions.
*/
private final int dimension;
/**
* Offset for each dimension.
*/
private final int[] uniCounterOffset;
/**
* Counter sizes.
*/
private final int[] size;
/**
* Total number of (one-dimensional) slots.
*/
private final int totalSize;
/**
* Index of last dimension.
*/
private final int last;
/**
* Creates a counter.
*
* @param size Counter sizes (number of slots in each dimension).
* @throws IllegalArgumentException if one of the sizes is negative
* or zero.
*/
private MultidimensionalCounter(int... size) {
dimension = size.length;
this.size = Arrays.copyOf(size, size.length);
uniCounterOffset = new int[dimension];
last = dimension - 1;
uniCounterOffset[last] = 1;
int tS = 1;
for (int i = last - 1; i >= 0; i--) {
final int index = i + 1;
checkStrictlyPositive("index size", size[index]);
tS *= size[index];
checkStrictlyPositive("cumulative size", tS);
uniCounterOffset[i] = tS;
}
totalSize = tS * size[0];
checkStrictlyPositive("total size", totalSize);
}
/**
* Creates a counter.
*
* @param size Counter sizes (number of slots in each dimension).
* @return a new instance.
* @throws IllegalArgumentException if one of the sizes is negative
* or zero.
*/
public static MultidimensionalCounter of(int... size) {
return new MultidimensionalCounter(size);
}
/**
* Gets the number of dimensions of the multidimensional counter.
*
* @return the number of dimensions.
*/
public int getDimension() {
return dimension;
}
/**
* Converts to a multidimensional counter.
*
* @param index Index in unidimensional counter.
* @return the multidimensional counts.
* @throws IndexOutOfBoundsException if {@code index} is not between
* {@code 0} and the value returned by {@link #getSize()} (excluded).
*/
public int[] toMulti(int index) {
if (index < 0 ||
index >= totalSize) {
throw new IndexOutOfBoundsException(createIndexOutOfBoundsMessage(totalSize, index));
}
final int[] indices = new int[dimension];
for (int i = 0; i < last; i++) {
indices[i] = index / uniCounterOffset[i];
// index = index % uniCounterOffset[i]
index = index - indices[i] * uniCounterOffset[i];
}
indices[last] = index;
return indices;
}
/**
* Converts to a unidimensional counter.
*
* @param c Indices in multidimensional counter.
* @return the index within the unidimensionl counter.
* @throws IllegalArgumentException if the size of {@code c}
* does not match the size of the array given in the constructor.
* @throws IndexOutOfBoundsException if a value of {@code c} is not in
* the range of the corresponding dimension, as defined in the
* {@link MultidimensionalCounter#of(int...) constructor}.
*/
public int toUni(int... c) {
if (c.length != dimension) {
throw new IllegalArgumentException("Wrong number of arguments: " + c.length +
"(expected: " + dimension + ")");
}
int count = 0;
for (int i = 0; i < dimension; i++) {
final int index = c[i];
if (index < 0 ||
index >= size[i]) {
throw new IndexOutOfBoundsException(createIndexOutOfBoundsMessage(size[i], index));
}
count += uniCounterOffset[i] * index;
}
return count;
}
/**
* Gets the total number of elements.
*
* @return the total size of the unidimensional counter.
*/
public int getSize() {
return totalSize;
}
/**
* Gets the number of multidimensional counter slots in each dimension.
*
* @return the number of slots in each dimension.
*/
public int[] getSizes() {
return Arrays.copyOf(size, size.length);
}
/** {@inheritDoc} */
@Override
public String toString() {
return Arrays.toString(size);
}
/**
* Check the size is strictly positive: {@code size > 0}.
*
* @param name the name of the size
* @param size the size
*/
private static void checkStrictlyPositive(String name, int size) {
if (size <= 0) {
throw new IllegalArgumentException("Not positive " + name + ": " + size);
}
}
/**
* Creates the message for the index out of bounds exception.
*
* @param size the size
* @param index the index
* @return the message
*/
private static String createIndexOutOfBoundsMessage(int size, int index) {
return "Index out of bounds [0, " + (size - 1) + "]: " + index;
}
}