package org.ivg; import java.io.Serializable; import java.util.*; import java.util.function.Predicate; public class Test { public static void main(String[] args) { String str[] = { "2", "1", "11", "201", "1300", "2299", "java 1", "java 1 java", "java 1 JAVA", "Java 2", "java 10", "java 10 v 113", "java 10 v 13", "Java 10 v 013", "Java 10 v 000013", "Java 2017", "Java 20017", "Java 200017", "JAVA 5", "jaVA 6.1", "Java 2017", "Java 2000017", "Java 20000017", "Java 20000017", "Java 200000017", }; java.util.Collections.shuffle(Arrays.asList(str)); Arrays.sort(str, Collections.comparingDecomposing( Character::isDigit, Comparator.comparing(CharSequence::toString, String::compareToIgnoreCase), Collections.comparingNumerically( Comparator.comparing(CharSequence::toString, String::compareTo)))); for (CharSequence s : str) System.out.println(s); } } class Collections { public static Comparator comparingNumerically( Comparator literalComparator) { return new Comparators.NumericalComparator<>(literalComparator, false); } public static Comparator comparingNumericallyLeadingZeroesFirst( Comparator literalComparator) { return new Comparators.NumericalComparator<>(literalComparator, true); } public static Comparator comparingDecomposing( Predicate decomposingPredicate, Comparator firstComparator, Comparator secondComparator) { return new Comparators.DecomposingComparator<>(decomposingPredicate, firstComparator, secondComparator); } } class Comparators { static class NumericalComparator implements Comparator, Serializable { private static final long serialVersionUID = 0x77164074581FAC97L; private Comparator literalComparator; private boolean leadingZeroesFirst; NumericalComparator(Comparator literalComparator, boolean leadingZeroesFirst) { this.literalComparator = literalComparator; this.leadingZeroesFirst = leadingZeroesFirst; } private boolean skipLeadingZeroes(CharSequence s, int len) { for (int i = 0; i < len; ++i) { if (Character.digit(s.charAt(i), 10) != 0) return false; } return true; } @Override public int compare(T o1, T o2) { CharSequence cs1 = o1, cs2 = o2; int len1 = cs1.length(), len2 = cs2.length(); int dlen = len1 - len2; if (dlen > 0) { if (!skipLeadingZeroes(cs1, dlen)) return 1; cs1 = cs1.subSequence(dlen, len1); } else if (dlen < 0) { if (!skipLeadingZeroes(cs2, -dlen)) return -1; cs2 = cs2.subSequence(-dlen, len2); } else if (len1 == 0) { return 0; } int cmp = literalComparator.compare(cs1, cs2); return cmp != 0 ? cmp : (leadingZeroesFirst ^ dlen < 0 ? dlen : -dlen); } } /** * A decomposing comparator. * Acts as if the input character sequences were decomposed into series * of subsequences using the provided predicate. */ static class DecomposingComparator implements Comparator, Serializable { private static final long serialVersionUID = 0x77134074481FAC97L; Predicate decomposingPredicate; Comparator firstComparator; Comparator secondComparator; DecomposingComparator( Predicate decomposingPredicate, Comparator firstComparator, Comparator secondComparator) { this.decomposingPredicate = decomposingPredicate; this.firstComparator = firstComparator; this.secondComparator = secondComparator; } @Override public int compare(T cs1, T cs2) { Decomposer s1 = new Decomposer(cs1, decomposingPredicate); Decomposer s2 = new Decomposer(cs2, decomposingPredicate); int cmp; for (;;) { if ((cmp = firstComparator.compare(s1.get(), s2.get())) != 0 || ((cmp = secondComparator.compare(s1.get(), s2.get())) != 0)) return cmp; if (s1.eos() && s2.eos()) return 0; } } /** * Given a CharSequence and a predicate splits the former into a series * of subsequences so that every character of the very first subsequence * (possibly empty) does not satisfy the predicate, then every character * of the second subsequence satisfies the predicate, and so on. */ private static class Decomposer { private Predicate predicate; private CharSequence sequence; private boolean expectedP = false; private int index = 0; Decomposer(CharSequence sequence, Predicate predicate) { this.predicate = predicate; this.sequence = sequence; } CharSequence get() { int start = index, end = start, len = sequence.length() - start; for (; len > 0; ++end, --len) { if (predicate.test(sequence.charAt(end)) ^ expectedP) break; } expectedP = !expectedP; return sequence.subSequence(start, index = end); } boolean eos() { return index == sequence.length(); } } } }