Skip to content

Instantly share code, notes, and snippets.

@westc
Created November 7, 2023 00:51
Show Gist options
  • Save westc/4ce41e95d0aa0864dcaa7fec4ea3c162 to your computer and use it in GitHub Desktop.
Save westc/4ce41e95d0aa0864dcaa7fec4ea3c162 to your computer and use it in GitHub Desktop.
Apex - Makes it easier to sort an array of values by allowing you to specify 1 or more key values for each array value.
/**
* Makes it easier to sort an array of values by allowing you to specify 1 or
* more key values for each array value.
*/
public class KeySorter {
/**
* Represents a key that will be used to sort a value passed into `KeySorter`.
* This class is only addressable internally.
*/
private class ComparableKey implements Comparable {
/**
* The different parts of the key. This should only be an array of
* numbers or strings.
*/
public Object[] keyParts;
/**
* An array corresponding to `keyParts` indicating the type of each key
* part (Decimal or String).
*/
public String[] keyPartTypes = new String[0];
/**
* The index of the key's corresponding value. This is used to help
* with sorting.
*/
public Integer index;
/**
* Constructor for defining the comparable key.
*/
public ComparableKey(Object key, Integer index) {
keyParts = key instanceof Object[] ? (Object[])key : new Object[]{key};
for (Object keyPart : keyParts) {
keyPartTypes.add(
(keyPart instanceof Integer || keyPart instanceof Double || keyPart instanceof Decimal)
? 'Decimal'
: 'String'
);
}
this.index = index;
}
/**
* Function called internally when calling `sort()` on a list of
* `ComparableKey` objects.
*/
public Integer compareTo(Object compareTo) {
Object[] keyPartsB = ((ComparableKey)compareTo).keyParts;
String[] keyPartTypesB = ((ComparableKey)compareTo).keyPartTypes;
Integer aLen = keyParts.size(), bLen = keyPartsB.size();
for (Integer i = 0, l = Math.min(aLen, bLen); i < l; i++) {
Object a = keyParts[i];
Object b = keyPartsB[i];
String aType = keyPartTypes[i];
String bType = keyPartTypesB[i];
if (a != b) {
if (aType == 'Decimal' && bType == 'Decimal') {
Decimal decA = (Decimal)a;
Decimal decB = (Decimal)b;
return decA < decB ? -1 : 1;
}
String strA = a == null ? '' : String.valueOf(a);
String strB = b == null ? '' : String.valueOf(b);
return strA < strB ? -1 : 1;
}
}
return aLen < bLen ? -1 : aLen > bLen ? 1 : 0;
}
}
/**
* Keys whose `index` property corresponding to the Keysorter's `values`.
*/
private ComparableKey[] keys;
/**
* The index of the current `key` and `value`.
*/
private Integer index;
/**
* The current values. If `sort()` has been called this will be an array of
* sorted values.
*/
public Object[] values { get; private set; }
/**
* The current value that corresponds to the `key` that can be set or
* retrieved.
*/
public Object value {
get { return values[index]; }
}
/**
* The key value(s) that will be used to determine the sort order of the
* current `value`.
*/
public Object key {
set {
keys[index] = new ComparableKey(value, index);
}
get { return keys[index].keyParts; }
}
/**
* Constructor for starting off the KeySorter.
*/
public KeySorter(Object[] values) {
this.values = values.clone();
reset();
}
/**
* Resets the keys and the index.
*/
public void reset() {
index = -1;
Integer i = values.size();
keys = new ComparableKey[i];
while (0 < i--) {
keys[i] = new ComparableKey(null, i);
}
}
/**
* Determines if there is another `key` and `value` after the current one.
*/
public Boolean hasNext() {
return index + 1 < values.size();
}
/**
* If `hasNext()` results in `true` this will advance you to being able to
* retrieve the next `value` and set/get the next `key`. This will return
* what would have bee returned if `hasNext()` was called.
*/
public Boolean next() {
Boolean result = this.hasNext();
if (result) index++;
return result;
}
/**
* Sorts the values by using the keys that were specified in ascending
* order.
*/
public Object[] sort() {
return sort(false);
}
/**
* Sorts the values by using the keys that were specified. If `reverse` is
* `true` the values will be sorted in descending order, otherwise it will
* be sorted in ascending order. The return value will be the sorted object
* array.
*/
public Object[] sort(Boolean reverse) {
keys.sort();
Object[] newValues = new Object[0];
for (ComparableKey key : keys) {
if (reverse && !newValues.isEmpty()) {
newValues.add(0, values[key.index]);
}
else {
newValues.add(values[key.index]);
}
}
return newValues;
}
/**
* Returns an inverted version of the input string so that you can invert
* how a string column gets sorted.
*/
public static String invert(String value) {
if (value == null) return value;
Integer[] ints = new Integer[0];
for (Integer i = 0, l = value.length(); i < l; i++) {
ints.add(65536 - value.charAt(i));
}
return String.fromCharArray(ints);
}
}
// Gets all of the accounts.
KeySorter ksAccts = new KeySorter([
SELECT Id, FirstName, LastName
FROM Account
WHERE FirstName != null
AND LastName != null
LIMIT 5
]);
// Establishes the key to be used to sort each account and then sorts them
// (FirstName ASC, LastName DESC).
while (ksAccts.next()) {
Account acct = (Account)ksAccts.value;
ksAccts.key = new Object[]{acct.FirstName, KeySorter.invert(acct.LastName)};
}
Object[] sorted1 = ksAccts.sort();
System.debug(JSON.serializePretty(sorted1));
// Establishes the key to be used to sort each account and then sorts them
// (FirstName DESC, LastName DESC).
ksAccts.reset();
while (ksAccts.next()) {
Account acct = (Account)ksAccts.value;
ksAccts.key = new Object[]{acct.FirstName, acct.LastName};
}
System.debug(JSON.serializePretty(ksAccts.sort(true)));
// Sort all of the words from longest to shortest and in alphabetical order.
KeySorter ksWords = new KeySorter('Where in the world is Carmen Sandiego?'.split('\\s+'));
while (ksWords.next()) {
ksWords.key = new Object[] {
-((String)ksWords.value).length(),
ksWords.value
};
}
System.debug(ksWords.sort());
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment