All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.common.collect.RegularImmutableTable Maven / Gradle / Ivy

There is a newer version: 4.1.4
Show newest version
/*
 * Copyright (C) 2009 Google Inc.
 *
 * 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.Function;
import com.google.common.base.Objects;

import java.util.Collections;
import java.util.Comparator;
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 [email protected] (Gregory Kick)
 */
@GwtCompatible
abstract class RegularImmutableTable extends ImmutableTable {
  private final ImmutableSet> cellSet;

  private RegularImmutableTable(ImmutableSet> cellSet) {
    this.cellSet = cellSet;
  }

  private static final Function, Object>
      GET_VALUE_FUNCTION =
          new Function, Object>() {
            @Override public Object apply(Cell from) {
              return from.getValue();
            }
          };

  @SuppressWarnings("unchecked")
  private Function, V> getValueFunction() {
    return (Function) GET_VALUE_FUNCTION;
  }

  @Nullable private transient volatile ImmutableList valueList;

  @Override public final ImmutableCollection values() {
    ImmutableList result = valueList;
    if (result == null) {
      valueList = result = ImmutableList.copyOf(
          Iterables.transform(cellSet(), getValueFunction()));
    }
    return result;
  }

  @Override public final int size() {
    return cellSet().size();
  }

  @Override public final boolean containsValue(@Nullable Object value) {
    return values().contains(value);
  }

  @Override public final boolean isEmpty() {
    return false;
  }

  @Override public final ImmutableSet> cellSet() {
    return cellSet;
  }

  static final  RegularImmutableTable forCells(
      List> cells,
      @Nullable final Comparator rowComparator,
      @Nullable final Comparator 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 rowComparator,
          @Nullable Comparator columnComparator) {
    ImmutableSet.Builder> cellSetBuilder = ImmutableSet.builder();
    ImmutableSet.Builder rowSpaceBuilder = ImmutableSet.builder();
    ImmutableSet.Builder columnSpaceBuilder = ImmutableSet.builder();
    for (Cell cell : cells) {
      cellSetBuilder.add(cell);
      rowSpaceBuilder.add(cell.getRowKey());
      columnSpaceBuilder.add(cell.getColumnKey());
    }
    ImmutableSet> cellSet = cellSetBuilder.build();

    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 (cellSet.size() > ((rowSpace.size() * columnSpace.size()) / 2 )) ?
        new DenseImmutableTable(cellSet, rowSpace, columnSpace) :
        new SparseImmutableTable(cellSet, 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;

    /**
     * Creates a {@link Map} over the key space with
     * {@link ImmutableMap.Builder}s ready for values.
     */
    private static final  Map>
        makeIndexBuilder(ImmutableSet keySpace) {
      Map> indexBuilder = Maps.newLinkedHashMap();
      for (A key : keySpace) {
        indexBuilder.put(key, ImmutableMap.builder());
      }
      return indexBuilder;
    }

    /**
     * Builds the value maps of the index and creates an immutable copy of the
     * map.
     */
    private static final  ImmutableMap> buildIndex(
        Map> indexBuilder) {
      return ImmutableMap.copyOf(Maps.transformValues(indexBuilder,
          new Function, Map>() {
            @Override public Map apply(ImmutableMap.Builder from) {
              return from.build();
            }
          }));
    }

    SparseImmutableTable(ImmutableSet> cellSet,
        ImmutableSet rowSpace, ImmutableSet columnSpace) {
      super(cellSet);
      Map> rowIndexBuilder
          = makeIndexBuilder(rowSpace);
      Map> columnIndexBuilder
          = makeIndexBuilder(columnSpace);
      for (Cell cell : cellSet) {
        R rowKey = cell.getRowKey();
        C columnKey = cell.getColumnKey();
        V value = cell.getValue();
        rowIndexBuilder.get(rowKey).put(columnKey, value);
        columnIndexBuilder.get(columnKey).put(rowKey, value);
      }
      this.rowMap = buildIndex(rowIndexBuilder);
      this.columnMap = buildIndex(columnIndexBuilder);
    }

    @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);
    }
  }

  /**
   * A {@code RegularImmutableTable} optimized for dense data.
   */
  @Immutable @VisibleForTesting
  static final class DenseImmutableTable
      extends RegularImmutableTable {

    private final ImmutableBiMap rowKeyToIndex;
    private final ImmutableBiMap columnKeyToIndex;
    private final V[][] values;

    private static  ImmutableBiMap makeIndex(
        ImmutableSet set) {
      ImmutableBiMap.Builder indexBuilder =
          ImmutableBiMap.builder();
      int i = 0;
      for (E key : set) {
        indexBuilder.put(key, i);
        i++;
      }
      return indexBuilder.build();
    }

    DenseImmutableTable(ImmutableSet> cellSet,
        ImmutableSet rowSpace, ImmutableSet columnSpace) {
      super(cellSet);
      @SuppressWarnings("unchecked")
      V[][] array = (V[][]) new Object[rowSpace.size()][columnSpace.size()];
      this.values = array;
      this.rowKeyToIndex = makeIndex(rowSpace);
      this.columnKeyToIndex = makeIndex(columnSpace);
      for (Cell cell : cellSet) {
        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();
      }
    }

    @Override public ImmutableMap column(C columnKey) {
      checkNotNull(columnKey);
      Integer columnIndexInteger = columnKeyToIndex.get(columnKey);
      if (columnIndexInteger == null) {
        return ImmutableMap.of();
      } else {
        // unbox only once
        int columnIndex = columnIndexInteger;
        ImmutableMap.Builder columnBuilder = ImmutableMap.builder();
        for (int i = 0; i < values.length; i++) {
          V value = values[i][columnIndex];
          if (value != null) {
            columnBuilder.put(rowKeyToIndex.inverse().get(i), value);
          }
        }
        return columnBuilder.build();
      }
    }

    @Override public ImmutableSet columnKeySet() {
      return columnKeyToIndex.keySet();
    }

    private transient volatile ImmutableMap> columnMap;

    private ImmutableMap> makeColumnMap() {
      ImmutableMap.Builder> columnMapBuilder =
          ImmutableMap.builder();
      for (int c = 0; c < columnKeyToIndex.size(); c++) {
        ImmutableMap.Builder rowMapBuilder = ImmutableMap.builder();
        for (int r = 0; r < rowKeyToIndex.size(); r++) {
          V value = values[r][c];
          if (value != null) {
            rowMapBuilder.put(rowKeyToIndex.inverse().get(r), value);
          }
        }
        columnMapBuilder.put(columnKeyToIndex.inverse().get(c),
            rowMapBuilder.build());
      }
      return columnMapBuilder.build();
    }

    @Override public ImmutableMap> columnMap() {
      ImmutableMap> result = columnMap;
      if (result == null) {
        columnMap = result = makeColumnMap();
      }
      return result;
    }

    @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 {
        ImmutableMap.Builder rowBuilder = ImmutableMap.builder();
        V[] row = values[rowIndex];
        for (int r = 0; r < row.length; r++) {
          V value = row[r];
          if (value != null) {
            rowBuilder.put(columnKeyToIndex.inverse().get(r), value);
          }
        }
        return rowBuilder.build();
      }
    }

    @Override public ImmutableSet rowKeySet() {
      return rowKeyToIndex.keySet();
    }

    private transient volatile ImmutableMap> rowMap;

    private ImmutableMap> makeRowMap() {
      ImmutableMap.Builder> rowMapBuilder = ImmutableMap.builder();
      for (int r = 0; r < values.length; r++) {
        V[] row = values[r];
        ImmutableMap.Builder columnMapBuilder = ImmutableMap.builder();
        for (int c = 0; c < row.length; c++) {
          V value = row[c];
          if (value != null) {
            columnMapBuilder.put(columnKeyToIndex.inverse().get(c), value);
          }
        }
        rowMapBuilder.put(rowKeyToIndex.inverse().get(r),
            columnMapBuilder.build());
      }
      return rowMapBuilder.build();
    }

    @Override public ImmutableMap> rowMap() {
      ImmutableMap> result = rowMap;
      if (result == null) {
        rowMap = result = makeRowMap();
      }
      return result;
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy