0

My rowId is like the below, it will follow parent and child relationships

1
1.1
1.1.1
2
2.1
.
.
.
9
9.1
.
9.9
10
10.1

I am using the following code to sort that beans using rowid's

List<MyBean> sortedList = rootItems.stream().sorted(Comparator.comparing(MyBean::getRowId)) .collect(Collectors.toList());

if I sort like the above then it is sorting like the below

10
11
12
.
.
19
2
2.1
.
.
3
.
.

it should not be like this.

I want to sort like the example of rowid's I have given on top.

Someone suggested me to follow his code.. i.e..,

private static final Pattern SINGLE_DIGIT = Pattern.compile("\\b(\\d)\\b");
static String padWithZeroes(String InputString, int digits) {
    final Matcher matcher = SINGLE_DIGIT.matcher(InputString);
    StringBuffer sb = new StringBuffer();
    while(matcher.find()){

        matcher.appendReplacement(sb, pad(digits - matcher.group().length())+matcher.group());
    }
    matcher.appendTail(sb);
    return sb.toString();
}

static String pad(int length) {
    final char[] chars = new char[length];
    Arrays.fill(chars, '0');
    return new String(chars);
}

If I follow his code it is returning me a string but not the list of objects.. How can I use that code.. Please help me.

user9130953
  • 449
  • 2
  • 13

2 Answers2

3

You can compare two strings without actually splitting them:

int compare(String a, String b) {
  int ai = 0, bi = 0;
  while (ai < a.length() && bi < b.length()) {
    // Extract the next int from a.
    int an = 0;
    while (ai < a.length() && a.charAt(ai) != '.') {
      an = 10*an + Character.getNumericValue(a.charAt(ai));
      ++ai;
    }
    ++ai;  // Skip the dot.

    // Extract the next int from b.
    int bn = 0;
    while (bi < b.length() && b.charAt(bi) != '.') {
      bn = 10*bn + Character.getNumericValue(b.charAt(bi));
      ++bi;
    }
    ++bi;  // Skip the dot.

    // Compare these ints, and return if they're different.
    int cmp = Integer.compare(an, bn);
    if (cmp != 0) return cmp;
  }
  // If we reached the end of one string but not the other,
  // the one we didn't reach the end of is "after" the first.
  if (ai < a.length()) return 1;
  if (bi < b.length()) return -1;
  return 0;
}

Ideone demo

You can use this to sort elements of your list by constructing a Comparator<MyBean>:

List<MyBean> sortedList =
    rootItems.stream()
        .sorted((b1, b2) -> compare(b1.getRowId(), b2.getRowId())
        .collect(Collectors.toList());
Andy Turner
  • 137,514
  • 11
  • 162
  • 243
  • That looks suspiciously as if you could do only one `while` loop and short circuit when you come across a dot in one string, but not the other... – daniu Dec 22 '17 at 13:10
2

Ah might as well.

You need a Comparator for the ids that will interpret them as ints.

public class ChapterComparator {
    // just a simple test
    public static void main(String[] args) {
        List<String> ids = Arrays.asList("10.2", "3.1.1", "10", "1.1", "2", "1");
        Collections.sort(ids, ChapterComparator::compare);
        ids.forEach(System.out::println);
    }

    public static int compare(String o1, String o2) {
        String[] split1 = o1.split("\\."), split2 = o2.split("\\.");
        int result = 0;
        for (int i = 0; i < Math.min(split1.length, split2.length); i++) {
            // compare current segment
            if ((result = Integer.compare(Integer.parseInt(split1[i]), Integer.parseInt(split2[i]))) != 0) {
                return result;
            }
        }
        // all was equal up to now, like "1.1" vs "1.1.1"
        return Integer.compare(split1.length, split2.length);
    };
}

So now for your actual Objects, you can use that Comparator in the comparingBy.

List<MyBean> sorted = rootItems.stream()
                          .sorted(Comparator.comparing(MyBean::getRowId, ChapterComparator::compare))
                          .collect(Collectors.toList());

EDIT:

and a generic version

public static <T> int compareArray(T[] a1, T[] a2, Comparator<T> comparator) {
    int result = 0;
    for (int i = 0; i < Math.min(a1.lengt, a2.length); i++) {
        if (result = comparator.compare(a1[i], a2[i]) != 0) {
            return result;
        }
        return Integer.compare(a1.length, a2.length);
    }
}

that you would keep with

public static final Comparator<String> COMPARE_IDS = 
    (s1, s2) -> compareArray(s1.split("\\."), s2.split("\\."),
                Comparator.comparing(Integer::parseInt, Integer::compare));

and call with

.sorted(Comparator.comparing(MyBean::getRowId, ChapterComparator.COMPARE_IDS));
daniu
  • 14,137
  • 4
  • 32
  • 53
  • This answer is working absolutely perfect. I just want to know why we are using `List ids = Arrays.asList("10.2", "3.1.1", "10", "1.1", "2", "1");` and `String[] split1 = o1.split("\\.")`, can you please give me the explanation by the // comments – user9130953 Aug 18 '18 at 11:47
  • @user9130953 The `List ids = ...` is just to have something to sort (ie a list or an array, in this case a list). The splitting is done to get the individual version components between the dots; it makes `12.34.56` to `{ "12", "34", "56" }. Those are then compared one after the other, and the first item that is different is relevant for the order. – daniu Aug 18 '18 at 13:46
  • Great solution :-) – Shejo284 Feb 11 '20 at 05:53