Sorting tables alphabetically

Is there an easy way to sort a table alphabetically from an ImageJ macro?
Table.sort(“column header”) seems to work for numerical columns only.

Hello Martin -

Yes, it looks like sorting alphabetically is not currently
supported. The documentation for ResultsTable.sort()
contains the notation “TO DO: add string support.”

The following modification of the ResultsTable code might
provide a solution for you:

// ResultsTable2.java
package ij.measure;

import java.lang.reflect.Field;

import ij.util.Tools;
import java.util.*;

/** Subclass of ResultsTable with string support for sort() method.
*/
public class ResultsTable2 extends ResultsTable {

    class ComparableEntry implements Comparable<ComparableEntry>  {
	int index;
	double dValue;
	String sValue;
	boolean isStr() {
	    return  Double.isNaN (dValue)  &&  sValue != null  &&  !sValue.equals ("NaN");
	}
	public int compareTo (ComparableEntry e) {
	    if (isStr() && e.isStr())  return sValue.compareTo (e.sValue);
	    if (isStr())  return -1;
	    if (e.isStr())  return +1;
	    return  (dValue < e.dValue) ? -1 : ( (dValue > e.dValue) ? 1 : 0 );
	}
    }

    // use reflection to access private super-class columns and stringColumns
    double[][] getColumns() {
	Field colF = null;
	try {
	    colF = ResultsTable.class.getDeclaredField ("columns");
	}
	catch (NoSuchFieldException e) {
	    throw new RuntimeException (e);
	}
	colF.setAccessible (true);
	double[][] cols = null;
	try {
	    cols = (double[][]) colF.get (this);
	}
	catch (IllegalAccessException e) {
	    throw new RuntimeException (e);
	}
	return cols;
    }
    Map getStringColumns() {
	Field sColF = null;
	try {
	    sColF = ResultsTable.class.getDeclaredField ("stringColumns");
	}
	catch (NoSuchFieldException e) {
	    throw new RuntimeException (e);
	}
	sColF.setAccessible (true);
	Map sCols = null;
	try {
	    sCols = (Map) sColF.get (this);
	}
	catch (IllegalAccessException e) {
	    throw new RuntimeException (e);
	}
	return sCols;
    }
    
    /** Sorts this table on the specified column, with string support.*/
	public void sortNew (String column) {
		int col = getColumnIndex(column);
		if (col==COLUMN_NOT_FOUND)
			throw new IllegalArgumentException("Column not found");

		// pad short string columns with "NaN" to avoid "holes" after sorting
		if (getStringColumns() != null) {
		    for (Object c : getStringColumns().values()) {
			ArrayList sc = (ArrayList) c;
		        for (int i = sc.size(); i < size(); i++)  sc.add ("NaN");
		    }
		}
		
		ComparableEntry[] ces = new ComparableEntry[size()];
		ArrayList stringColumn = null;
		if (getStringColumns() != null)
		    stringColumn = (ArrayList) getStringColumns().get (new Integer (col));
		for (int i = 0; i < size(); i++) {
		    ComparableEntry ce = new ComparableEntry();
		    ce.index = i;
		    ce.dValue = getColumns()[col][i];
		    if (stringColumn != null)
			ce.sValue = (String) stringColumn.get (i);
		    ces[i] = ce;
		}
		Arrays.sort (ces);
		// copy sorted values back into rt from a duplicate
		ResultsTable2 rt2 = (ResultsTable2) clone();
		for (int i = 0; i <= getLastColumn(); i++) {
		    for (int j = 0; j < size(); j++)
			getColumns()[i][j] = rt2.getColumns()[i][ces[j].index];
		    ArrayList sc = null;
		    Map scs =  getStringColumns();
		    if (scs != null)
			sc = (ArrayList) scs.get (new Integer (i));
		    if (sc != null) {
			ArrayList sc2 = (ArrayList) rt2.getStringColumns().get (new Integer (i));
			for (int j = 0; j < size(); j++)
			    sc.set (j, sc2.get (ces[j].index));
		    }
		}
	}

    // verbatim copy of (super-class) ResultsTable.sort()
    /** Sorts this table on the specified column. TO DO: add string support.*/
	public void sortLegacy (String column) {
		int col = getColumnIndex(column);
		if (col==COLUMN_NOT_FOUND)
			throw new IllegalArgumentException("Column not found");
		double[] values = new double[size()];
		for (int i=0; i<size(); i++)
			values[i] = getValueAsDouble(col,i);
		int[] indexes = Tools.rank(values);
		ResultsTable rt2 = (ResultsTable)clone();
		String[] headers = getHeadings();
		for (int i=0; i<headers.length; i++) {
			if ("Label".equals(headers[i])) {
				for (int row = 0; row<size(); row++)
					setLabel(rt2.getLabel(indexes[row]), row);
			} else {
				col = getColumnIndex(headers[i]);
				for (int row = 0; row<size(); row++)
					setValue(col, row, rt2.getValueAsDouble(col,indexes[row]));
			}
		}
	}

}

I’ve packaged the string-aware sorting algorithm as the
sortNew() method of ResultsTable2 (a subclass of
ResultsTable).

To use it, copy the .jar attached below (as a fake .tif file)
to your plugins directory, instantiate a ResultsTable2,
populate it, and call its sortNew() method.

(For convenience, if you call ResultsTable2.sort(),
you will actually run the sort() method of the
ResultsTable superclass. And for cross-checking
purposes, if you run ResultsTable2.sortLeagcy()
you will be running a verbatim copy of the original
ResultsTable.sort() code that resides in the
ResultsTable2 subclass.)

Here are a couple of jython scripts that demonstrate
ResultsTable2's string-aware sorting.

First, this shows that sorting a numeric column reorders
a string column along side it, and vice versa:

# rt2_basic_test.py
# demontrate basic sorting with string support
#
from ij.measure import ResultsTable2
rt2_basic = ResultsTable2()
rt2_basic.setValue ('int', 0, 22)
rt2_basic.setValue ('int', 1, 11)
rt2_basic.setValue ('String', 0, 'bb')
rt2_basic.setValue ('String', 1, 'aa')
rt2_basic.setValue ('double', 0, 2.2)
rt2_basic.setValue ('double', 1, 1.1)
rt2_basic_int = rt2_basic.clone()
rt2_basic_string = rt2_basic.clone()
rt2_basic_double = rt2_basic.clone()
rt2_basic_int.sortNew ('int')
rt2_basic_string.sortNew ('String')
rt2_basic_double.sortNew ('double')
rt2_basic.show ('basic -- unsorted')
rt2_basic_int.show ('basic -- int')
rt2_basic_string.show ('basic -- String')
rt2_basic_double.show ('basic -- double')

And second, because sortNew() uses a stable sort,
java.util.Arrays.sort(), internally, you can use it
to sort on multiple columns by sorting them sequentially:

# rt2_stable_test.py
# demontrate using stable-sort property to sort by multiple columns
#
from ij.measure import ResultsTable2
rt2_stable = ResultsTable2()
rt2_stable.setValue ('c1', 0, 20)
rt2_stable.setValue ('c1', 1, 20)
rt2_stable.setValue ('c1', 2, 20)
rt2_stable.setValue ('c1', 3, 20)
rt2_stable.setValue ('c1', 4, 10)
rt2_stable.setValue ('c1', 5, 10)
rt2_stable.setValue ('c1', 6, 10)
rt2_stable.setValue ('c1', 7, 10)
rt2_stable.setValue ('c2', 0, 7)
rt2_stable.setValue ('c2', 1, 6)
rt2_stable.setValue ('c2', 2, 7)
rt2_stable.setValue ('c2', 3, 6)
rt2_stable.setValue ('c2', 4, 6)
rt2_stable.setValue ('c2', 5, 7)
rt2_stable.setValue ('c2', 6, 6)
rt2_stable.setValue ('c2', 7, 7)
rt2_stable.setValue ('c3', 0, 4)
rt2_stable.setValue ('c3', 1, 3)
rt2_stable.setValue ('c3', 2, 2)
rt2_stable.setValue ('c3', 3, 1)
rt2_stable.setValue ('c3', 4, 4)
rt2_stable.setValue ('c3', 5, 3)
rt2_stable.setValue ('c3', 6, 2)
rt2_stable.setValue ('c3', 7, 1)
rt2_c2_c1 = rt2_stable.clone()
rt2_c3_c2_c1 = rt2_stable.clone()
rt2_c2_c1.sortNew ('c2')
rt2_c2_c1.sortNew ('c1')
rt2_c3_c2_c1.sortNew ('c3')
rt2_c3_c2_c1.sortNew ('c2')
rt2_c3_c2_c1.sortNew ('c1')
rt2_stable.show ('stable -- unsorted')
rt2_c2_c1.show ('stable -- c2-c1')
rt2_c3_c2_c1.show ('stable -- c3-c2-c1')

Here is “ResultsTable2.jar” renamed to “ResultsTable2_jar.tif”
(so I can post it as a fake .tif):

ResultsTable2_jar.tif (3.8 KB)

Even though it’s not an image, you should be able to
downloaded from the above forum link, and then rename
it back to “ResultsTable2.jar”.

Thanks, mm

1 Like

Just to mention that we have allowed the upload of .jar files recently so you should not need to rename it :wink:
If it does not work please let us know.
Thanks

2 Likes

Hello Laurent -

Thanks for the tip!

Here’s the .jar file, uploaded and labelled as .jar:

ResultsTable2.jar (3.8 KB)

(I ran into one issue: When I tried to upload my original .jar,
it kept showing up in the forum link as “ResutlsTable2_jar.tif”.
I had to modify the file and upload it in order for the forum
link to display correctly as “ResutsTable2.jar”. I’m guessing
that the forum checks for exact duplicate files to save space
and reuses their links when duplicates are found.)

Thanks, mm

Thanks to @mountain_man, tables can be sorted alphabetically in the latest ImageJ daily build (1.52o26).

1 Like

Hello Wayne -

Thanks.

To confirm, I ran a couple of my test scripts (modified to use
ResultsTable.sort()) after upgrading to daily build 1.52o26.
Sorting with strings works as I would expect.

A minor comment: When a column contains both strings and
numbers, I elected to sort the strings to the top. No compelling
reason for this, but I thought one might sometimes have
descriptive information at the top and numbers below, and
strings-first sorting would preserve this.

Thanks, mm

@Wayne, also thank you from me.
And also thank you @mountain_man - Wayne chimed in before I had time to implement your solution.