f7
f7 is a spreadsheet formula execution library
git clone https://git.vogt.world/f7.git
Log | Files | README.md | LICENSE.md
← All files
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}