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 for (int i = 0; i < last; i++) {
130 indices[i] = index / uniCounterOffset[i];
131 // index = index % uniCounterOffset[i]
132 index = index - indices[i] * uniCounterOffset[i];
133 }
134
135 indices[last] = index;
136
137 return indices;
138 }
139
140 /**
141 * Converts to a unidimensional counter.
142 *
143 * @param c Indices in multidimensional counter.
144 * @return the index within the unidimensionl counter.
145 * @throws IllegalArgumentException if the size of {@code c}
146 * does not match the size of the array given in the constructor.
147 * @throws IndexOutOfBoundsException if a value of {@code c} is not in
148 * the range of the corresponding dimension, as defined in the
149 * {@link MultidimensionalCounter#of(int...) constructor}.
150 */
151 public int toUni(int... c) {
152 if (c.length != dimension) {
153 throw new IllegalArgumentException("Wrong number of arguments: " + c.length +
154 "(expected: " + dimension + ")");
155 }
156 int count = 0;
157 for (int i = 0; i < dimension; i++) {
158 final int index = c[i];
159 if (index < 0 ||
160 index >= size[i]) {
161 throw new IndexOutOfBoundsException(createIndexOutOfBoundsMessage(size[i], index));
162 }
163 count += uniCounterOffset[i] * index;
164 }
165 return count;
166 }
167
168 /**
169 * Gets the total number of elements.
170 *
171 * @return the total size of the unidimensional counter.
172 */
173 public int getSize() {
174 return totalSize;
175 }
176
177 /**
178 * Gets the number of multidimensional counter slots in each dimension.
179 *
180 * @return the number of slots in each dimension.
181 */
182 public int[] getSizes() {
183 return Arrays.copyOf(size, size.length);
184 }
185
186 /** {@inheritDoc} */
187 @Override
188 public String toString() {
189 return Arrays.toString(size);
190 }
191
192 /**
193 * Check the size is strictly positive: {@code size > 0}.
194 *
195 * @param name the name of the size
196 * @param size the size
197 */
198 private static void checkStrictlyPositive(String name, int size) {
199 if (size <= 0) {
200 throw new IllegalArgumentException("Not positive " + name + ": " + size);
201 }
202 }
203
204 /**
205 * Creates the message for the index out of bounds exception.
206 *
207 * @param size the size
208 * @param index the index
209 * @return the message
210 */
211 private static String createIndexOutOfBoundsMessage(int size, int index) {
212 return "Index out of bounds [0, " + (size - 1) + "]: " + index;
213 }
214 }