Sort List<Object[]> after three objects - java

I have a list List<Object[]>.
The Object[] has the following objects:
Date "dd.MM.yyyy"
String
Date "HH:mm".
I am able to sort the list after the dd.MM.yyyy and then after the String. Now I want to sort it after the time too also the result should be sorted "dd.MM.yyyy", sorted "String", sorted "time".
Collections.sort(resultList, new Comparator<Object[]>() {
public int compare(Object[] o1, Object[] o2) {
int dateComparison = (( o2[0]).compareTo(o1[0]));
return dateComparison == 0 ? ( o1[1]).compareTo( o2[1]) : dateComparison;
}
});
How can I get it to work?

Your problem is: wrong abstraction.
When you have "data" that belongs together, then you turn that into a class.
Meaning: instead of keeping three lists with data, you create a class that reasonably "wraps" around these three pieces of information. And then you create an array/list of that class.
Because then you can easily create different Comparator objects that compare objects of that class - by looking at different fields for example.
In other words: your are (more or less) writing "procedural" code with flat data, and "outside" code that "combines" that flat data. But when using a OO language such as Java, you should instead strive to create helpful abstractions.
But in case you want to go on with your approach, have a look at this pseudo code:
int dateComparison = o2[0].compareTo(o1[0]);
if (dateComparison != 0) return dateComparison;
int stringComparison = o2[1].compareTo(o1[1]);
if (stringComparison != 0) return stringComparison;
int secondDateComparison = o2[2].compareTo(o1[2]);
return dateComparison;
( obviously you need some casts here and there, but as your input isn't using them I left that out as well )

Try this.
resultList.sort(
Comparator.comparing((Object[] a) -> (Date)a[0])
.thenComparing(a -> (String)a[1])
.thenComparing(a -> (Date)a[2]));

Merge your Date and Time objects into a java.util.Date, then use your String as the key in a HashMap. From here, you can sort the List<HashMap<String, Date> by getting the Date first, then comparing the String keys using compareTo().
Just as a side note: if you are using a List of Arrays or a List of Lists, then it's likely a code smell.

Related

Sort List in place by a variable deep down in the structure in a complex Object

I have a complex "Order" object parsed from json. Somewhere deep in the structure i have the orderDate. I can extract it only as a STRING! And now I try to sort a List of Orders in place.
I came with the Idea of creating a new List, where the array inside consist two elements, first the Order Object itself, second the Date parsed to A Date object.
eg. new Object[]{order, new Date(order.getOrderDate())}. Then sort by second element and then parse back to a List and return. But this creates two new Lists and is not in place.
The other idea is to create a custom Comparator that sorts like this
orders.sort(new Comparator<Order>() {
#Override
public int compare(Order o1, Order o2) {
return new Date(o1.getOrderDate()).compareTo(new Date(o2.getOrderDate()));
}
});
But the second variant will create a lot of new Date Objects. Worst case a lot times for every entry.
Is there a more beautiful way around it?
It's possible to create Date instance for unique date - using Map.
final Comparator<Order> sortByDateAsc = new Comparator<Order>() {
private final Map<String, Date> map = new HashMap<>();
#Override
public int compare(Order o1, Order o2) {
Date d1 = map.computeIfAbsent(o1.getOrderDate(), Date::new);
Date d2 = map.computeIfAbsent(o2.getOrderDate(), Date::new);
return d1.compareTo(d2);
}
};
orders.sort(sortByDateAsc);
The only option in the scenario you provided is to compare the Strings directly. So basically splitting the strings down to there respective content (year, month, day, ...) and then compare those values. You could return as soon as one valule is higher, e.g. if a.month > b.month, you would not have to compare day etc.
But on the other hand, that's probably uglier and also not necessarily more performant than your current approach.
A much better approach is to adjust your JSON parsing. A JSON should always be parsed into its respective counterpart, instead of keeping the strings. So adjust your parsing to already have Date instead of String and save yourself the hassle of having to work with the data later on in your code. That is what marshalling is supposed to do, leave you with a plain (e.g.) Java representation of the JSON to work with it without having to bother with its origins.

How to sort List<String> list numerically?

I have a List<String> list which is initialized to an arrayList. That is,
List<String>list = new ArrayList();
It has the following elements.
[1,bread, 1,turkey, 10,potato, 11,plum, 12,carrot, 2,milk, 2,rice]
I would like to sort the list so that the numbers are in ascending order. For example,
[1,bread,1 turkey,2,milk,2,rice,10,potato,11,plum,12,carrot]
How can I do that?
Java is an Object-Oriented language, and you should use it.
So, create a new class with two fields: int and String.
Now parse your strings and create objects, i.e. 1,bread is parsed into the int value 1, and the String value bread.
Next, make your class implement Comparable, and implement the compareTo method to order the objects by the int value.
Finally, now that List<String> was converted to List<MyObj>, call Collections.sort(list).
You're not trying to sort the elements in the List--you're trying to sort pairs of elements. You can't do that with a simple sort. What you'll need to do is:
Define a class with two fields, an int and a String. Make the class implement Comparable.
Define a comparator for the class that compares the int fields to get the order you want. You'll have to decide what your comparator will do if the int fields are equal (do you want the String fields to be in ascending order?)
Create a List<YourClass> whose size is half the size of the original list, by going through the source list in pairs, something like
for (int i = 0; i < list.size() - 1; i += 2) {
create a YourClass by converting list.get(i) to an int, and using list.get(i+1) as the String field
}
Sort the new list
If desired, recreate a List<String> by going through the List<YourClass> and adding a String conversion of the int, followed by the String field from YourClass, to the new list.
I don't know what you're planning to do with the String list, but in most cases it will make your program easier if you create a List<YourClass> list as soon as possible, and work with YourClass objects throughout the rest of the program
The simple answer is that you could provide a custom Comparator which understands the structure of each individual String element and can parse and compare them properly. Something like this:
#Test
public void testShouldSortByNumber() {
// Arrange
List<String> list = Arrays.asList("1,bread", "1,turkey", "10,potato", "11,plum", "12,carrot", "2,milk", "2,rice");
final List<String> EXPECTED_LIST = Arrays.asList("1,bread", "1,turkey", "2,milk", "2,rice", "10,potato", "11,plum", "12,carrot");
// Act
Collections.sort(list, new Comparator<String>() {
#Override
public int compare(String o1, String o2) {
try {
int i1 = Integer.parseInt(o1.split(",")[0]);
int i2 = Integer.parseInt(o2.split(",")[0]);
// If the numbers are equal, could order by alpha on the second part of the string split
return i1 < i2 ? -1 : i1 == i2 ? 0 : 1;
} catch (Exception e) {
// Lots of possible errors above -- NPE, NFE, invalid string format, etc.
e.printStackTrace();
}
return 0;
}
});
// Assert
assert list.equals(EXPECTED_LIST);
}
The more complex answer is that you should better define your problem -- what should the result be if an element is empty or null, if the numbers are equal are the other strings compared lexicographically or is it irrelevant?
You may also want to use a different data structure -- if the content of each element is really two different logical concepts, a tuple or class may be correct. Or, you may want to use a SortedMap (of which TreeMap is probably the best implementation here) where the key is the "ingredient" and the value is the "count" (or "cost", I don't have any context on the numerical value).
You can also enhance the code above with a lambda if you have access to JDK 8+.

Java sort 2D String Array by number value of third column

I'm given a text file with countries, names and scores of skating competitors. I have to read each line into an array, then sort the array in descending order.
I managed to get everything organized like this:
infoArray[0][0] = "GER";
infoArray[0][1] = "Nathalie WEINZIERL Maylin & Daniel Peter LIEBERS Nelli & Alexander";
infoArray[0][2] = "17.0";
infoArray[1][0] = "RUS";
infoArray[1][1] = "Evgeny PLYUSHCHENKO Tatiana & Maxim Yulia LIPNITSKAYA Ekaterina & Dmitri"
infoArray[1][2] = "37.0";
infoArray[2][0] = "CHN";
infoArray[2][1] = "Kexin ZHANG Han YAN Cheng & Hao Xintong & Xun"
infoArray[2][2] = "20.0"
And so on for 10 different countries.
What I need now is to display the 10 different countries by descending order using the third column, which is the numeric value (though it's in string format).
So in other words I want the output of those three countries to be:
RUS: 37.0
CHN: 20.0
GER: 17.0
I thought about using nested loops but can't quite grasp how I could get that to work.
Using Java 8, you can just create a Comparator using the respective column as the key for comparing and then reverse that comparator. This will sort the array in-place.
Arrays.sort(infoArray,
Comparator.comparing((String[] entry) -> Double.parseDouble(entry[2]))
.reversed());
Or for older versions of Java, create you own Comparator implementing compare:
Arrays.sort(infoArray, new Comparator<String[]>() {
public int compare(String[] o1, String[] o2) {
return Double.compare(Double.parseDouble(o2[2]),
Double.parseDouble(o1[2]));
}
});
However, as noted in comments it may be worth creating a class for those information items and having that class implement Comparable:
class Info implements Comparable<Info> {
String nation;
String names;
double score;
// more stuff ...
public int compareTo(Info other) {
return Double.compare(this.score, other.score);
}
}
Then sort like this:
Arrays.sort(infoArray, Comparator.reverseOrder());
You can create a class and can set all the fields.In your case 3 fields will be there den either you can implement Comparable and in compareTo method you can write your own logic which will be dependent on numeric value and den you can sort it using Collections utility.
If you don't want to implement Comparable interface den you can write your own Comparator class and pass that to in Collections utility method that will do sort on the basis of your logic.
Please avoid using set collections if you have duplicates.
This is an XY Question because this is not the proper usage of a 2D array in Java. When using a collection or n-dimensional array in Java, the data within that data structure should always be of the same type (or at least within that type's inheritance hierarchy).
As Alexis C mentioned in his/her comment: Java is an Object Oriented language, and therefore you should be using objects of a class to contain the data. Then you can sort the structure on a particular field of that object.
Here's an example (I'm not exactly sure what the different components of the array actually represent:
class MyClass {
private string countryCode;
private string names;
private double score;
public MyClass() {
countryCode = null;
names = null;
score = 0d;
}
public string getCountryCode() { return this.countryCode; }
public void setCountryCode(string code) { this.countryCode = code; }
//repeat the above pattern for names & score
}
You can then add create an object and add data into it as such:
MyClass data = new MyClass();
data.setCountryCode("GER");
data.setNames("Nathalie WEINZIERL Maylin & Daniel Peter LIEBERS Nelli & Alexander");
data.setScore(17.0);
This data can be added to a collection such as an ArrayList and that can be sorted.
List<MyClass> myDataCollection = new ArrayList<>();
myDataCollection.add(data);
//repeat for each object to populate the list
//sort the collection
Collections.sort(myDataCollection, Comparator.comparingDouble(s -> s.getScore()).reversed());
Keep in mind that this example is for Java 8. This allows for us to use a lambda expression instead of creating an explicit comparator.
I think I would just make a new list, containing only the scores. Then sort this score list. And then replacing the scores with the related array (containing country, name, score)

Sort and dedupe java collections

I want to achieve the following, I have a collection of dates in a list form which I want deduped and sorted. I'm using collections.sort to sort the list in ascending date order and then using a treeSet to copy and dedupe elements from the list. This is a 2 shot approach ? Is there a faster, 1 step approach ?
EDIT::
Metadata
{
String name;
Date sourceDate;
}
Basically I want to order Metadata object based on the sourceDate and dedupe it too.
You can skip the Collections#sort step: TreeSet will remove duplicates and sort the entries. So basically it is a one line operation:
Set<Date> sortedWithoutDupes = new TreeSet<Date> (yourList);
If the Date is a field in your object, you can either:
have your object implement Comparable and compare objects based on their date
or pass a Comparator<YourObject> as an argument to the TreeSet constructor, that sorts your objects by date
In both cases, you don't need to pre-sort your list.
IMPORTANT NOTE:
TreeSet uses compareTo to compare keys. So if 2 keys have the same date but different names, you should make sure that your compare or compareTo method returns a non-0 value, otherwise the 2 objects will be considered equal and only one will be inserted.
EDIT
The code could look like this (not tested + you should handle nulls):
Comparator<Metadata> comparator = new Comparator<Metadata>() {
#Override
public int compare(Metadata o1, Metadata o2) {
if (o1.sourceDate.equals(o2.sourceDate)) {
return o1.name.compareTo(o2.name);
} else {
return o1.sourceDate.compareTo(o2.sourceDate);
}
}
};
Set<Metadata> sortedWithoutDupes = new TreeSet<Metadata> (comparator);
sortedWithoutDupes.addAll(yourList);
TreeSet will automatically sort its elements, so you shouldn't need to sort the list before adding to the set.

Implementing search based on 2 fields in a java class

I am trying to present a simplified version of my requirement here for ease of understanding.
I have this class
public class MyClass {
private byte[] data1;
private byte[] data2;
private long hash1; // Hash value for data1
private long hash2; // Hash value for data2
// getter and setters }
Now I need to search between 2 List instances of this class, find how many hash1's match between the 2 instances and for all matches how many corresponding hash2's match. The 2 list will have about 10 million objects of MyClass.
Now I am planning to iterate over first list and search in the second one. Is there a way I can optimize the search by sorting or ordering in any particular way? Should I sort both list or only 1?
Best solution would be to iterate there is no faster solution than this. You can create Hashmap and take advantage that map does not add same key but then it has its own creation overload
sort only second, iterate over first and do binary search in second, sort O(nlogn) and binary search for n item O(nlogn)
or use hashset for second, iterate over first and search in second, O(n)
If you have to check all the elements, I think you should iterate over the first list and have a Hashmap for the second one as said AmitD.
You just have to correctly override equals and hashcode in your MyClass class. Finally, I will recomend you to use basic types as much as possible. For example, for the first list, instead of a list will be better to use a simple array.
Also, at the beginning you could select which of the two lists is the shorter one (if there's a difference in the size) and iterate over that one.
I think you should create a hashmap for one of the lists (say list1) -
Map<Long, MyClass> map = new HashMap<Long, MyClass>(list1.size());//specify the capacity
//populate map like - put(myClass.getHash1(), myClass) : for each element in the list
Now just iterate through the second list (there is no point in sorting both) -
int hash1MatchCount = 0;
int hash2MatchCount = 0;
for(MyClass myClass : list2) {
MyClass mc = map.get(myClass.getHash1());
if(mc != null) {
hash1MatchCount++;
if(myClass.getHash2() == mc.getHash2) {
hash2MatchCount++;
}
}
}
Note: Assuming that there is no problem regarding hash1 being duplicates.

Categories

Resources