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.math3.util;
19 
20 import java.util.NoSuchElementException;
21 import org.apache.commons.math3.exception.DimensionMismatchException;
22 import org.apache.commons.math3.exception.NotStrictlyPositiveException;
23 import org.apache.commons.math3.exception.OutOfRangeException;
24 
25 /**
26  * Converter between unidimensional storage structure and multidimensional
27  * conceptual structure.
28  * This utility will convert from indices in a multidimensional structure
29  * to the corresponding index in a one-dimensional array. For example,
30  * assuming that the ranges (in 3 dimensions) of indices are 2, 4 and 3,
31  * the following correspondences, between 3-tuples indices and unidimensional
32  * indices, will hold:
33  * <ul>
34  *  <li>(0, 0, 0) corresponds to 0</li>
35  *  <li>(0, 0, 1) corresponds to 1</li>
36  *  <li>(0, 0, 2) corresponds to 2</li>
37  *  <li>(0, 1, 0) corresponds to 3</li>
38  *  <li>...</li>
39  *  <li>(1, 0, 0) corresponds to 12</li>
40  *  <li>...</li>
41  *  <li>(1, 3, 2) corresponds to 23</li>
42  * </ul>
43  *
44  * @since 2.2
45  */
46 public class MultidimensionalCounter implements Iterable<Integer> {
47     /**
48      * Number of dimensions.
49      */
50     private final int dimension;
51     /**
52      * Offset for each dimension.
53      */
54     private final int[] uniCounterOffset;
55     /**
56      * Counter sizes.
57      */
58     private final int[] size;
59     /**
60      * Total number of (one-dimensional) slots.
61      */
62     private final int totalSize;
63     /**
64      * Index of last dimension.
65      */
66     private final int last;
67 
68     /**
69      * Perform iteration over the multidimensional counter.
70      */
71     public class Iterator implements java.util.Iterator<Integer> {
72         /**
73          * Multidimensional counter.
74          */
75         private final int[] counter = new int[dimension];
76         /**
77          * Unidimensional counter.
78          */
79         private int count = -1;
80         /**
81          * Maximum value for {@link #count}.
82          */
83         private final int maxCount = totalSize - 1;
84 
85         /**
86          * Create an iterator
87          * @see #iterator()
88          */
Iterator()89         Iterator() {
90             counter[last] = -1;
91         }
92 
93         /**
94          * {@inheritDoc}
95          */
hasNext()96         public boolean hasNext() {
97             return count < maxCount;
98         }
99 
100         /**
101          * @return the unidimensional count after the counter has been
102          * incremented by {@code 1}.
103          * @throws NoSuchElementException if {@link #hasNext()} would have
104          * returned {@code false}.
105          */
next()106         public Integer next() {
107             if (!hasNext()) {
108                 throw new NoSuchElementException();
109             }
110 
111             for (int i = last; i >= 0; i--) {
112                 if (counter[i] == size[i] - 1) {
113                     counter[i] = 0;
114                 } else {
115                     ++counter[i];
116                     break;
117                 }
118             }
119 
120             return ++count;
121         }
122 
123         /**
124          * Get the current unidimensional counter slot.
125          *
126          * @return the index within the unidimensionl counter.
127          */
getCount()128         public int getCount() {
129             return count;
130         }
131         /**
132          * Get the current multidimensional counter slots.
133          *
134          * @return the indices within the multidimensional counter.
135          */
getCounts()136         public int[] getCounts() {
137             return MathArrays.copyOf(counter);
138         }
139 
140         /**
141          * Get the current count in the selected dimension.
142          *
143          * @param dim Dimension index.
144          * @return the count at the corresponding index for the current state
145          * of the iterator.
146          * @throws IndexOutOfBoundsException if {@code index} is not in the
147          * correct interval (as defined by the length of the argument in the
148          * {@link MultidimensionalCounter#MultidimensionalCounter(int[])
149          * constructor of the enclosing class}).
150          */
getCount(int dim)151         public int getCount(int dim) {
152             return counter[dim];
153         }
154 
155         /**
156          * @throws UnsupportedOperationException
157          */
remove()158         public void remove() {
159             throw new UnsupportedOperationException();
160         }
161     }
162 
163     /**
164      * Create a counter.
165      *
166      * @param size Counter sizes (number of slots in each dimension).
167      * @throws NotStrictlyPositiveException if one of the sizes is
168      * negative or zero.
169      */
MultidimensionalCounter(int ... size)170     public MultidimensionalCounter(int ... size) throws NotStrictlyPositiveException {
171         dimension = size.length;
172         this.size = MathArrays.copyOf(size);
173 
174         uniCounterOffset = new int[dimension];
175 
176         last = dimension - 1;
177         int tS = size[last];
178         for (int i = 0; i < last; i++) {
179             int count = 1;
180             for (int j = i + 1; j < dimension; j++) {
181                 count *= size[j];
182             }
183             uniCounterOffset[i] = count;
184             tS *= size[i];
185         }
186         uniCounterOffset[last] = 0;
187 
188         if (tS <= 0) {
189             throw new NotStrictlyPositiveException(tS);
190         }
191 
192         totalSize = tS;
193     }
194 
195     /**
196      * Create an iterator over this counter.
197      *
198      * @return the iterator.
199      */
iterator()200     public Iterator iterator() {
201         return new Iterator();
202     }
203 
204     /**
205      * Get the number of dimensions of the multidimensional counter.
206      *
207      * @return the number of dimensions.
208      */
getDimension()209     public int getDimension() {
210         return dimension;
211     }
212 
213     /**
214      * Convert to multidimensional counter.
215      *
216      * @param index Index in unidimensional counter.
217      * @return the multidimensional counts.
218      * @throws OutOfRangeException if {@code index} is not between
219      * {@code 0} and the value returned by {@link #getSize()} (excluded).
220      */
getCounts(int index)221     public int[] getCounts(int index) throws OutOfRangeException {
222         if (index < 0 ||
223             index >= totalSize) {
224             throw new OutOfRangeException(index, 0, totalSize);
225         }
226 
227         final int[] indices = new int[dimension];
228 
229         int count = 0;
230         for (int i = 0; i < last; i++) {
231             int idx = 0;
232             final int offset = uniCounterOffset[i];
233             while (count <= index) {
234                 count += offset;
235                 ++idx;
236             }
237             --idx;
238             count -= offset;
239             indices[i] = idx;
240         }
241 
242         indices[last] = index - count;
243 
244         return indices;
245     }
246 
247     /**
248      * Convert to unidimensional counter.
249      *
250      * @param c Indices in multidimensional counter.
251      * @return the index within the unidimensionl counter.
252      * @throws DimensionMismatchException if the size of {@code c}
253      * does not match the size of the array given in the constructor.
254      * @throws OutOfRangeException if a value of {@code c} is not in
255      * the range of the corresponding dimension, as defined in the
256      * {@link MultidimensionalCounter#MultidimensionalCounter(int...) constructor}.
257      */
getCount(int ... c)258     public int getCount(int ... c)
259         throws OutOfRangeException, DimensionMismatchException {
260         if (c.length != dimension) {
261             throw new DimensionMismatchException(c.length, dimension);
262         }
263         int count = 0;
264         for (int i = 0; i < dimension; i++) {
265             final int index = c[i];
266             if (index < 0 ||
267                 index >= size[i]) {
268                 throw new OutOfRangeException(index, 0, size[i] - 1);
269             }
270             count += uniCounterOffset[i] * c[i];
271         }
272         return count + c[last];
273     }
274 
275     /**
276      * Get the total number of elements.
277      *
278      * @return the total size of the unidimensional counter.
279      */
getSize()280     public int getSize() {
281         return totalSize;
282     }
283     /**
284      * Get the number of multidimensional counter slots in each dimension.
285      *
286      * @return the sizes of the multidimensional counter in each dimension.
287      */
getSizes()288     public int[] getSizes() {
289         return MathArrays.copyOf(size);
290     }
291 
292     /**
293      * {@inheritDoc}
294      */
295     @Override
toString()296     public String toString() {
297         final StringBuilder sb = new StringBuilder();
298         for (int i = 0; i < dimension; i++) {
299             sb.append("[").append(getCount(i)).append("]");
300         }
301         return sb.toString();
302     }
303 }
304