/** * The Alphanum Algorithm is an improved sorting algorithm for strings * containing numbers. Instead of sorting numbers in ASCII order like * a standard sort, this algorithm sorts numbers in numeric order. * * The Alphanum Algorithm is discussed at http://www.DaveKoelle.com * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ package com.davekoelle.alphanum; import java.util.Comparator; /** * This is an updated version with enhancements made by Daniel Migowski, * Andre Bogus, and David Koelle. * * This class has been modified by Niall Scott for better code formatting and * other enhancements. */ public class AlphanumComparator implements Comparator { /** * {@inheritDoc} */ @Override public int compare(final Object o1, final Object o2) { // Get the Strings by calling toString() on the passed in objects. final String s1 = o1.toString(); final String s2 = o2.toString(); int thisMarker = 0; int thatMarker = 0; // Cache their lengths to avoid looking this up later. final int s1Length = s1.length(); final int s2Length = s2.length(); // Keep looping until the end of either String is reached. while(thisMarker < s1Length && thatMarker < s2Length) { final String thisChunk = getChunk(s1, thisMarker); thisMarker += thisChunk.length(); final String thatChunk = getChunk(s2, thatMarker); thatMarker += thatChunk.length(); // If both chunks contain numeric characters, sort them numerically. int result; if(Character.isDigit(thisChunk.charAt(0)) && Character.isDigit(thatChunk.charAt(0))) { // Simple chunk comparison by length. final int thisChunkLength = thisChunk.length(); result = thisChunkLength - thatChunk.length(); // If equal, the first different number counts. if(result == 0) { for(int i = 0; i < thisChunkLength; i++) { result = thisChunk.charAt(i) - thatChunk.charAt(i); if(result != 0) return result; } } } else { result = thisChunk.compareTo(thatChunk); } if(result != 0) return result; } return s1Length - s2Length; } /** * Split the String in to chunks of digits and non-digits. * * @param s The String to get the chunk from. * @param marker The index of the String to start looking at. * @return A chunk of digits or non-digits. */ private static String getChunk(final String s, int marker) { if(s == null) throw new IllegalArgumentException("String is null."); // Cache the character array to avoid repeated calls to String.charAt() final char[] chars = s.toCharArray(); final int len = chars.length; if(marker < 0 || marker > (len - 1)) throw new IllegalArgumentException("marker is invalid."); final StringBuilder chunk = new StringBuilder(); // The first character will always appear in the chunk. chunk.append(chars[marker]); marker++; if(Character.isDigit(chars[marker - 1])) { // If first character is a digit, keep appending characters until we // encounter a non-digit. while(marker < len) { if(!Character.isDigit(chars[marker])) break; chunk.append(chars[marker]); marker++; } } else { // If first character is a non-digit, keep appending character until // we encounter a digit. while(marker < len) { if(Character.isDigit(chars[marker])) break; chunk.append(chars[marker]); marker++; } } return chunk.toString(); } }