简体   繁体   中英

sorting data in specific order

Here my rowId's are like the below

1

1.1

1.2

.

1.9

2

2.1

.

. 3 . . 9

.

9.9

10

10.1

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

By using the above code I am getting the output like below.

1

10

11

12

2

2.1

2.2

.

.

but I want output like the below

1

1.1

1.1.1

1.1.1.1

1.2

1.3

.

.

.

1.9

10

11

.

2

2.1

2.2

2.3

.

.

3

3.1

3.2

.

.

4

.

.

.

9

9.1

9.2

.

.

9.9

10

10.1

.

.

like this it need to proceed

If I want like this which Set I need to use

I did not try it on my computer, but it seems like the data are not translated to double for the comparison. I think adding mapToDouble(i->i.getRowId) before the sorted would solve your problem.

Use a own compare() method like this:

Collections.sort(myList, new Comparator<String>() {
    @Override
    public int compare(String id1, String id2) {
        double d1 = Double.parseDouble(id1);
        double d2 = Double.parseDouble(id2);
        return Double.compare(d1, d2);
    }
});

OR just parse/mapTo the ID into double and compare/sort it then.

As already stated in my comment the problem is that strings are compared on a character-by-character basis. Thus you'll need to either alter the strings so that each element has equal length and each character position corresponds to the same position in the other ids or parse the elements into sequences of numbers

Alternative 1 has the drawback that you'd need to have a fixed maximum length for each component (ie for a, b, c in abc etc.) or you'd have to calculate the number of elements first.

Alternative 2 thus seems to be better, but parsing the strings each time you compare them might be a little costly. Hence you might want to introduce a class that represents your id and which holds the actual id string as well as a parsed version. That class could implement Comparable and operate on the parsed version only.

Example:

class MyRowId implements Comparable<MyRowId> {
  private final String id;

  //when serializing instances of that class you might want to ignore this array 
  //and upon deserialization parse the string again
  private final int[] parsed;

  public MyRowId( String idString ) {
    id = idString;
    parsed = Arrays.stream( idString.split("\\.") ).mapToInt( Integer::parseInt ).toArray();
  }

  public int compareTo( MyRowId other )  {
    //Compare the elements one by one until we run out of elements or hit a difference
    //There might be a more Java8-ish way but I didn't find it yet
    for( int i = 0; i < Math.min( parsed.length, other.parsed.length ); i++ ) {
      int r = Integer.compare( parsed[i], other.parsed[i] );
      if( r != 0 ) {
        return r;
      }
    }

    //If we're here all compared elements were equal but one id might be longer so compare the lengths.
    //That would handle things like comparing "1.1" to "1.1.1" where the latter is greater.
    return Integer.compare( parsed.length, other.parsed.length );
  }
}

Comparing these strings would be a lot easier if you normalized them internally, to a format that can then be sorted. I'd suggest to pad the individual items with zeroes, something like this:

private static final Pattern SINGLE_DIGIT = Pattern.compile("\\b(\\d)\\b");
...
String padWithZeroes = SINGLE_DIGIT.matcher(yourInputString).replaceAll("0$1");

now just sort on the padded strings. This is assuming that the highest number will be 2 digits, if not, we will need to adjust the regex and the logic will be slightly more complicated.


If any of the numeric segments can be longer than 2 digits, then it will get more complicated. Here's a method that pads all segments to the maximum length:

static String padWithZeroes(String yourInputString, int digits) {
    final Matcher matcher = SINGLE_DIGIT.matcher(yourInputString);
    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);
}

The problem here is that you will have to know the value of the digits parameter up front, so either you know your data, or you will have to do a separate iteration in which you measure the max length.

If you had specified the return type of getRowId() then the implementation might be different.


What you may have to do is to define a Comparator for the class MyBean. It can be implemented as

  • class
  • anonymous class
  • lambda expression.

In your case it could be:

class MyBeanComparator implements Comparator<MyBean>{
  public static final MyBeanComparator INSTANCE = new MyBeanComparator();
  public int compare(MyBean b1, MyBean b2){
         //your custom rules
      }
}

Then

List<MyBean> sortedList = rootItems.stream().sorted(MyBeanComparator.INSTANCE::compare) .collect(Collectors.toList());

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM