class StandardTableSliceGroup extends TableSliceGroup {

  /**
   * Constructs a TableSliceGroup made by subdividing the original table by the given columns. A
   * group subdividing on the two columns "Name" and "Place" will have a slice for every combination
   * of name and place in the table
   */
  private StandardTableSliceGroup(Table original, CategoricalColumn<?>... columns) {
    super(original, splitColumnNames(columns));
    setSourceTable(getSourceTable());
    splitOn(getSplitColumnNames());
  }

  private static String[] splitColumnNames(CategoricalColumn<?>... columns) {
    String[] splitColumnNames = new String[columns.length];
    for (int i = 0; i < columns.length; i++) {
      splitColumnNames[i] = columns[i].name();
    }
    return splitColumnNames;
  }

  /**
   * Returns a viewGroup splitting the original table on the given columns. The named columns must
   * be CategoricalColumns
   */
  public static StandardTableSliceGroup create(Table original, String... columnsNames) {
    List<CategoricalColumn<?>> columns = original.categoricalColumns(columnsNames);
    return new StandardTableSliceGroup(original, columns.toArray(new CategoricalColumn<?>[0]));
  }

  /**
   * Returns a viewGroup splitting the original table on the given columns. The named columns must
   * be CategoricalColumns
   */
  public static StandardTableSliceGroup create(Table original, CategoricalColumn<?>... columns) {
    return new StandardTableSliceGroup(original, columns);
  }

  /**
   * Splits the sourceTable table into sub-tables, grouping on the columns whose names are given in
   * splitColumnNames
   */
  private void splitOn(String... splitColumnNames) {<FILL_FUNCTION_BODY>}

  private boolean containsTextColumn(List<Column<?>> splitColumns) {
    return false;
    // return splitColumns.stream().anyMatch(objects -> objects.type().equals(ColumnType.TEXT));
  }

  /** Wrapper class for a byte[] that implements equals and hashcode. */
  private static class ByteArray {
    final byte[] bytes;

    ByteArray(byte[] bytes) {
      this.bytes = bytes;
    }

    @Override
    public boolean equals(Object o) {
      if (this == o) return true;
      if (o == null || getClass() != o.getClass()) return false;
      ByteArray byteArray = (ByteArray) o;
      return Arrays.equals(bytes, byteArray.bytes);
    }

    @Override
    public int hashCode() {
      return Arrays.hashCode(bytes);
    }
  }
}

    Map<ByteArray, Selection> selectionMap = new LinkedHashMap<>();
    Map<ByteArray, String> sliceNameMap = new HashMap<>();
    List<Column<?>> splitColumns = getSourceTable().columns(splitColumnNames);

    if (containsTextColumn(splitColumns)) {
      for (int i = 0; i < getSourceTable().rowCount(); i++) {
        ByteArrayList byteArrayList = new ByteArrayList();
        StringBuilder stringKey = new StringBuilder();
        int count = 0;
        for (Column<?> col : splitColumns) {
          stringKey.append(col.getString(i));
          if (count < splitColumns.size() - 1) {
            stringKey.append(SPLIT_STRING);
          }
          byteArrayList.addElements(byteArrayList.size(), col.asBytes(i));
          count++;
        }
        // Add to the matching selection.
        ByteArray byteArray = new ByteArray(byteArrayList.toByteArray());
        Selection selection = selectionMap.getOrDefault(byteArray, new BitmapBackedSelection());
        selection.add(i);
        selectionMap.put(byteArray, selection);
        sliceNameMap.put(byteArray, stringKey.toString());
      }
    } else { // handle the case where split is on non-text-columns
      int byteSize = getByteSize(splitColumns);
      for (int i = 0; i < getSourceTable().rowCount(); i++) {
        // TODO: instead of splitting on column type, have a function that returns the byte size?
        StringBuilder stringKey = new StringBuilder();
        ByteBuffer byteBuffer = ByteBuffer.allocate(byteSize);
        int count = 0;
        for (Column<?> col : splitColumns) {
          stringKey.append(col.getString(i));
          if (count < splitColumns.size() - 1) {
            stringKey.append(SPLIT_STRING);
          }
          byteBuffer.put(col.asBytes(i));
          count++;
        }
        // Add to the matching selection.
        ByteArray byteArray = new ByteArray(byteBuffer.array());
        Selection selection = selectionMap.getOrDefault(byteArray, new BitmapBackedSelection());
        selection.add(i);
        selectionMap.put(byteArray, selection);
        sliceNameMap.put(byteArray, stringKey.toString());
      }
    }

    // Construct slices for all the values in our maps
    for (Entry<ByteArray, Selection> entry : selectionMap.entrySet()) {
      TableSlice slice = new TableSlice(getSourceTable(), entry.getValue());
      slice.setName(sliceNameMap.get(entry.getKey()));
      addSlice(slice);
    }
  
<methods>public transient tech.tablesaw.api.Table aggregate(java.lang.String, AggregateFunction<?,?>[]) ,public tech.tablesaw.api.Table aggregate(ListMultimap<java.lang.String,AggregateFunction<?,?>>) ,public static java.lang.String aggregateColumnName(java.lang.String, java.lang.String) ,public List<tech.tablesaw.api.Table> asTableList() ,public tech.tablesaw.table.TableSlice get(int) ,public List<tech.tablesaw.table.TableSlice> getSlices() ,public tech.tablesaw.api.Table getSourceTable() ,public Iterator<tech.tablesaw.table.TableSlice> iterator() ,public int size() ,public static tech.tablesaw.api.Table summaryTableName(tech.tablesaw.api.Table) <variables>private static final Splitter SPLITTER,protected static final java.lang.String SPLIT_STRING,private tech.tablesaw.api.Table sourceTable,private final non-sealed java.lang.String[] splitColumnNames,private final List<tech.tablesaw.table.TableSlice> subTables