name:
src/main/java/io/protobase/f7/models/Grid.java
-rw-r--r--
11870
1package io.protobase.f7.models;
2
3import com.google.common.base.MoreObjects;
4
5import java.util.HashMap;
6import java.util.Iterator;
7import java.util.Map;
8
9/**
10 * A grid is the fundamental 2D data structure of F7. It should NOT be nested. Values are either filled with an
11 * object, or left null, although the meaning of an intentionally stored null value depends on where this structure is
12 * used.
13 *
14 * @param <V> - type stored.
15 */
16public class Grid<V> extends BaseObject implements Iterable<V> {
17 /**
18 * Store each value by a column-row key (basically x and y dimensions).
19 */
20 private Map<ColumnRowKey, V> grid = new HashMap<>();
21 /**
22 * A grid can have a column and row size even if there are no values. But there are many places where we need to know
23 * how big the grid is, regardless of how full it is. This is the number of columns in this grid.
24 */
25 private int columnSize;
26 /**
27 * This is the number of rows in this grid.
28 */
29 private int rowSize;
30
31 /**
32 * Create a new grid of a specific size.
33 *
34 * @param columnSize - greater than 0.
35 * @param rowSize - greater that 0.
36 */
37 public Grid(int columnSize, int rowSize) {
38 if (columnSize < 0 || rowSize < 0) {
39 throw new IndexOutOfBoundsException();
40 }
41 this.columnSize = columnSize;
42 this.rowSize = rowSize;
43 }
44
45 /**
46 * Construct a grid of 0x0 dimensions.
47 */
48 public Grid() {
49 this.columnSize = 0;
50 this.rowSize = 0;
51 }
52
53 /**
54 * Create a grid builder.
55 *
56 * @return grid builder.
57 */
58 public static Builder<Object> builder() {
59 return new Builder<>();
60 }
61
62 /**
63 * Set a value by column and row.
64 *
65 * @param column - column index.
66 * @param row - row index.
67 * @param value - value to set.
68 */
69 public void set(int column, int row, V value) {
70 bumpIndices(column, row);
71 grid.put(new ColumnRowKey(column, row), value);
72 }
73
74 /**
75 * Set a value by key.
76 *
77 * @param key - index.
78 * @param value - to store
79 */
80 public void set(ColumnRowKey key, V value) {
81 bumpIndices(key.getColumnIndex(), key.getRowIndex());
82 grid.put(key, value);
83 }
84
85 /**
86 * Get a value at a given row or column.
87 *
88 * @param column - index.
89 * @param row - index.
90 * @return value at that index or null.
91 */
92 public V get(int column, int row) {
93 return grid.get(new ColumnRowKey(column, row));
94 }
95
96 /**
97 * Get a value by key.
98 *
99 * @param key - index.
100 * @return value if found, null if not.
101 */
102 public V get(ColumnRowKey key) {
103 return grid.get(key);
104 }
105
106 /**
107 * Remove a value at a given row and column.
108 *
109 * @param column - index.
110 * @param row - index.
111 */
112 public void remove(int column, int row) {
113 grid.remove(new ColumnRowKey(column, row));
114 }
115
116 /**
117 * Remove a value by index.
118 *
119 * @param key - index.
120 */
121 public void remove(ColumnRowKey key) {
122 grid.remove(key);
123 }
124
125 /**
126 * Does this grid only have one value?
127 *
128 * @return true if it just has one value.
129 */
130 public boolean isSingle() {
131 return getRowSize() == 1 && getColumnSize() == 1;
132 }
133
134 /**
135 * Set an empty/blank/null value at a row/column.
136 *
137 * @param column - index.
138 * @param row - index.
139 */
140 public void setEmpty(int column, int row) {
141 set(column, row, null);
142 }
143
144 /**
145 * Get column size.
146 *
147 * @return column size.
148 */
149 public int getColumnSize() {
150 return columnSize;
151 }
152
153 /**
154 * Get row size.
155 *
156 * @return row size.
157 */
158 public int getRowSize() {
159 return rowSize;
160 }
161
162 /**
163 * Add one grid to this one in the column-wise dimension (right.)
164 *
165 * @param other grid
166 */
167 public void addGridToRight(Grid<V> other) {
168 int oldGridColumnSize = columnSize;
169 columnSize = columnSize + other.getColumnSize();
170 rowSize = Math.max(rowSize, other.getRowSize());
171 other.reverseIndexIterator().forEachRemaining(key -> {
172 V value = other.get(key.getColumnIndex(), key.getRowIndex());
173 set(key.getColumnIndex() + oldGridColumnSize, key.getRowIndex(), value);
174 });
175 }
176
177 /**
178 * Add a single value in the 0th row, after the last column, increasing the column count.
179 *
180 * @param value - to add.
181 */
182 public void addOneToRight(V value) {
183 columnSize++;
184 set(columnSize - 1, 0, value);
185 }
186
187 /**
188 * Add one grid to this one in the row-wise dimension (bottom.)
189 *
190 * @param other grid
191 */
192 public void addGridToBottom(Grid<V> other) {
193 int oldGridRowSize = getRowSize();
194 rowSize = rowSize + other.getRowSize();
195 other.reverseIndexIterator().forEachRemaining(key -> {
196 V value = other.get(key.getColumnIndex(), key.getRowIndex());
197 set(key.getColumnIndex(), key.getRowIndex() + oldGridRowSize, value);
198 });
199 }
200
201 /**
202 * Ensure indices are within bounds, throwing exception if they're not.
203 *
204 * @param columnIndex - between 0 and size-1.
205 * @param rowIndex - between 0 and size-1.
206 */
207 private void bumpIndices(int columnIndex, int rowIndex) {
208 columnSize = Math.max(columnIndex + 1, columnSize);
209 rowSize = Math.max(rowIndex + 1, rowSize);
210 }
211
212 /**
213 * Is the grid empty?
214 *
215 * @return true if it contains no values.
216 */
217 public boolean isEmpty() {
218 return grid.isEmpty();
219 }
220
221 /**
222 * Is the grid complete?
223 *
224 * @return true if an only if the number of keys is equal to columns * rows.
225 */
226 public boolean isComplete() {
227 return grid.keySet().size() == (columnSize * rowSize);
228 }
229
230 @Override
231 public String toString() {
232 return MoreObjects.toStringHelper(this)
233 .add("columnSize", columnSize)
234 .add("rowSize", rowSize)
235 .add("grid", grid)
236 .toString();
237 }
238
239 @Override
240 public Object[] significantAttributes() {
241 return new Object[]{
242 columnSize,
243 rowSize,
244 grid
245 };
246 }
247
248 /**
249 * Iterate through grid in no guaranteed order.
250 *
251 * @return natural iterator.
252 */
253 public Iterator<V> iterator() {
254 return this.reverseIterator();
255 }
256
257 /**
258 * Column first iterator going column by column, row by row in reverse order. Bottom to top, right to left.
259 * then top to bottom.
260 *
261 * @return reverse iterator.
262 */
263 public Iterator<V> reverseIterator() {
264 return new DataGridIterator<>(this, new DataGridIndexIterator(getColumnSize() - 1, getRowSize() - 1));
265 }
266
267 /**
268 * Column first iterator going column by column, row by row. Eg: column=0,row=0 then column=1,row=0. Left to right,
269 * then top to bottom.
270 *
271 * @return natural iterator.
272 */
273 public Iterator<ColumnRowKey> indexIterator() {
274 return new DataGridIndexIterator(getColumnSize() - 1, getRowSize() - 1);
275 }
276
277 /**
278 * Reverse iterator going column by column, row by row, in reverse order.
279 *
280 * @return natural iterator.
281 */
282 public Iterator<ColumnRowKey> reverseIndexIterator() {
283 return new DataGridReverseIndexIterator(getColumnSize() - 1, getRowSize() - 1);
284 }
285
286 /**
287 * Reverse iterator starting at a high column and row, going to a low column and row.
288 *
289 * @param highColumnIndex - high column index to start at, inclusively.
290 * @param lowColumnIndex - low column index to end at, inclusively.
291 * @param highRowIndex - high row index to start at, inclusively.
292 * @param lowRowIndex - low row index to start at, inclusively.
293 * @return iterator.
294 */
295 public Iterator<ColumnRowKey> reverseIndexIterator(int highColumnIndex, int lowColumnIndex, int highRowIndex,
296 int lowRowIndex) {
297 return new DataGridReverseIndexIterator(highColumnIndex, lowColumnIndex, highRowIndex, lowRowIndex);
298 }
299
300 /**
301 * Iterates from low to high column indices, left to right, top to bottom.
302 */
303 private static class DataGridIndexIterator implements Iterator<ColumnRowKey> {
304 private int lowColumnIndex;
305 private int lowRowIndex;
306 private int highColumnIndex;
307 private int highRowIndex;
308 private int currentColumnIndex;
309 private int currentRowIndex;
310
311 private DataGridIndexIterator(int highColumnIndex, int highRowIndex) {
312 this.highColumnIndex = highColumnIndex;
313 this.highRowIndex = highRowIndex;
314 this.currentColumnIndex = lowColumnIndex - 1;
315 this.currentRowIndex = lowRowIndex;
316 }
317
318 /**
319 * Has next if we've not reached the last row index and last column index.
320 *
321 * @return true if we've not reached the last row index and last column index.
322 */
323 public boolean hasNext() {
324 return (currentColumnIndex < highColumnIndex || currentRowIndex < highRowIndex);
325 }
326
327 /**
328 * Get the index pair.
329 *
330 * @return next index pair.
331 */
332 public ColumnRowKey next() {
333 currentColumnIndex++;
334 if (currentColumnIndex == highColumnIndex + 1) {
335 currentColumnIndex = lowColumnIndex;
336 currentRowIndex++;
337 }
338 return new ColumnRowKey(currentColumnIndex, currentRowIndex);
339 }
340 }
341
342 /**
343 * Iterates from high to low column indices, right to left, bottom to top.
344 */
345 private static class DataGridReverseIndexIterator implements Iterator<ColumnRowKey> {
346 private int lowColumnIndex;
347 private int lowRowIndex;
348 private int highColumnIndex;
349 private int highRowIndex;
350 private int currentColumnIndex;
351 private int currentRowIndex;
352
353 private DataGridReverseIndexIterator(int highColumnIndex, int lowColumnIndex, int highRowIndex, int lowRowIndex) {
354 this.highColumnIndex = highColumnIndex;
355 this.lowColumnIndex = lowColumnIndex;
356 this.highRowIndex = highRowIndex;
357 this.lowRowIndex = lowRowIndex;
358 this.currentColumnIndex = highColumnIndex + 1;
359 this.currentRowIndex = highRowIndex;
360 }
361
362 private DataGridReverseIndexIterator(int highColumnIndex, int highRowIndex) {
363 this.highColumnIndex = highColumnIndex;
364 this.highRowIndex = highRowIndex;
365 this.currentColumnIndex = highColumnIndex + 1;
366 this.currentRowIndex = highRowIndex;
367 }
368
369 /**
370 * Has next if we've not reached the last row index and last column index.
371 *
372 * @return true if we've not reached the last row index and last column index.
373 */
374 public boolean hasNext() {
375 return (currentColumnIndex > lowColumnIndex || currentRowIndex > lowRowIndex);
376 }
377
378 /**
379 * Get the index pair.
380 *
381 * @return next index pair.
382 */
383 public ColumnRowKey next() {
384 currentColumnIndex--;
385 if (currentColumnIndex == lowColumnIndex - 1) {
386 currentColumnIndex = highColumnIndex;
387 currentRowIndex--;
388 }
389 return new ColumnRowKey(currentColumnIndex, currentRowIndex);
390 }
391 }
392
393 /**
394 * Iterator helps us go through values.
395 *
396 * @param <V> type of value stored and iterated through.
397 */
398 private static class DataGridIterator<V> implements Iterator<V> {
399 private Grid<V> grid;
400 private Iterator<ColumnRowKey> internalIterator;
401
402
403 private DataGridIterator(Grid<V> grid, Iterator<ColumnRowKey> internalIterator) {
404 this.grid = grid;
405 this.internalIterator = internalIterator;
406 }
407
408 /**
409 * Has next if we've not reached the last row index and last column index.
410 *
411 * @return true if we've not reached the last row index and last column index.
412 */
413 public boolean hasNext() {
414 return internalIterator.hasNext();
415 }
416
417 /**
418 * Get the next value.
419 *
420 * @return next value.
421 */
422 public V next() {
423 ColumnRowKey key = internalIterator.next();
424 return grid.get(key.getColumnIndex(), key.getRowIndex());
425 }
426 }
427
428 /**
429 * Simple builder class to create grids. Mostly used for testing.
430 *
431 * @param <V>
432 */
433 public static class Builder<V> {
434 private Grid<V> grid = new Grid<>(0, 0);
435
436 public Builder<V> add(int columnIndex, int rowIndex, V value) {
437 grid.set(columnIndex, rowIndex, value);
438 return this;
439 }
440
441 public Grid<V> build() {
442 return grid;
443 }
444 }
445}