Sort ArrayList of ArrayList of Floats [duplicate] - java

I am looking to implement a sort feature for my address book application.
I want to sort an ArrayList<Contact> contactArray. Contact is a class which contains four fields: name, home number, mobile number and address. I want to sort on name.
How can I write a custom sort function to do this?

Here's a tutorial about ordering objects:
The Java Tutorials - Collections - Object Ordering
Although I will give some examples, I would recommend to read it anyway.
There are various way to sort an ArrayList. If you want to define a natural (default) ordering, then you need to let Contact implement Comparable. Assuming that you want to sort by default on name, then do (nullchecks omitted for simplicity):
public class Contact implements Comparable<Contact> {
private String name;
private String phone;
private Address address;
#Override
public int compareTo(Contact other) {
return name.compareTo(other.name);
}
// Add/generate getters/setters and other boilerplate.
}
so that you can just do
List<Contact> contacts = new ArrayList<Contact>();
// Fill it.
Collections.sort(contacts);
If you want to define an external controllable ordering (which overrides the natural ordering), then you need to create a Comparator:
List<Contact> contacts = new ArrayList<Contact>();
// Fill it.
// Now sort by address instead of name (default).
Collections.sort(contacts, new Comparator<Contact>() {
public int compare(Contact one, Contact other) {
return one.getAddress().compareTo(other.getAddress());
}
});
You can even define the Comparators in the Contact itself so that you can reuse them instead of recreating them everytime:
public class Contact {
private String name;
private String phone;
private Address address;
// ...
public static Comparator<Contact> COMPARE_BY_PHONE = new Comparator<Contact>() {
public int compare(Contact one, Contact other) {
return one.phone.compareTo(other.phone);
}
};
public static Comparator<Contact> COMPARE_BY_ADDRESS = new Comparator<Contact>() {
public int compare(Contact one, Contact other) {
return one.address.compareTo(other.address);
}
};
}
which can be used as follows:
List<Contact> contacts = new ArrayList<Contact>();
// Fill it.
// Sort by address.
Collections.sort(contacts, Contact.COMPARE_BY_ADDRESS);
// Sort later by phone.
Collections.sort(contacts, Contact.COMPARE_BY_PHONE);
And to cream the top off, you could consider to use a generic javabean comparator:
public class BeanComparator implements Comparator<Object> {
private String getter;
public BeanComparator(String field) {
this.getter = "get" + field.substring(0, 1).toUpperCase() + field.substring(1);
}
public int compare(Object o1, Object o2) {
try {
if (o1 != null && o2 != null) {
o1 = o1.getClass().getMethod(getter, new Class[0]).invoke(o1, new Object[0]);
o2 = o2.getClass().getMethod(getter, new Class[0]).invoke(o2, new Object[0]);
}
} catch (Exception e) {
// If this exception occurs, then it is usually a fault of the developer.
throw new RuntimeException("Cannot compare " + o1 + " with " + o2 + " on " + getter, e);
}
return (o1 == null) ? -1 : ((o2 == null) ? 1 : ((Comparable<Object>) o1).compareTo(o2));
}
}
which you can use as follows:
// Sort on "phone" field of the Contact bean.
Collections.sort(contacts, new BeanComparator("phone"));
(as you see in the code, possibly null fields are already covered to avoid NPE's during sort)

In addition to what was already posted by BalusC it may be worth pointing that since Java 8 we can shorten our code and write it like:
Collection.sort(yourList, Comparator.comparing(YourClass::getSomeComparableField));
or since List now have sort method also like
yourList.sort(Comparator.comparing(YourClass::getSomeComparableField));
Explanation:
Since Java 8, functional interfaces (interfaces with only one abstract method - they can have more default or static methods) can be easily implemented using:
lambdas arguments -> body
or method references source::method.
Since Comparator<T> has only one abstract method int compare(T o1, T o2) it is functional interface.
So instead of (example from #BalusC answer)
Collections.sort(contacts, new Comparator<Contact>() {
public int compare(Contact one, Contact other) {
return one.getAddress().compareTo(other.getAddress());
}
});
we can reduce this code to:
Collections.sort(contacts, (Contact one, Contact other) -> {
return one.getAddress().compareTo(other.getAddress());
});
We can simplify this (or any) lambda by skipping
argument types (Java will infer them based on method signature)
or {return ... }
So instead of
(Contact one, Contact other) -> {
return one.getAddress().compareTo(other.getAddress();
}
we can write
(one, other) -> one.getAddress().compareTo(other.getAddress())
Also now Comparator has static methods like comparing(FunctionToComparableValue) or comparing(FunctionToValue, ValueComparator) which we could use to easily create Comparators which should compare some specific values from objects.
In other words we can rewrite above code as
Collections.sort(contacts, Comparator.comparing(Contact::getAddress));
//assuming that Address implements Comparable (provides default order).

This page tells you all you need to know about sorting collections, such as ArrayList.
Basically you need to
make your Contact class implement the Comparable interface by
creating a method public int compareTo(Contact anotherContact) within it.
Once you do this, you can just call Collections.sort(myContactList);,
where myContactList is ArrayList<Contact> (or any other collection of Contact).
There's another way as well, involving creating a Comparator class, and you can read about that from the linked page as well.
Example:
public class Contact implements Comparable<Contact> {
....
//return -1 for less than, 0 for equals, and 1 for more than
public compareTo(Contact anotherContact) {
int result = 0;
result = getName().compareTo(anotherContact.getName());
if (result != 0)
{
return result;
}
result = getNunmber().compareTo(anotherContact.getNumber());
if (result != 0)
{
return result;
}
...
}
}

BalusC and bguiz have already given very complete answers on how to use Java's built-in Comparators.
I just want to add that google-collections has an Ordering class which is more "powerful" than the standard Comparators.
It might be worth checking out. You can do cool things such as compounding Orderings, reversing them, ordering depending on a function's result for your objects...
Here is a blog post that mentions some of its benefits.

You need make your Contact classes implement Comparable, and then implement the compareTo(Contact) method. That way, the Collections.sort will be able to sort them for you. Per the page I linked to, compareTo 'returns a negative integer, zero, or a positive integer as this object is less than, equal to, or greater than the specified object.'
For example, if you wanted to sort by name (A to Z), your class would look like this:
public class Contact implements Comparable<Contact> {
private String name;
// all the other attributes and methods
public compareTo(Contact other) {
return this.name.compareTo(other.name);
}
}

By using lambdaj you can sort a collection of your contacts (for example by their name) as it follows
sort(contacts, on(Contact.class).getName());
or by their address:
sort(contacts, on(Contacts.class).getAddress());
and so on. More in general, it offers a DSL to access and manipulate your collections in many ways, like filtering or grouping your contacts based on some conditions, aggregate some of their property values, etc.

Ok, I know this was answered a long time ago... but, here's some new info:
Say the Contact class in question already has a defined natural ordering via implementing Comparable, but you want to override that ordering, say by name. Here's the modern way to do it:
List<Contact> contacts = ...;
contacts.sort(Comparator.comparing(Contact::getName).reversed().thenComparing(Comparator.naturalOrder());
This way it will sort by name first (in reverse order), and then for name collisions it will fall back to the 'natural' ordering implemented by the Contact class itself.

The Collections.sort is a good sort implementation. If you don't have The comparable implemented for Contact, you will need to pass in a Comparator implementation
Of note:
The sorting algorithm is a modified mergesort (in which the merge is omitted if the highest element in the low sublist is less than the lowest element in the high sublist). This algorithm offers guaranteed n log(n) performance. The specified list must be modifiable, but need not be resizable. This implementation dumps the specified list into an array, sorts the array, and iterates over the list resetting each element from the corresponding position in the array. This avoids the n2 log(n) performance that would result from attempting to sort a linked list in place.
The merge sort is probably better than most search algorithm you can do.

I did it by the following way.
number and name are two arraylist. I have to sort name .If any change happen to name arralist order then the number arraylist also change its order.
public void sortval(){
String tempname="",tempnum="";
if (name.size()>1) // check if the number of orders is larger than 1
{
for (int x=0; x<name.size(); x++) // bubble sort outer loop
{
for (int i=0; i < name.size()-x-1; i++) {
if (name.get(i).compareTo(name.get(i+1)) > 0)
{
tempname = name.get(i);
tempnum=number.get(i);
name.set(i,name.get(i+1) );
name.set(i+1, tempname);
number.set(i,number.get(i+1) );
number.set(i+1, tempnum);
}
}
}
}
}

use this method:
private ArrayList<myClass> sortList(ArrayList<myClass> list) {
if (list != null && list.size() > 1) {
Collections.sort(list, new Comparator<myClass>() {
public int compare(myClass o1, myClass o2) {
if (o1.getsortnumber() == o2.getsortnumber()) return 0;
return o1.getsortnumber() < o2.getsortnumber() ? 1 : -1;
}
});
}
return list;
}
`
and use: mySortedlist = sortList(myList);
No need to implement comparator in your class.
If you want inverse order swap 1 and -1

With java 8 feature
List<Contact> contact = contactArray.stream().sorted((c1, c2) -> ((c1.getName().compareTo(c2.getName())))).collect(Collectors.toList());

You shoud use the Arrays.sort function. The containing classes should implement Comparable.

Related

Duplicate items added in ConcurrentSkipListSet

I am trying to maintain insertion order in ConcurrentSkipListSet. The item being added is a custom class type with value(String) and index (int) properties. It implements Comparable interface. The set behaves very inconsistently, sometimes adding duplicate items. Items are considered duplicate if they have same value.
// This is the Item class being added in the set.
final class Item implements Comparable<Item> {
private String value;
private int index;
Item(String val, int idx) {
this.value = val;
this.index = idx;
}
#Override
public int compareTo(Item o) {
// returns zero when values are equal indicating it's a duplicate item.
return this.value.equals(o.value) ? 0 : this.index - o.index;
}
#Override
public String toString() {
return this.value;
}
}
// Below is the main class.
public class Test {
ConcurrentSkipListSet<Item> set;
AtomicInteger index;
public Test() {
set = new ConcurrentSkipListSet<>();
index = new AtomicInteger(0);
}
public static void main(String[] args) {
for (int i = 1; i <= 10; i++) {
Test test = new Test();
test.addItems();
test.assertItems();
}
}
//trying to test it for 10 times. It always fails for once or twice.
private void assertItems() {
Iterator<Item> iterator = set.iterator();
String[] values = {"yyyy", "bbbb", "aaaa"};
for (String value : values) {
if (!value.equals(iterator.next().toString())) {
System.out.println("failed for :" + set);
return;
}
}
System.out.println("passed for :" + set);
}
//adding items with some duplicate values
private void addItems() {
set.add(new Item("yyyy", index.getAndIncrement()));
set.add(new Item("bbbb", index.getAndIncrement()));
set.add(new Item("yyyy", index.getAndIncrement()));
set.add(new Item("aaaa", index.getAndIncrement()));
}
Expected : passed for :[yyyy, bbbb, aaaa]
Actual : failed for :[yyyy, bbbb, yyyy, aaaa]
But as mentioned before, the result is very inconsistent. Most of the times, it passes.
Please let know what could be the reason for this behavior. Is the 'compareTo()' method wrong? If so, it should always fail.
Ideally we should override 'equals()' method also. But it doesn't matter from sorted set perspective.
Appreciate your help.
You have broken the contract of compareTo, which results in undefined behaviour.
Finally, the implementor must ensure that x.compareTo(y)==0 implies
that sgn(x.compareTo(z)) == sgn(y.compareTo(z)), for all z.
You can easily see that you fail this requirement by pulling your Items out into variables:
final Item x = new Item("yyyy", index.getAndIncrement());
final Item z = new Item("bbbb", index.getAndIncrement());
final Item y = new Item("yyyy", index.getAndIncrement());
System.out.println(x.compareTo(y));
System.out.println(x.compareTo(z));
System.out.println(y.compareTo(z));
Output:
0
-1
1
The signs are different, therefore the contract has been broken.
In your compareTo-implementation you are mixing two different properties in an illegal way. Thus you break the contract of the Comparable interface.
In your comparison, you look at the index only if the values are not equal. This way you do not define an overall natural order for your items. Depending on what comparison is done first, the result of sorting a list will be random.
#Override
public int compareTo(Item o) {
int vCompare = this.value.compareTo(o.value);
if (vCompare == 0) {
return this.index - o.index;
}
return vCompare;
}
This implementation will first compare by value and then by index. It adheres to the Comparable contract and actually defines a natural order for Items and works fine with the Set implementation.
Caution: This sample implementation will break the tests.
The tests are there to show the code behaves as intended. But in this case the intended behavior is the actual issue.
It is incompatible with the Comparable contract.
You cannot sort a list by numeric index and expect a lookup by alphabetical value to succeed. But that's exactly what is attempted here. Sort by index but find duplicate names. It does not work this way.
I don't know the implementation of ConcurrentSkipListSet in detail, but it looks like you need to override the equals method of your class to specify what qualifies two objects to be equal.
This is not an answer, rather a solution to achieve the objective based on root cause finding by #Michael and #Jochen. Modified the Item class comparator to below to have natural order of value String.
public int compareTo(Item o) {
return this.value.compareTo(o.value);
}
And then, added an index based comparator to achieve FIFO retrieval.
// This iterator would now be used in assertItems() method in main class.
private Iterator<Item> getFIFOIterator() {
ArrayList<Item> list = new ArrayList<>(set);
list.sort(Comparator.comparingInt(Item::getIndex));
return list.iterator();
}
#Michael and #Jochen : Appreciate you for taking your time and figuring out the root cause.

Iterate and invoke a list of methods

Let say I have 2 classes:
public class Person
{
private String name;
private int age;
private Contact contact;
//getter & setter
}
public class Contact
{
private String phone;
private String email;
//getter & setter
}
With the classes above, I want to create 2 instances of Person class, with different field value. Then I want to compare some fields of 2 objects with their getter function, but I don't want to compare all fields.
For example, I want to compare the field name and phone, then I will store this 2 getter method to a list like something below:
List<WhatShouldBeTheDataType> funcList = new ArrayList<>();
funcList.add(MyClass::getName);
funcList.add(MyClass::getContact::getPhone) //I know this won't work, what should be the solution?
then loop through the funcList, pass the 2 objects I want to compare into the function, if the value not same, write something into the database. This can be easily done with ordinary if...else... way, but is it possible to do in Java 8 way?
Below is what I want to achieve in if...else... way:
if(person1.getName() != person2.getName())
{
//message format basically is: "fieldName + value of object 1 + value of object 2"
log.append("Name is different: " + person1.getName() + ", " + person2.getName());
}
if(person1.getContact.getPhone() != person2.getContact().getPhone())
{
log.append("Phone is different: " + person1.getContact.getPhone() + ", " + person2.getContact.getPhone());
}
//other if to compare other fields
It looks like Person and MyClass refer to the same thing in your question.
You need a Function<Person,String>, since your functions accept a Person instance and return a String:
List<Function<Person,String>> funcList = new ArrayList<>();
funcList.add(Person::getName);
funcList.add(p -> p.getContact().getPhone());
For the second function, you can't use a method reference, but you can use a lambda expression instead.
Given an instance of Person, you can apply your functions as follows:
Person instance = ...;
for (Function<Person,String> func : funcList) {
String value = func.apply(instance);
}
to complete Eran's code:
boolean isEqual(Person person1, Person person2){
for (Function<Person,String> function:functionList) {
if (!function.apply(person1).equals(function.apply(person2))) return false;
}
return true;
}
then use the returned boolean to check and update your database.
Although you can use a list of functions (as suggested in Eran's answer), using comparators directly is probably more appropriate for your use case.
You can alternatively use a chain of comparators, and then use the result of compare:
Comparator<Person> comparators = Comparator.comparing((Person p) -> p.getName())
.thenComparing((Person p) -> p.getContact().getPhone());
Person p1 = null, p2 = null;
if(0 != comparators.compare(person1, person2)) {
//p1 and p2 are different
}
Even simpler (and more natural, in my opinion), is overriding equals in Person, and checking if(!person1.equals(person2))
Edit (after update of the question):
Here's a version built on a function list, dynamically generating the log content by adding a field name list:
List<Function<Person, String>> functions =
Arrays.asList(Person::getName, p -> p.getContact().getPhone());
List<String> fieldNames = Arrays.asList("Name", "Phone");
IntStream.range(0, functions.size())
.filter(i -> functions.get(i).apply(person1)
.compareTo(functions.get(i).apply(person2)) != 0)
.mapToObj(i -> String.format("%s is different: %s, %s",
fieldNames.get(i),
functions.get(i).apply(person1),
functions.get(i).apply(person2)))
.forEach(log::append);
This rather takes advantage of the fact that String is already comparable, and avoids creating comparators altogether.

Android II JAVA Sorting An ArrayList of an object

First of all sorry if my English bad, its not my first language..
I'm working on and android app project, that needed to sort ArrayList of an object..so I made this method to deal with that...
Lets say that I have an object of Restaurant that will contain this data:
private String name;
private float distance ;
And I sort it using the value of the variable distance from lowest to highest:
public void sort(RArrayList<RestaurantData> datas) {
RestaurantData tmp = new RestaurantData();
int swapped;
boolean b = true;
while (b) {
swapped = 0;
for (int i = 0; i < datas.size()-1; i++) {
if (datas.get(i).getDistance() > datas.get(i+1).getDistance()) {
tmp = datas.get(i);
datas.set(i, datas.get(i+1));
datas.set(i+1, tmp);
swapped = 1;
System.err.println("Swapped happening");
}
}
if (swapped == 0) {
System.err.println("Swapped end");
break;
}
}
But when i try the program..the result of an ArrayList is still random, is there any problem with my logic to sort the ArrayList of an object..
Please Help...Thankyou..
Why not use the Collections.sort method?
Here's how you could do it in your project:
public void sort(RArrayList<RestaurantData> datas) {
Collections.sort(datas, new Comparator<RestaurantData>() {
#Override
public int compare(RestaurantData lhs, RestaurantData rhs) {
return lhs.getDistance() - rhs.getDistance();
}
});
}
The above solution is a bit "destructive" in the sense that it changes the order of the elements in the original array - datas. If that's fine for you go ahead and use it. Personally I prefer things less destructive and if you have the memory to spare (meaning your array is small) you could consider this solution which copies the array before sorting. It also assumes your RArrayList is an implementation of ArrayList or backed up by it:
public List<RestaurantData> sort(RArrayList<RestaurantData> datas) {
// Create a list with enough capacity for all elements
List<RestaurantData> newList = new RArrayList<RestaurantData>(datas.size());
Collections.copy(newList, datas);
Collections.sort(newList, new Comparator<RestaurantData>() {
#Override
public int compare(RestaurantData lhs, RestaurantData rhs) {
return lhs.getDistance() - rhs.getDistance();
}
});
return newList;
}
Another thing to consider is also to create a single instance of the Comparator used in the method, since this implementation will create one instance per call. Not sure if it's worth it though, because it will also be destroyed quite soon since the scope is local.
Here's the documentation for the Collections api
One last thing, the comparator simply needs to return a value less than 0 if the elements are in the right order, bigger than 0 if they're in the wrong order or 0 if they're the same. Therefore it seems to be that it's enough to simply subtract the distances of each restaurant. However, if this isn't the case, please implement the comparator suiting your needs.

Using enum as a parameter

So, I am trying to use an enumerated data type as parameter in the place of an object being passed in. I know that a simple switch statement would work but that doesn't really seem elegant to me. I have searched and found that enums can also have actions attached to them but I'm not so clear how to use it in this case or if it is even possible, or if i am just really tired. let me try to use code to explain what I'm asking.
First I have a class with certain fields of other objects that I am basically trying to use the enums to reference. In this case I have a method that acts on one of the fields of trees, because their are multiple trees the method needs to know which tree to act on.
public class bstContactManage()
{
// fields of other objects
BST searchTreeFirstName = new BST(new ComparatorObjOne);
BST searchTreeLastName = new BST(new ComparatorObjTwo);
// and so on and so forth
public boolean modify(Contact contactToFind, BST ToFindIn, String newContactInfo)
{
Contact contUpdate = new Contact(ContactToFind)//save for readdition to tree
contUpdate.update(newContactInfo);
toFindIn.remove(contactToFind);
if(toFindIn.add(contUpdate)) return true;
else return false;
}
}
what I'm wondering or more or less pondering is how to replace the BST parameter with a an enum
i know i could use a switch statement but that doesn't seem any more effective maybe more elegant than passing it an int value and letting it go wild!
so is there a way to get method to look something like
public boolean modify(Contact contactToFind, Enum BSTType, String newContactInfo)
{
Contact contUpdate = new Contact(ContactToFind)//save for readdition to tree
contUpdate.update(newContactInfo);
BSTType.remove(contactToFind);
if(BSTType.add(contUpdate)) return true;
else return false;
}
most of my question stems from the fact that an object such as
bstContactManage man = new bstContactManage()
will be instantiated in another class, and therefore it isn't safe or doesn't seem proper to me to do something like
man.modify(contactIn, man.searchTreeFirstName, "String");
update:
so for more clarification i have another method find which searches a given BST, and currently i am implementing it like this
public List<Contact> find(BinarySearchTree treeUsed, String findThis)
{
//create a new contact with all fields being the same, find is dependent and comparator on tree;
Contact tempContact = new Contact(findThis, findThis, findThis);
return treeUsed.getEntry(tempContact); // where getEntry returns a list of all matching contacts
}
I could do something like
public List<Contact> find(EnumField field, String findThis)
{
BST treeUsed;
switch(Field){
case FIRST:
treeUsed = this.searchTreeFirstName;
break;
cast LAST:
treeUsed = this.searchTreeLastName;
break;
Contact tempContact = new Contact(findThis, findThis, findThis);
return treeUsed.getEntry(tempContact); // where getEntry returns a list of all matching contacts
}
Enum could provide different implementation of its method. A good example would be Math operation:
enum Op {
PLUS {
int exec(int l, int r) { return l + r; }
},
MINUS {
int exec(int l, int r) { return l - r; }
};
abstract int exec(int l, int r);
}
Then I could do Op.PLUS.exec(5, 7) to perform 5 plus 7
See http://docs.oracle.com/javase/tutorial/java/javaOO/enum.html for more detail on how to use enum.
In your case, I wouldn't use enum for something having loads of logic and state, but here is how you could use enum with methods having different implementations.
enum BSTType {
SearchTreeFirstName {
void someMethod(Contact c) {...}
},
SearchTreeLastName {
void someMethod(Contact c) {...}
};
abstract void somemethod(Contact c);
}
public boolean modify(Contact contactToFind, BSTType bstType, String newContactInfo) {
// ...
bstType.someMethod(contact);
// ...
}
By looking at the variable name and class name, I think what you actually meant is indexing Contact in a TreeSet either by first name or last name
enum IndexType implements Comparator<Contact> {
IndexByFirstName {
#Override
public int compare(Contact o1, Contact o2) {
return o1.firstName.compareTo(o2.firstName);
}
},
IndexByLastName {
#Override
public int compare(Contact o1, Contact o2) {
return o1.lastName.compareTo(o2.lastName);
}
};
}
TreeSet<Contact> contacts = new TreeSet<Contact>(IndexType.IndexByLastName);

How to sort a String collection that contains numbers?

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 ?
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.
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)
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.
Create a java.util.Comparator and provide it to the sort method.
Implement your own Comparator class that compares two values and call Collections.sort(List list, Comparator c).
Implement your own Comparator and give it as second argument to the Colelctions.sort method.
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 :)
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;
}
}
}
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 :)

Categories