MultidimensionalCounter.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */

  17. package org.apache.commons.numbers.arrays;

  18. import java.util.Arrays;

  19. /**
  20.  * Converter between unidimensional storage structure and multidimensional
  21.  * conceptual structure.
  22.  * This utility will convert from indices in a multidimensional structure
  23.  * to the corresponding index in a one-dimensional array. For example,
  24.  * assuming that the ranges (in 3 dimensions) of indices are 2, 4 and 3,
  25.  * the following correspondences, between 3-tuples indices and unidimensional
  26.  * indices, will hold:
  27.  * <ul>
  28.  *  <li>(0, 0, 0) corresponds to 0</li>
  29.  *  <li>(0, 0, 1) corresponds to 1</li>
  30.  *  <li>(0, 0, 2) corresponds to 2</li>
  31.  *  <li>(0, 1, 0) corresponds to 3</li>
  32.  *  <li>...</li>
  33.  *  <li>(1, 0, 0) corresponds to 12</li>
  34.  *  <li>...</li>
  35.  *  <li>(1, 3, 2) corresponds to 23</li>
  36.  * </ul>
  37.  */
  38. public final class MultidimensionalCounter {
  39.     /**
  40.      * Number of dimensions.
  41.      */
  42.     private final int dimension;
  43.     /**
  44.      * Offset for each dimension.
  45.      */
  46.     private final int[] uniCounterOffset;
  47.     /**
  48.      * Counter sizes.
  49.      */
  50.     private final int[] size;
  51.     /**
  52.      * Total number of (one-dimensional) slots.
  53.      */
  54.     private final int totalSize;
  55.     /**
  56.      * Index of last dimension.
  57.      */
  58.     private final int last;

  59.     /**
  60.      * Creates a counter.
  61.      *
  62.      * @param size Counter sizes (number of slots in each dimension).
  63.      * @throws IllegalArgumentException if one of the sizes is negative
  64.      * or zero.
  65.      */
  66.     private MultidimensionalCounter(int... size) {
  67.         dimension = size.length;
  68.         this.size = Arrays.copyOf(size, size.length);

  69.         uniCounterOffset = new int[dimension];

  70.         last = dimension - 1;
  71.         uniCounterOffset[last] = 1;

  72.         int tS = 1;
  73.         for (int i = last - 1; i >= 0; i--) {
  74.             final int index = i + 1;
  75.             checkStrictlyPositive("index size", size[index]);
  76.             tS *= size[index];
  77.             checkStrictlyPositive("cumulative size", tS);
  78.             uniCounterOffset[i] = tS;
  79.         }

  80.         totalSize = tS * size[0];
  81.         checkStrictlyPositive("total size", totalSize);
  82.     }

  83.     /**
  84.      * Creates a counter.
  85.      *
  86.      * @param size Counter sizes (number of slots in each dimension).
  87.      * @return a new instance.
  88.      * @throws IllegalArgumentException if one of the sizes is negative
  89.      * or zero.
  90.      */
  91.     public static MultidimensionalCounter of(int... size) {
  92.         return new MultidimensionalCounter(size);
  93.     }

  94.     /**
  95.      * Gets the number of dimensions of the multidimensional counter.
  96.      *
  97.      * @return the number of dimensions.
  98.      */
  99.     public int getDimension() {
  100.         return dimension;
  101.     }

  102.     /**
  103.      * Converts to a multidimensional counter.
  104.      *
  105.      * @param index Index in unidimensional counter.
  106.      * @return the multidimensional counts.
  107.      * @throws IndexOutOfBoundsException if {@code index} is not between
  108.      * {@code 0} and the value returned by {@link #getSize()} (excluded).
  109.      */
  110.     public int[] toMulti(int index) {
  111.         if (index < 0 ||
  112.             index >= totalSize) {
  113.             throw new IndexOutOfBoundsException(createIndexOutOfBoundsMessage(totalSize, index));
  114.         }

  115.         final int[] indices = new int[dimension];

  116.         for (int i = 0; i < last; i++) {
  117.             indices[i] = index / uniCounterOffset[i];
  118.             // index = index % uniCounterOffset[i]
  119.             index = index - indices[i] * uniCounterOffset[i];
  120.         }

  121.         indices[last] = index;

  122.         return indices;
  123.     }

  124.     /**
  125.      * Converts to a unidimensional counter.
  126.      *
  127.      * @param c Indices in multidimensional counter.
  128.      * @return the index within the unidimensionl counter.
  129.      * @throws IllegalArgumentException if the size of {@code c}
  130.      * does not match the size of the array given in the constructor.
  131.      * @throws IndexOutOfBoundsException if a value of {@code c} is not in
  132.      * the range of the corresponding dimension, as defined in the
  133.      * {@link MultidimensionalCounter#of(int...) constructor}.
  134.      */
  135.     public int toUni(int... c) {
  136.         if (c.length != dimension) {
  137.             throw new IllegalArgumentException("Wrong number of arguments: " + c.length +
  138.                                                "(expected: " + dimension + ")");
  139.         }
  140.         int count = 0;
  141.         for (int i = 0; i < dimension; i++) {
  142.             final int index = c[i];
  143.             if (index < 0 ||
  144.                 index >= size[i]) {
  145.                 throw new IndexOutOfBoundsException(createIndexOutOfBoundsMessage(size[i], index));
  146.             }
  147.             count += uniCounterOffset[i] * index;
  148.         }
  149.         return count;
  150.     }

  151.     /**
  152.      * Gets the total number of elements.
  153.      *
  154.      * @return the total size of the unidimensional counter.
  155.      */
  156.     public int getSize() {
  157.         return totalSize;
  158.     }

  159.     /**
  160.      * Gets the number of multidimensional counter slots in each dimension.
  161.      *
  162.      * @return the number of slots in each dimension.
  163.      */
  164.     public int[] getSizes() {
  165.         return Arrays.copyOf(size, size.length);
  166.     }

  167.     /** {@inheritDoc} */
  168.     @Override
  169.     public String toString() {
  170.         return Arrays.toString(size);
  171.     }

  172.     /**
  173.      * Check the size is strictly positive: {@code size > 0}.
  174.      *
  175.      * @param name the name of the size
  176.      * @param size the size
  177.      */
  178.     private static void checkStrictlyPositive(String name, int size) {
  179.         if (size <= 0) {
  180.             throw new IllegalArgumentException("Not positive " + name + ": " + size);
  181.         }
  182.     }

  183.     /**
  184.      * Creates the message for the index out of bounds exception.
  185.      *
  186.      * @param size the size
  187.      * @param index the index
  188.      * @return the message
  189.      */
  190.     private static String createIndexOutOfBoundsMessage(int size, int index) {
  191.         return "Index out of bounds [0, " + (size - 1) + "]: " + index;
  192.     }
  193. }