Reputation: 4547
I have a String Vector that contains data like this :
5:34, 5:38, 17:21, 22:11, ...
If i try to merge this using Collections.sort( ... ); it will appear like this :
17:21, 22:11, 5:34, 5:38
Actually i want it to appear like this :
5:34, 5:38, 17:21, 22:11
So i want to sort the elements according to the number before the colon ":" then if some elements have the same number before ":" then sort them according to the number after the ":".
What is the simplest way to do this ?
Upvotes: 3
Views: 8231
Reputation: 1
Just found this (quite old) post and the answers didn't quite solve the problem I have. I needed a more generic solution, as the values were user inputs and something like "abc 1 a 12" and "abc 1 a 1" should be sorted in order of the contained number(s). So I wrote the following Comparator:
new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
String[] s1=splitNumeric(o1);
String[] s2=splitNumeric(o2);
for (int x=0;x<s1.length&&x<s2.length;x++){
if (!s1[x].equals(s2[x])){
if (s1[x].charAt(0)=='N' && s2[x].charAt(0)=='N'){
long l1=Long.parseLong(s1[x].substring(1));
long l2=Long.parseLong(s2[x].substring(1));
return (int)Math.signum(l1-l2);
}
break;
}
}
return o1.compareTo(o2);
}
}
While the function splitNumeric is defined as follows:
private String[] splitNumeric(String s){
final String numbers="0123456789";
LinkedList<String> out=new LinkedList<String>();
int state=-1;
for (int x=0;x<s.length();x++){
if (numbers.contains(s.charAt(x)+"")){
if (state==1)
out.set(out.size()-1,out.getLast()+s.charAt(x));
else{
state=1;
out.add("N"+s.charAt(x));
}
}
else{
if (state==0)
out.set(out.size()-1,out.getLast()+s.charAt(x));
else{
state=0;
out.add("S"+s.charAt(x)+"");
}
}
}
return out.toArray(new String[0]);
}
The code will sort Strings
"X 124 B"
"X 1 Y"
"X 111 Z"
"X 12 Y"
"12:15"
"12:13"
"12:1"
"1:1"
"2:2"
as follows:
"1:1"
"2:2"
"12:1"
"12:13"
"12:15"
"X 1 Y"
"X 12 Y"
"X 111 Z"
"X 124 B"
Enjoy :)
Upvotes: 0
Reputation: 308041
The correct way to do this is to not store non-string values as strings.
The data in your collection has some structure and rules and can't be any arbitrary string. Therefore you should not use the String
data type.
Let's define a type called TwoNumbers
(because I don't know what the type should represent, even if I could guess):
class TwoNumbers implements Comparable<TwoNumbers> {
private final int num1;
private final int num2;
public TwoNumbers(int num1, int num2) {
if (num1 <= 0 || num2 <= 0) {
throw new IllegalArgumentException("Numbers must be positive!");
}
this.num1 = num1;
this.num2 = num2;
}
public static TwoNumbers parse(String s) {
String[] parts = s.split(":");
if (parts.length != 2) {
throw new IllegalArgumentException("String format must be '<num>:<num>'");
}
try {
return new TwoNumbers(Integer.parseInt(parts[0]), Integer.parseInt(parts[0]));
} catch (NumberFormatException e) {
throw new IllegalArgumentException("parts must be numeric!", e);
}
}
public int getNum1() {
return num1;
}
public int getNum2() {
return num2;
}
@Override
public int compareTo(TwoNumbers o) {
if (o == null) {
return 1;
}
int diff = Integer.compare(o.num1, this.num1);
if (diff == 0) {
diff = Integer.compare(o.num2, this.num2);
}
return diff;
}
}
The compareTo
method exists as the implementation of the Comparable
interface: it defines how objects of this type are ordered.
I've used the final
fields (and don't provide setters), because the class implements immutable objects.
This way you can directly sort your data without an additional Comparator
and don't need to distribute all that "split and parse" code all over your program. Instead you have a single class that's responsible for handling that specific format and all the other pieces of code can just use that.
Upvotes: 7
Reputation: 16209
I think this is pretty simple:
public class NumericalStringSort {
public static void main(String[] args) {
List<String> input = Arrays.asList(new String[] {"17:21", "22:11", "5:34", "5:38"});
Collections.sort(input, new NumericalStringComparator());
System.out.println(input);
}
public static class NumericalStringComparator implements Comparator<String> {
public int compare(String object1, String object2) {
return pad(object1).compareTo(pad(object2));
}
private String pad(String input) {
return input.indexOf(":") == 1 ? "0" + input : input;
}
}
}
Upvotes: 0
Reputation: 1017
Generally, objects in Java (including Collections) are compared with their default hashCode() and equals() method. For the built in objects and data types (like String, Integet etc.,) the hashCode() is computed internally and hence they are used as guaranteed by the JLS (Java Language Specification).
As we can't always be dependent upon the default/built in objects and we need to deal with our own custom objects (like Employee, Customer etc.,), we should have to override hashCode() and equals() method, so that we can provide the true/false according to the "BEST" equality of the objects of our custom classes.
Similary, sort() involves a comparison act that indeed needs a Comparator (which is a class implementing the Comparator interface with an overridden method of compare method). You should also override the compare method that takes two Objects to be compared and returns a result (0 for equal, 1 for the 1st object being greater than the second, 2 for the reverse of case 1).
Now, you data should be dealt in a different way which is quite away from the normal comparsion. You need to split the data into two parts (using a split method you can do) and then you can do the individual comparison on the two parats (first part before the colon, second part after the colon).
Finally, you should provide an instance of this custom comparator to the sort method, that will eventually do the custom sorting for your custom data :)
Upvotes: 0
Reputation: 54705
You could either create a custom Comparator
to split the String
and parse it into two ints, or create a bespoke class to represent each String
and store that in the Collection
instead. I favour the latter approach as you only incur the overhead of splitting / parsing the String once; e.g.
public class Data implements Comparable<Data> {
private final int prefix;
private final int suffix;
public Data(String str) {
String[] arr = str.split(":");
if (arr.length != 2) {
throw new IllegalArgumentException();
}
this.prefix = Integer.parseInt(arr[0]);
this.suffix = Integer.parseInt(arr[1]);
}
public int compareTo(Data data) {
// Should really avoid subtraction in case of overflow but done to keep code brief.
int ret = this.prefix - data.prefix;
if (ret == 0) {
ret = this.suffix - data.suffix;
}
return ret;
}
// TODO: Implement equals and hashCode (equals to be consistent with compareTo).
public String toString() { return String.format("%d:%d", prefix, suffix); }
}
Then it's simply a case of storing some Data
objects in your Collection
; e.g.
List<Data> l = new ArrayList<Data>();
l.add(new Data("13:56"));
l.add(new Data("100:16"));
l.add(new Data("9:1"));
Collections.sort(l);
One more thing - You mention you're using a Vector
. You should try to avoid using Vector
/ Hashtable
as these have been superseded by List
/ Map
, which were introduced as part of the Collections Framework in JDK 1.2.
Upvotes: 2
Reputation: 298908
This is horribly inefficient, but it should do the job.
Collections.sort(data, new Comparator<String>(){
public int compare(String a, String b){
String[] as = a.split(":");
String[] bs = b.split(":");
int result = Integer.valueOf(as[0]).compareTo(Integer.valueOf(bs[0]));
if(result==0)
result = Integer.valueOf(as[1]).compareTo(Integer.valueOf(bs[1]));
return result;
}
})
(Hint: if it were my code, I'd optimize it to use substrings instead of String.split(), but I'm too lazy)
Upvotes: 4
Reputation: 4543
Implement your own Comparator and give it as second argument to the Colelctions.sort method.
Upvotes: 0
Reputation: 2017
Implement your own Comparator
class that compares two values and call Collections.sort(List list, Comparator c)
.
Upvotes: 0
Reputation: 33273
Create a java.util.Comparator
and provide it to the sort
method.
Upvotes: 0