Sorting numeric String interval in java
Asked Answered
S

5

5

I'm having an Person class with some Person and there details as there name, age band.
The ageband interval is {"0-5", "6-10", "11-30","31-45", "46-50","50-100", "100-110"};

I'm having a Person class with name , ageBand String interval and it's parameterised constructor, getters, setters.

class Person {
    String name;
    String ageBand; //say it is string "0-50" which i pass in constructor while creating a person.
    //getters
    //setters
}

class TestAgeBand {
    public static void main(String args[]) {
        ArrayList<Person> person = new ArrayList<Person>();

        Person p1 = new Person("Mike1", "0-5");   
        Person p2 = new Person("Mike2", "6-10");
        Person p3 = new Person("Mike3", "11-30");   
        Person p4 = new Person("Mike4", "31-45");   
        Person p5 = new Person("Mike5", "50-100");   
        Person p6 = new Person("Mike6", "46-50"); 
        Person p7 = new Person("Mike7", "100-110");

        person.add(p1);
        //adding all persons to list.
    }
}

Here's what I'm doing with my code to sort the interval. I need to sort persons according to increasing intervals. I'm using Treemap to sort the intervals.

Map<String, Person> ageBandMap = new TreeMap<String, Person>(){
    for(Person p: person) {
        ageBandMap.put(p.ageBand, p.name);
    }
}

When I print interval keyset, I get

Output:

[0-5, 100-110, 11-30, 31-45, 46-50, 50-100, 6-10]

Which I don't need. I need intervals sorted like this:

[0-5, 6-10, 11-30, 31-45, 46-50, 50-100, 100-110]

Serotonin answered 4/9, 2017 at 11:35 Comment(5)
Please get most of the important non-code information out of code comments and into the question where it deserves to be.Acrid
You might want to split the age range into two fields. Then it's easier to compare.Personify
Indeed, what @MuratK. says, and make them both numeric, i.e., int fields. You're trying to sort String representation of numbers, not int representation.Acrid
Why don't you make a simple IntRange class and use that for sorting?Kryska
If the lower interval limit is always the natural successor of the previous intervals upper limit, you could omit one of them.Adrian
S
4

Try splitting the your ageBand string and converting it into an Integer, it will be easier to sort.

person.stream().sorted(Comparator.comparing(element -> Integer.parseInt(element.getAgeBand().split("-")[0])))
            .collect(Collectors.toList());

If you don't want to use Java 8, you can do it with Collections.sort() method.

 Collections.sort(person, new Comparator<Person>() {
        @Override
        public int compare(Person o1, Person o2) {
            return Integer.parseInt(o1.getAgeBand().split("-")[0]) - Integer.parseInt(o2.getAgeBand().split("-")[0]);
        }
    });
Stockton answered 4/9, 2017 at 11:41 Comment(5)
I don't have knowledge of java8 syntax. it will be helpful if it is in java7 syntax.Serotonin
To make correct comparison, there needs to be a thenComparing for secons part of the band.Kryska
Your solution saved my time. Thank you.Serotonin
Keep in mind that Collections.sort is mutating your list - which might be not desirable behavior.Coupling
@SchiduLuca since you are comparing ints here: Comparator.comparingIntShutout
L
2

We could try to be really clever here, and pass a custom comparator to the TreeMap which actually compares ranges. But appreciate that since your ranges are completely non-overlapping, we could just represent a Person using the lower (or even upper) value of his respective range, and get the same sorting effect. Hence, I suggest the following:

public class Person {
    String name;
    Integer lower;
    Integer upper;
}

SortedSet<Person> set =
    new TreeSet<Person>(new Comparator<Person>()
    {
        public int compare(Person p1, Person p2) {
            if (p1 == null && p2 == null) return 0;
            if (p1 == null) return -1;
            if (p2 == null) return 1;
            return p1.getLower().compareTo(p2.getLower());
        }
    });
Lankester answered 4/9, 2017 at 11:43 Comment(0)
M
0

If you put key-values the way you want them to be printed out you can use LinkedHashMap - it remembers put order.

Manufacturer answered 4/9, 2017 at 11:51 Comment(0)
C
0

You need to abstract interval to an interface:

interface Interval extends Comparable {

     int left();
     int right();
}

public final class IntervalImpl implements Interval {

     private final int left;
     private final int right;


     public IntervalImpl(int left, int right) {
          this.left = left;
          this.right = right;
     }

     public IntervalImpl(String interval) {
          this.left = Integer.parseInt(interval.split("-")[0]);
          this.right = Integer.parseInt(interval.split("-")[1]);
     }

     @Override
     public int left() { return left; }

     @Override 
     int right() { return right; }

     @Override
     int compareTo(Interval other) {
         return left.compareTo(other.left());
     }

 }

And then use it in your person class:

 public final class Person {
     private final Interval interval;
     private final String name;

     public Person (String name, String interval) {
          this.name = name;
          this.interval = new Interval(interval);
     }

     public Interval getInterval() {
          return interval;
     }
 }

And then use it in map:

Map<Interval, Person> map = new TreeMap<>();

I even suggest to move your intervals constants to an Enum. Made it inside your Interval abstraction.

Coupling answered 4/9, 2017 at 11:55 Comment(4)
I like this approach, except the choice of name of IntervalImpl. This suggests that this is the one and only implementation of Interval. If that interface is designed to only ever have one implementation, why not just make it a concrete class? If it can have more than one implementation, then the name IntervalImpl should state what characterizes that particular implementation.Photomultiplier
@KlitosKyriacou, with an interface like that, this might as well be the one and only implementation. 'Cause how else would you implement container of a pair of ints?Kryska
It might seems to be only one implementation. But then you can have date interval or something else. It can have more logic as well being not only "the container of a pair of ints". Yes, you can change the name as you like it.Coupling
I'm just giving overall idea. Thats an OOP approach to the problem. Interface may contain different methods. I'm not sure for example does OP needs left and right methods on the interface. And being comparable, which is natural to any Interval will lead to easy usage in sorting.Coupling
C
0

Here is the code snippet you want :

    Map<String,Person> ageBandMap = new LinkedHashMap<>();
    Map<Integer, Person> ageBandIntMap = new TreeMap<>();

    for(Person p: person)
        ageBandIntMap.put(Integer.parseInt(p.ageBand.split("-")[0]), p);

    for(Entry<Integer, Person> entry : ageBandIntMap.entrySet())
        ageBandMap.put(entry.getValue().ageBand, entry.getValue());

    for(Entry<String, Person> entry : ageBandMap.entrySet())
        System.out.format("\nageBand : %7s\t Person name : %S", entry.getKey(), entry.getValue().name);
Colophon answered 4/9, 2017 at 12:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.