com.google.common.collect.RegularImmutableTable Maven / Gradle / Ivy
/*
* Copyright (C) 2009 The Guava Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.common.collect;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.annotations.GwtCompatible;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Objects;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
/**
* An implementation of {@link ImmutableTable} holding an arbitrary number of
* cells.
*
* @author Gregory Kick
*/
@GwtCompatible
abstract class RegularImmutableTable extends ImmutableTable {
private RegularImmutableTable() {}
private transient ImmutableCollection values;
@Override public final ImmutableCollection values() {
ImmutableCollection result = values;
return (result == null) ? values = createValues() : result;
}
abstract ImmutableCollection createValues();
@Override public abstract int size();
@Override public final boolean containsValue(@Nullable Object value) {
return values().contains(value);
}
private transient ImmutableSet> cellSet;
@Override
public final ImmutableSet> cellSet() {
ImmutableSet> result = cellSet;
return (result == null) ? cellSet = createCellSet() : result;
}
abstract ImmutableSet> createCellSet();
abstract class CellSet extends ImmutableSet> {
@Override
public int size() {
return RegularImmutableTable.this.size();
}
@Override
public boolean contains(@Nullable Object object) {
if (object instanceof Cell) {
Cell, ?, ?> cell = (Cell, ?, ?>) object;
Object value = get(cell.getRowKey(), cell.getColumnKey());
return value != null && value.equals(cell.getValue());
}
return false;
}
@Override
boolean isPartialView() {
return false;
}
}
@Override public final boolean isEmpty() {
return false;
}
static final RegularImmutableTable forCells(
List> cells,
@Nullable final Comparator super R> rowComparator,
@Nullable final Comparator super C> columnComparator) {
checkNotNull(cells);
if (rowComparator != null || columnComparator != null) {
/*
* This sorting logic leads to a cellSet() ordering that may not be
* expected and that isn't documented in the Javadoc. If a row Comparator
* is provided, cellSet() iterates across the columns in the first row,
* the columns in the second row, etc. If a column Comparator is provided
* but a row Comparator isn't, cellSet() iterates across the rows in the
* first column, the rows in the second column, etc.
*/
Comparator> comparator = new Comparator>() {
@Override public int compare(Cell cell1, Cell cell2) {
int rowCompare = (rowComparator == null) ? 0
: rowComparator.compare(cell1.getRowKey(), cell2.getRowKey());
if (rowCompare != 0) {
return rowCompare;
}
return (columnComparator == null) ? 0
: columnComparator.compare(
cell1.getColumnKey(), cell2.getColumnKey());
}
};
Collections.sort(cells, comparator);
}
return forCellsInternal(cells, rowComparator, columnComparator);
}
static final RegularImmutableTable forCells(
Iterable> cells) {
return forCellsInternal(cells, null, null);
}
/**
* A factory that chooses the most space-efficient representation of the
* table.
*/
private static final RegularImmutableTable
forCellsInternal(Iterable> cells,
@Nullable Comparator super R> rowComparator,
@Nullable Comparator super C> columnComparator) {
ImmutableSet.Builder rowSpaceBuilder = ImmutableSet.builder();
ImmutableSet.Builder columnSpaceBuilder = ImmutableSet.builder();
ImmutableList> cellList = ImmutableList.copyOf(cells);
for (Cell cell : cellList) {
rowSpaceBuilder.add(cell.getRowKey());
columnSpaceBuilder.add(cell.getColumnKey());
}
ImmutableSet rowSpace = rowSpaceBuilder.build();
if (rowComparator != null) {
List rowList = Lists.newArrayList(rowSpace);
Collections.sort(rowList, rowComparator);
rowSpace = ImmutableSet.copyOf(rowList);
}
ImmutableSet columnSpace = columnSpaceBuilder.build();
if (columnComparator != null) {
List columnList = Lists.newArrayList(columnSpace);
Collections.sort(columnList, columnComparator);
columnSpace = ImmutableSet.copyOf(columnList);
}
// use a dense table if more than half of the cells have values
// TODO(gak): tune this condition based on empirical evidence
return (cellList.size() > ((rowSpace.size() * columnSpace.size()) / 2)) ?
new DenseImmutableTable(cellList, rowSpace, columnSpace) :
new SparseImmutableTable(cellList, rowSpace, columnSpace);
}
/**
* A {@code RegularImmutableTable} optimized for sparse data.
*/
@Immutable
@VisibleForTesting
static final class SparseImmutableTable
extends RegularImmutableTable {
private final ImmutableMap> rowMap;
private final ImmutableMap> columnMap;
private final int[] iterationOrderRow;
private final int[] iterationOrderColumn;
SparseImmutableTable(ImmutableList> cellList,
ImmutableSet rowSpace, ImmutableSet columnSpace) {
Map rowIndex = Maps.newHashMap();
Map> rows = Maps.newLinkedHashMap();
for (R row : rowSpace) {
rowIndex.put(row, rows.size());
rows.put(row, new LinkedHashMap());
}
Map> columns = Maps.newLinkedHashMap();
for (C col : columnSpace) {
columns.put(col, new LinkedHashMap());
}
int[] iterationOrderRow = new int[cellList.size()];
int[] iterationOrderColumn = new int[cellList.size()];
for (int i = 0; i < cellList.size(); i++) {
Cell cell = cellList.get(i);
R rowKey = cell.getRowKey();
C columnKey = cell.getColumnKey();
V value = cell.getValue();
iterationOrderRow[i] = rowIndex.get(rowKey);
Map thisRow = rows.get(rowKey);
iterationOrderColumn[i] = thisRow.size();
V oldValue = thisRow.put(columnKey, value);
if (oldValue != null) {
throw new IllegalArgumentException("Duplicate value for row=" + rowKey + ", column="
+ columnKey + ": " + value + ", " + oldValue);
}
columns.get(columnKey).put(rowKey, value);
}
this.iterationOrderRow = iterationOrderRow;
this.iterationOrderColumn = iterationOrderColumn;
ImmutableMap.Builder> rowBuilder = ImmutableMap.builder();
for (Map.Entry> row : rows.entrySet()) {
rowBuilder.put(row.getKey(), ImmutableMap.copyOf(row.getValue()));
}
this.rowMap = rowBuilder.build();
ImmutableMap.Builder> columnBuilder = ImmutableMap.builder();
for (Map.Entry> col : columns.entrySet()) {
columnBuilder.put(col.getKey(), ImmutableMap.copyOf(col.getValue()));
}
this.columnMap = columnBuilder.build();
}
@Override public ImmutableMap column(C columnKey) {
checkNotNull(columnKey);
// value maps are guaranteed to be immutable maps
return Objects.firstNonNull((ImmutableMap) columnMap.get(columnKey),
ImmutableMap.of());
}
@Override public ImmutableSet columnKeySet() {
return columnMap.keySet();
}
@Override public ImmutableMap> columnMap() {
return columnMap;
}
@Override public ImmutableMap row(R rowKey) {
checkNotNull(rowKey);
// value maps are guaranteed to be immutable maps
return Objects.firstNonNull((ImmutableMap) rowMap.get(rowKey),
ImmutableMap.of());
}
@Override public ImmutableSet rowKeySet() {
return rowMap.keySet();
}
@Override public ImmutableMap> rowMap() {
return rowMap;
}
@Override public boolean contains(@Nullable Object rowKey,
@Nullable Object columnKey) {
Map row = rowMap.get(rowKey);
return (row != null) && row.containsKey(columnKey);
}
@Override public boolean containsColumn(@Nullable Object columnKey) {
return columnMap.containsKey(columnKey);
}
@Override public boolean containsRow(@Nullable Object rowKey) {
return rowMap.containsKey(rowKey);
}
@Override public V get(@Nullable Object rowKey,
@Nullable Object columnKey) {
Map row = rowMap.get(rowKey);
return (row == null) ? null : row.get(columnKey);
}
@Override
ImmutableCollection createValues() {
return new ImmutableList() {
@Override
public int size() {
return iterationOrderRow.length;
}
@Override
public V get(int index) {
int rowIndex = iterationOrderRow[index];
ImmutableMap row = (ImmutableMap) rowMap.values().asList().get(rowIndex);
int columnIndex = iterationOrderColumn[index];
return row.values().asList().get(columnIndex);
}
@Override
boolean isPartialView() {
return true;
}
};
}
@Override
public int size() {
return iterationOrderRow.length;
}
@Override
ImmutableSet> createCellSet() {
return new SparseCellSet();
}
class SparseCellSet extends CellSet {
@Override
public UnmodifiableIterator> iterator() {
return asList().iterator();
}
@Override
ImmutableList> createAsList() {
return new ImmutableAsList>() {
@Override
public Cell get(int index) {
int rowIndex = iterationOrderRow[index];
Map.Entry> rowEntry = rowMap.entrySet().asList().get(rowIndex);
ImmutableMap row = (ImmutableMap) rowEntry.getValue();
int columnIndex = iterationOrderColumn[index];
Map.Entry colEntry = row.entrySet().asList().get(columnIndex);
return Tables.immutableCell(rowEntry.getKey(), colEntry.getKey(), colEntry.getValue());
}
@Override
ImmutableCollection> delegateCollection() {
return SparseCellSet.this;
}
};
}
}
}
/**
* An immutable map implementation backed by an indexed nullable array, used in
* DenseImmutableTable.
*/
private abstract static class ImmutableArrayMap extends ImmutableMap {
private final int size;
ImmutableArrayMap(int size) {
this.size = size;
}
abstract ImmutableMap keyToIndex();
// True if getValue never returns null.
private boolean isFull() {
return size == keyToIndex().size();
}
K getKey(int index) {
return keyToIndex().keySet().asList().get(index);
}
@Nullable abstract V getValue(int keyIndex);
@Override
ImmutableSet createKeySet() {
return isFull() ? keyToIndex().keySet() : super.createKeySet();
}
@Override
public int size() {
return size;
}
@Override
public V get(@Nullable Object key) {
Integer keyIndex = keyToIndex().get(key);
return (keyIndex == null) ? null : getValue(keyIndex);
}
@Override
ImmutableSet> createEntrySet() {
if (isFull()) {
return new ImmutableMapEntrySet() {
@Override ImmutableMap map() {
return ImmutableArrayMap.this;
}
@Override
public UnmodifiableIterator> iterator() {
return new AbstractIndexedListIterator>(size()) {
@Override
protected Entry get(int index) {
return Maps.immutableEntry(getKey(index), getValue(index));
}
};
}
};
} else {
return new ImmutableMapEntrySet() {
@Override ImmutableMap map() {
return ImmutableArrayMap.this;
}
@Override
public UnmodifiableIterator> iterator() {
return new AbstractIterator>() {
private int index = -1;
private final int maxIndex = keyToIndex().size();
@Override
protected Entry computeNext() {
for (index++; index < maxIndex; index++) {
V value = getValue(index);
if (value != null) {
return Maps.immutableEntry(getKey(index), value);
}
}
return endOfData();
}
};
}
};
}
}
}
/**
* A {@code RegularImmutableTable} optimized for dense data.
*/
@Immutable @VisibleForTesting
static final class DenseImmutableTable
extends RegularImmutableTable {
private final ImmutableMap rowKeyToIndex;
private final ImmutableMap columnKeyToIndex;
private final ImmutableMap> rowMap;
private final ImmutableMap> columnMap;
private final int[] rowCounts;
private final int[] columnCounts;
private final V[][] values;
private final int[] iterationOrderRow;
private final int[] iterationOrderColumn;
private static ImmutableMap makeIndex(
ImmutableSet set) {
ImmutableMap.Builder indexBuilder =
ImmutableMap.builder();
int i = 0;
for (E key : set) {
indexBuilder.put(key, i);
i++;
}
return indexBuilder.build();
}
DenseImmutableTable(ImmutableList> cellList,
ImmutableSet rowSpace, ImmutableSet columnSpace) {
@SuppressWarnings("unchecked")
V[][] array = (V[][]) new Object[rowSpace.size()][columnSpace.size()];
this.values = array;
this.rowKeyToIndex = makeIndex(rowSpace);
this.columnKeyToIndex = makeIndex(columnSpace);
rowCounts = new int[rowKeyToIndex.size()];
columnCounts = new int[columnKeyToIndex.size()];
int[] iterationOrderRow = new int[cellList.size()];
int[] iterationOrderColumn = new int[cellList.size()];
for (int i = 0; i < cellList.size(); i++) {
Cell cell = cellList.get(i);
R rowKey = cell.getRowKey();
C columnKey = cell.getColumnKey();
int rowIndex = rowKeyToIndex.get(rowKey);
int columnIndex = columnKeyToIndex.get(columnKey);
V existingValue = values[rowIndex][columnIndex];
checkArgument(existingValue == null, "duplicate key: (%s, %s)", rowKey,
columnKey);
values[rowIndex][columnIndex] = cell.getValue();
rowCounts[rowIndex]++;
columnCounts[columnIndex]++;
iterationOrderRow[i] = rowIndex;
iterationOrderColumn[i] = columnIndex;
}
this.iterationOrderRow = iterationOrderRow;
this.iterationOrderColumn = iterationOrderColumn;
this.rowMap = new RowMap();
this.columnMap = new ColumnMap();
}
private final class Row extends ImmutableArrayMap {
private final int rowIndex;
Row(int rowIndex) {
super(rowCounts[rowIndex]);
this.rowIndex = rowIndex;
}
@Override
ImmutableMap keyToIndex() {
return columnKeyToIndex;
}
@Override
V getValue(int keyIndex) {
return values[rowIndex][keyIndex];
}
@Override
boolean isPartialView() {
return true;
}
}
private final class Column extends ImmutableArrayMap {
private final int columnIndex;
Column(int columnIndex) {
super(columnCounts[columnIndex]);
this.columnIndex = columnIndex;
}
@Override
ImmutableMap keyToIndex() {
return rowKeyToIndex;
}
@Override
V getValue(int keyIndex) {
return values[keyIndex][columnIndex];
}
@Override
boolean isPartialView() {
return true;
}
}
private final class RowMap extends ImmutableArrayMap> {
private RowMap() {
super(rowCounts.length);
}
@Override
ImmutableMap keyToIndex() {
return rowKeyToIndex;
}
@Override
Map getValue(int keyIndex) {
return new Row(keyIndex);
}
@Override
boolean isPartialView() {
return false;
}
}
private final class ColumnMap extends ImmutableArrayMap> {
private ColumnMap() {
super(columnCounts.length);
}
@Override
ImmutableMap keyToIndex() {
return columnKeyToIndex;
}
@Override
Map getValue(int keyIndex) {
return new Column(keyIndex);
}
@Override
boolean isPartialView() {
return false;
}
}
@Override public ImmutableMap column(C columnKey) {
Integer columnIndex = columnKeyToIndex.get(checkNotNull(columnKey));
if (columnIndex == null) {
return ImmutableMap.of();
} else {
return new Column(columnIndex);
}
}
@Override public ImmutableSet columnKeySet() {
return columnKeyToIndex.keySet();
}
@Override public ImmutableMap> columnMap() {
return columnMap;
}
@Override public boolean contains(@Nullable Object rowKey,
@Nullable Object columnKey) {
return (get(rowKey, columnKey) != null);
}
@Override public boolean containsColumn(@Nullable Object columnKey) {
return columnKeyToIndex.containsKey(columnKey);
}
@Override public boolean containsRow(@Nullable Object rowKey) {
return rowKeyToIndex.containsKey(rowKey);
}
@Override public V get(@Nullable Object rowKey,
@Nullable Object columnKey) {
Integer rowIndex = rowKeyToIndex.get(rowKey);
Integer columnIndex = columnKeyToIndex.get(columnKey);
return ((rowIndex == null) || (columnIndex == null)) ? null
: values[rowIndex][columnIndex];
}
@Override public ImmutableMap row(R rowKey) {
checkNotNull(rowKey);
Integer rowIndex = rowKeyToIndex.get(rowKey);
if (rowIndex == null) {
return ImmutableMap.of();
} else {
return new Row(rowIndex);
}
}
@Override public ImmutableSet rowKeySet() {
return rowKeyToIndex.keySet();
}
@Override
public ImmutableMap> rowMap() {
return rowMap;
}
@Override
ImmutableCollection createValues() {
return new ImmutableList() {
@Override
public int size() {
return iterationOrderRow.length;
}
@Override
public V get(int index) {
return values[iterationOrderRow[index]][iterationOrderColumn[index]];
}
@Override
boolean isPartialView() {
return true;
}
};
}
@Override
public int size() {
return iterationOrderRow.length;
}
@Override
ImmutableSet> createCellSet() {
return new DenseCellSet();
}
class DenseCellSet extends CellSet {
@Override
public UnmodifiableIterator> iterator() {
return asList().iterator();
}
@Override
ImmutableList> createAsList() {
return new ImmutableAsList>() {
@Override
public Cell get(int index) {
int rowIndex = iterationOrderRow[index];
int columnIndex = iterationOrderColumn[index];
R rowKey = rowKeySet().asList().get(rowIndex);
C columnKey = columnKeySet().asList().get(columnIndex);
V value = values[rowIndex][columnIndex];
return Tables.immutableCell(rowKey, columnKey, value);
}
@Override
ImmutableCollection> delegateCollection() {
return DenseCellSet.this;
}
};
}
}
}
}
| | | | | | | | | | | | | | | | | | | | | | |
© 2015 - 2024 Weber Informatics LLC | Privacy Policy