View Javadoc
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  
18  package org.apache.commons.numbers.arrays;
19  
20  import java.util.Arrays;
21  
22  /**
23   * Converter between unidimensional storage structure and multidimensional
24   * conceptual structure.
25   * This utility will convert from indices in a multidimensional structure
26   * to the corresponding index in a one-dimensional array. For example,
27   * assuming that the ranges (in 3 dimensions) of indices are 2, 4 and 3,
28   * the following correspondences, between 3-tuples indices and unidimensional
29   * indices, will hold:
30   * <ul>
31   *  <li>(0, 0, 0) corresponds to 0</li>
32   *  <li>(0, 0, 1) corresponds to 1</li>
33   *  <li>(0, 0, 2) corresponds to 2</li>
34   *  <li>(0, 1, 0) corresponds to 3</li>
35   *  <li>...</li>
36   *  <li>(1, 0, 0) corresponds to 12</li>
37   *  <li>...</li>
38   *  <li>(1, 3, 2) corresponds to 23</li>
39   * </ul>
40   */
41  public final class MultidimensionalCounter {
42      /**
43       * Number of dimensions.
44       */
45      private final int dimension;
46      /**
47       * Offset for each dimension.
48       */
49      private final int[] uniCounterOffset;
50      /**
51       * Counter sizes.
52       */
53      private final int[] size;
54      /**
55       * Total number of (one-dimensional) slots.
56       */
57      private final int totalSize;
58      /**
59       * Index of last dimension.
60       */
61      private final int last;
62  
63      /**
64       * Creates a counter.
65       *
66       * @param size Counter sizes (number of slots in each dimension).
67       * @throws IllegalArgumentException if one of the sizes is negative
68       * or zero.
69       */
70      private MultidimensionalCounter(int... size) {
71          dimension = size.length;
72          this.size = Arrays.copyOf(size, size.length);
73  
74          uniCounterOffset = new int[dimension];
75  
76          last = dimension - 1;
77          uniCounterOffset[last] = 1;
78  
79          int tS = 1;
80          for (int i = last - 1; i >= 0; i--) {
81              final int index = i + 1;
82              checkStrictlyPositive("index size", size[index]);
83              tS *= size[index];
84              checkStrictlyPositive("cumulative size", tS);
85              uniCounterOffset[i] = tS;
86          }
87  
88          totalSize = tS * size[0];
89          checkStrictlyPositive("total size", totalSize);
90      }
91  
92      /**
93       * Creates a counter.
94       *
95       * @param size Counter sizes (number of slots in each dimension).
96       * @return a new instance.
97       * @throws IllegalArgumentException if one of the sizes is negative
98       * or zero.
99       */
100     public static MultidimensionalCounter of(int... size) {
101         return new MultidimensionalCounter(size);
102     }
103 
104     /**
105      * Gets the number of dimensions of the multidimensional counter.
106      *
107      * @return the number of dimensions.
108      */
109     public int getDimension() {
110         return dimension;
111     }
112 
113     /**
114      * Converts to a multidimensional counter.
115      *
116      * @param index Index in unidimensional counter.
117      * @return the multidimensional counts.
118      * @throws IndexOutOfBoundsException if {@code index} is not between
119      * {@code 0} and the value returned by {@link #getSize()} (excluded).
120      */
121     public int[] toMulti(int index) {
122         if (index < 0 ||
123             index >= totalSize) {
124             throw new IndexOutOfBoundsException(createIndexOutOfBoundsMessage(totalSize, index));
125         }
126 
127         final int[] indices = new int[dimension];
128 
129         int pos = index;
130         for (int i = 0; i < last; i++) {
131             indices[i] = pos / uniCounterOffset[i];
132             // pos = pos % uniCounterOffset[i]
133             pos = pos - indices[i] * uniCounterOffset[i];
134         }
135 
136         indices[last] = pos;
137 
138         return indices;
139     }
140 
141     /**
142      * Converts to a unidimensional counter.
143      *
144      * @param c Indices in multidimensional counter.
145      * @return the index within the unidimensionl counter.
146      * @throws IllegalArgumentException if the size of {@code c}
147      * does not match the size of the array given in the constructor.
148      * @throws IndexOutOfBoundsException if a value of {@code c} is not in
149      * the range of the corresponding dimension, as defined in the
150      * {@link MultidimensionalCounter#of(int...) constructor}.
151      */
152     public int toUni(int... c) {
153         if (c.length != dimension) {
154             throw new IllegalArgumentException("Wrong number of arguments: " + c.length +
155                                                "(expected: " + dimension + ")");
156         }
157         int count = 0;
158         for (int i = 0; i < dimension; i++) {
159             final int index = c[i];
160             if (index < 0 ||
161                 index >= size[i]) {
162                 throw new IndexOutOfBoundsException(createIndexOutOfBoundsMessage(size[i], index));
163             }
164             count += uniCounterOffset[i] * index;
165         }
166         return count;
167     }
168 
169     /**
170      * Gets the total number of elements.
171      *
172      * @return the total size of the unidimensional counter.
173      */
174     public int getSize() {
175         return totalSize;
176     }
177 
178     /**
179      * Gets the number of multidimensional counter slots in each dimension.
180      *
181      * @return the number of slots in each dimension.
182      */
183     public int[] getSizes() {
184         return Arrays.copyOf(size, size.length);
185     }
186 
187     /** {@inheritDoc} */
188     @Override
189     public String toString() {
190         return Arrays.toString(size);
191     }
192 
193     /**
194      * Check the size is strictly positive: {@code size > 0}.
195      *
196      * @param name the name of the size
197      * @param size the size
198      */
199     private static void checkStrictlyPositive(String name, int size) {
200         if (size <= 0) {
201             throw new IllegalArgumentException("Not positive " + name + ": " + size);
202         }
203     }
204 
205     /**
206      * Creates the message for the index out of bounds exception.
207      *
208      * @param size the size
209      * @param index the index
210      * @return the message
211      */
212     private static String createIndexOutOfBoundsMessage(int size, int index) {
213         return "Index out of bounds [0, " + (size - 1) + "]: " + index;
214     }
215 }