Search code examples
javasortingguavacomparator

Guava TreeBasedTable - Sort By Column


I'm trying to use the Guava Table and TreeBaedTable implementation and I'm working on trying to sort the table by column name. Here is what I have so far:

import com.google.common.collect.Ordering;
import com.google.common.collect.Table;
import com.google.common.collect.TreeBasedTable;

import java.util.Arrays;
import java.util.Comparator;
import java.util.List;


public class CustomTable {
    enum SortDirection {
        ASC, DESC,
    }

    enum Column {
        COL_1, COL_2, COL_3;
        public static final Column[] columns = Column.values();
    }

    private final TreeBasedTable<Integer, Column, Integer> table;

    public CustomTable() {
        this.table = TreeBasedTable.create();
    }

    public Integer cell(int ID, Column column) {
        return table.get(ID, column);
    }

    public void addRow(List<Integer> values) {
        Integer rowNum = nextID();
        for (int i = 0; i < values.size(); i++) {
            table.put(rowNum, Column.columns[i], values.get(i));
        }
    }

    public Table<Integer, Column, Integer> sortBy(Column column, SortDirection sortDirection) {
        Comparator<Integer> rowComparator = Comparator.comparing(id -> cell(id, column));
        rowComparator = (sortDirection.equals(SortDirection.ASC)) ? rowComparator : rowComparator.reversed();
        Table<Integer, Column, Integer> table = TreeBasedTable.create(rowComparator, Ordering.natural());
        table.putAll(table);
        return table;
    }

    public String toString() {
        return table.toString();
    }

    public int maxID() {
        return table.rowKeySet().size();
    }

    public int nextID() {
        return maxID() + 1;
    }
}

And example usage:

CustomTable table = new CustomTable();
table.addRow(Arrays.asList(1, 2, 3));
table.addRow(Arrays.asList(6, 7, 8));
table.addRow(Arrays.asList(4, 5, 6));
System.out.println(table.sortBy(Column.COL_2, SortDirection.DESC));

Now, this works as expected when the cells have different values. However, if two cells have the same value, the latter is omitted.

I have tried remedy this with the following comparator:

Comparator<Integer> rowComparator = (id1, id2) -> {
    Integer cell1 = cell(id1, column);
    Integer cell2 = cell(id2, column);
    if (cell1 != cell2)
        return cell1.compareTo(cell2);
    return -1; // So, row id1 appears above the row id2.
};

But this yields some unwanted mutation of the table. Is there something I'm missing?


Solution

  • Using print statements, I noticed that IDs were being checked against themselves i.e., id1 == id2 when two different cells had the same value. The solution was to catch this case and return 0 (when two Integer objects have the same value).

    The comparator used is then:

    Comparator<Integer> valueComparator = (id1, id2) -> {
        if (id1.equals(id2))
            return 0;
        Integer cell1 = cell(id1, column);
        Integer cell2 = cell(id2, column);
        if (cell1.equals(cell2))
            return 1;
        return cell1.compareTo(cell2);
    };
    

    And it works as expected; returning 1 when the two cells share the same value maintains original insertion order.

    As a side note, if there are multiple cells with the same value (e.g., more than 5 duplicate cells), when printing out the table, some data may appear to be lost. However, querying the table shows that all the data is still there, just for some reason unknown to me, the toString method does not output some rows.