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