Java Sort Collection on two expressions - java

I have a class
public class User {
String country;
Double rating;
Double status;
}
I need to sort a List of this class based on two conditions.
At the start of the list needs to be Users which have a certain value for country. These users sort on rating. If rating is same, compare status.
If User has another value for country, just sort it on rating.
I have tried many attempts, and this is the most recent:
final String country = me.getCountry();
Collections.sort(usersArray, new Comparator<User>() {
#Override
public int compare(User lhs, User rhs) {
User user1 = lhs.getUser();
String country1 = user1.getCountry();
int result = country.equals(country1) ? 1 : 0;
if (result == 0) {
result = Double.compare(lhs.rating, rhs.rating);
if (result == 0) {
return Double.compare(lhs.status, rhs.status);
} else
return result;
}
return Double.compare(lhs.rating, rhs.rating);
}
});

Consider using CompareToBuilder from Apache Commons Lang. You tell it which fields to compare in which order and it does the rest. Less code for you, too.
return new CompareToBuilder()
.append(lhs.country, rhs.country)
.append(lhs.rating, rhs.rating)
.append(lhs.status, rhs.status)
.toComparison();
}

The users which have another country value, not my - located at top
This happens because if countries are equal you return 1; otherwise you check rating/status. If one of these comparison return 1 then your Comparator also return 1. So countries are not equal but user from not your country has greater rating and Comparator return 1. That's why list is not sorted properly.
int result = country.equals(country1) ? 1 : 0; // DEBUG result = 0
if (result == 0) { # DEBUG enter
result = Double.compare(lhs.rating, rhs.rating); // DEBUG result = 1
if (result == 0) {
result = Double.compare(lhs.status, rhs.status);
} else
return result; // this could be omitted
}
return result; // DEBUG method return 1.
After edit:
You have to specify all possible situation:
country can be your or not (2 situations)
rating can be greater, equal, lower (3 situations)
status can be greater, equal, lower (3 situations)
If I calculated correctly (probability is not my strongest side) you have 18 situations.

Using streams from Java 8, you can do this:
Arrays.stream(usersArray)
.sorted((lhs, rhs) -> Double.compare(lhs.status, rhs.status))
.sorted((lhs, rhs) -> Double.compare(lhs.rating, rhs.rating))
.toArray(size -> new User[size])
Because the resulting stream from the array is ordered, Java guarantees that the sorting is stable. This means that the order of the statuses is kept stable when the ratings are equal. This is why I first sort on status, then on ratings.
By the way, this approach does not change the order of the original array. It returns a new one.

Related

How to sort by two fields one of which is enum?

I need to sort a List in alphabetical order by name and then to sort it one more time by type and put on the top of the list elements with specific type. This is what I have done so far, but it didn't work as expected and it returns the list sorted only by name.
public List<PRDto> present(
List<ParticipantReference> participants) {
return participants.stream()
.map(MAPPER::toDto)
.sorted(Comparator.comparing(PRDto::getName)
.thenComparing(PRDto::getParticipantType, (type1, type2) -> {
if (type1.equals(type2)) {
return 0;
}
if (type2.equals(ParticipantType.S.getDescription())) {
return 1;
}
if (type1.equals(ParticipantType.S.getDescription())) {
return -1;
}
return type1.compareTo(type2);
}))
.collect(toList());
}
This is my enum:
#Getter
public enum ParticipantType {
F("F"),
D_F("D+F"),
S("S");
private final String description;
ParticipantType(String description) {
this.description = description;
}
}
To preserve readability I would not leave the comparison in the stream pipeline but extract it in a comparator
public List<PRDto> present(List<ParticipantReference> participants) {
Comparator<PRDto> byType = Comparator.comparing(o -> !o.getType().equals(ParticipantType.S));
Comparator<PRDto> byName = Comparator.comparing(PRDto::getName);
return participants.stream().sorted(byType.thenComparing(byName)).collect(toList());
}
My answer had unnecessary redundant logic, which #Holger thankfully pointed out to me. This could also be inlined as Holger mentions it in the comments to:
return participants.stream().sorted(
Comparator.comparing((PRDto o) -> !o.getType().equals(ParticipantType.S))
.thenComparing(PRDto::getName))
.collect(toList());
Note that if you want to sort by type first and then by name you need to do so. Doing it the other way round would require the sort algorithm to keep the existing order for equal elements which might not always be guaranteed.
It would also be easier to map the enum to an integer representing the order and let Comparator do the work.
So your code could look like this:
...
.sorted(Comparator.comparingInt(p -> { //first compare by mapped type
switch( p.getParticipantType() ) {
case S: return 0;
default: return Integer.MAX_VALUE; //so you could insert other enum values if needed
}
}).thenComparing(PRDto::getName) //then compare by name
)
...
If you use a composite comparator like this it will put elements with type S at the top. If you'd write that comparator the "old" way it could look like this:
compare(PRDto left, PRDto right) {
int typeOrderLeft = mapToInt(left.getParticipantType()); //mapToInt contains the mapping expression, i.e the switch in the example above
int typeOrderRight = mapToInt(right.getParticipantType());
//first order by type
int result = Integer.compare(typeOrderLeft, typeOrderRight);
//if the type order is the same, i.e. both have S or non-S, sort by name
if( result == 0 ) {
result = String.compare(left.getName(), right.getName());
}
return result;
}
Example (already ordered):
type name
---------------
S zulu //S < F and S < D_F due to the mapping, name is irrelevant here
D_F alpha //F and D_F are considered equal, so names are compared
F bravo

Given an array, find the integer that appears an odd number of times in Java.

I am trying to find the integer that appears an odd numbers of time, but somehow the tests on qualified.io are not returning true. May be there is something wrong with my logic?
The problem is that in an array [5,1,1,5,2,2,5] the number 5 appears 3 times, therefore the answer is 5. The method signature wants me to use List<>. So my code is below.
public static List<Integer> findOdd( List<Integer> integers ) {
int temp = integers.size();
if (integers.size() % 2 == 0) {
//Do something here.
}
return integers;
}
}
I need to understand couple things. What is the best way to check all elements inside integers list, and iterate over to see if any similar element is present, if yes, return that element.
If you are allowed to use java 8, you can use streams and collectors for this:
Map<Integer, Long> collect = list.stream()
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
Given a list with integers, this code will generate a map, where the key is the actual number and value is number of repetitions.
You just have to iterate through map and find out what are you interested in.
You want to set up a data structure that will let you count every integer that appears in the list. Then iterate through your list and do the counting. When you're done, check your data structure for all integers that occur an odd number of times and add them to your list to return.
Something like:
public static List<Integer> findOdd(List<Integer> integers) {
Map<Integer, MutableInt> occurrences = new HashMap<>(); // Count occurrences of each integer
for (Integer i : integers) {
if (occurrences.containsKey(i)) {
occurrences.get(i).increment();
} else {
occurrences.put(i, new MutableInt(1));
}
}
List<Integer> answer = new ArrayList<>();
for (Integer i : occurrences.keySet()) {
if ((occurrences.get(i) % 2) == 1) { // It's odd
answer.add(i)
}
}
return answer;
}
MutableInt is an Apache Commons class. You can do it with plain Integers, but you have to replace the value each time.
If you've encountered streams before you can change the second half of the answer above (the odd number check) to something like:
return occurrences.entrySet().stream()
.filter(i -> i % 2 == 1)
.collect(Collectors.toList());
Note: I haven't compiled any of this myself so you may need to tweak it a bit.
int findOdd(int[] nums) {
Map<Integer, Boolean>evenNumbers = new HashMap<>();
nums.forEach(num -> {
Boolean status = evenNumbers.get(num);
if(status == null) {
evenNumbers.put(num, false);
}else{
evenNumbers.put(num, !status);
}
});
// Map holds true for all values with even occurrences
Iterator<Integer> it = evenNumbers.keySet().iterator();
while(it.hasNext()){
Integer key = it.next();
Boolean next = evenNumbers.get(key);
if(next == false){
return key;
}
}
}
You could use the reduce method from the IntStream package.
Example:
stream(ints).reduce(0, (x, y) -> x ^ y);

Changing complexity from O(n) to o(logn)

We have a linkedlist called ratings that contains 3 integers
userId, ItemId and value of the actual rating (example from 0 to 10)
this method actually returns rating of User i and item j that the programs reads it from a File and returns -1 if there is no rating
the method that is BigOh(n) :
public int getRating(int i, int j){
ratings.findFirst();
while(!ratings.empty()){
if(ratings.retrieve().getUserId() == i && ratings.retrieve().getItemId() == j)
return ratings.retrieve().getValue();
else
ratings.findNext();
}
return -1;
}
How can I do this in BigOh(logn)?
Or is there anyway I can solve it using Binary Search tree?
The short answer is: use a different data structure. Linked lists aren't capable of doing searches in anything other than linear time, since each element is linked together without any real semblance or order (and even if the list were sorted, you'd still have to do some kind of timed traversal).
One data structure that you could use would be a Table from Guava. With this data structure, you'd have to do more work to add an element in...
Table<Integer, Integer, Rating> ratings = HashBasedTable.create();
ratings.put(rating.getUserId(), rating.getItemId(), rating);
...but you can retrieve very quickly - in roughly O(1) time since HashBasedTable is backed by LinkedHashSet<Integer, LinkedHashSet<Integer, Rating>>.
ratings.get(i, j);
You can use hashing to achieve your task in O(1). Please read this article to gain a deeper understanding about hashing.
Since you are using Java, you can use HashMap to accomplish your task. Note that, worst case time complexity for hashing technique is O(log n) but on average it is O(1). If you are more interested to know about hash tables and amortized analysis, please go through this article.
Code Example: You can create a Class with the required attributes and implement equals and hashCode method as follows. [read Java collections - hashCode() and equals()]
class Rating {
public int user_id; // id of the user who rated
public int item_id; // id of the item being rated
public Rating(int user_id, int item_id) {
this.user_id = user_id;
this.item_id = item_id;
}
#Override
public boolean equals(Object o) {
if (o == this) {
return true;
}
if (!(o instanceof Rating)) {
return false;
}
Rating ratingObj = (Rating) o;
return ratingObj.user_id == user_id
&& ratingObj.item_id == item_id;
}
#Override
public int hashCode() {
int result = 17;
result = 31 * result + user_id;
result = 31 * result + item_id;
return result;
}
}
Then store values in HashMap as follows:
public static void main(String[] args) {
HashMap<Rating, Integer> ratingMap = new HashMap<>();
Rating rt = new Rating(1, 5); // user id = 1, item id = 5
ratingMap.put(rt, 3);
rt = new Rating(1, 2); // user id = 1, item id = 2
ratingMap.put(rt, 4);
rt = new Rating(1, 3); // user id = 1, item id = 3
ratingMap.put(rt, 5);
// now search in HashMap
System.out.println(ratingMap.get(new Rating(1, 3))); // prints 5
}
As presented, this could hardly be done in O(log n). You're looking through elements until you find the one you need. In the worst case, you won't find the element you want until the end of the loop, thus making it O(n).
Of course, if ratings were a dictionary you'd retrieve the value in almost O(1): user ids as keys and for example a list of ratings as value. Insertion would be a bit slower but not much.

Sorting a list based on a field on each of its members

Suppose I have the following List, which contains items of type ENTITY. ENTITY has an integer field which determines its natural ordering. I want to get the ENTITY which is the maximum or minimum based on that field's value. How can I implement this in Java?
List<ENTITY> lt = new ArrayList<ENTITY>();
class ENTITY
{
int field;
/* Constructor, getters, setters... */
}
Use Collections.sort with a Comparator to sort your list. Depending on whether you sort ascending or descending, the positions of the max and min elements will differ. In either case, they will be at opposite ends, one at the top of the list and one at the bottom. To sort in ascending order (smallest element at the beginning of the list, largest at the end) you can use something like this:
Collections.sort(lt, new Comparator<ENTITY> {
public int compare(ENTITY o1, ENTITY o2) {
if (o1 == null) {
if (o2 == null) {
return 0;
}
return -1;
}
else if (o2 == null) {
return 1;
}
// If field is Comparable:
return o1.getField().compareTo(o2.getField());
// OR - If field is an int
return o1.getField() < o2.getField() ? -1 : (o1.getField() > o2.getField() ? 1 : 0);
}
});
//stream the elements, map to their fields, and get the max
return lt.stream().max((e1, e2) -> Integer.compare(e1.filed, e2.filed)).orElse(/* default */);
Just one of the many applications of Java 8's stream api.
Though I would suggest working on some coding conventions first.

sorting a particular string of a certain format [closed]

Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
Questions asking for code must demonstrate a minimal understanding of the problem being solved. Include attempted solutions, why they didn't work, and the expected results. See also: Stack Overflow question checklist
Closed 9 years ago.
Improve this question
I am trying to sort this logically
Suppose I have a list of strings that have the follow elements
String[] myList = {"Spring 2013", "Fall 2009", "Fall 2010", "Spring 2012"};
I want to know how I can sort this where the result set would be
Fall 2009, Fall 2010, Spring 2012, Spring 2013
I appreciate your help, thanks!
Presuming you can't reformat the string, you can write a custom comparator that implements that logic. Your Comparator would call split to separate the semester and year. Then it can compare the year first followed by the custom month/semester logic.
You can call Collections.sort or Arrays.sort with a list and your custom comparator.
Note that the the season name such as "Spring" and "Fall" are "less significant" than the year,
Also that the seasons are not to be sorted alphabetically, but as winter, spring, summer, fall (I assume?).
So if you convert "Fall 2010" to "20104" and "Spring 2013" to "20132" then you can sort them as numbers.
In this case, the desired order is the natural (alphabetical) order, so you can use the Arrays.sort(Object\[\]) method. If the desired order does not match the natural order, you can implement a Comparator and use the Arrays.sort(String\[\], Comparator<String>) method.
An example for the Comparator implementation (just to show how it could work):
public class SemesterComparator implements Comparator<String> {
private static final List<String> SEASONS = Arrays.asList("Spring", "Summer", "Fall", "Winter");
#Override
public int compare(String one, String two) {
String[] partsOne = one.split(" ");
String[] partsTwo = two.split(" ");
if (partsOne.length != 2 || partsTwo.length != 2) {
throw new IllegalArgumentException();
}
if (!SEASONS.contains(partsOne[0]) || !SEASONS.contains(partsTwo[0])) {
throw new IllegalArgumentException();
}
// compare years
int comparison = partsOne[1].compareTo(partsTwo[1]);
if (comparison == 0) {
// if years are equal: compare season
comparison = SEASONS.indexOf(partsOne[0]).compareTo(SEASONS.indexOf(partsTwo[0]));
}
return comparison;
}
}
But as already mentioned in the comments, it would be better to store the values in a format or class that is better sortable.
Here is how a Comparator#compare function might look like:
int compare(String o1, String o2) {
// null values always prioritized
if (o1 == o2) return 0;
if (o1 == null) return -1;
if (o2 == null) return 1;
String[] parts1 = o1.split(' '); // e.g. ["Fall", "2012"]
String[] parts2 = o2.split(' ');
// invalid data, just throw some nonsense ordering out
if (parts1.length != 2 || parts2.length != 2) {
return parts2.length - parts1.length;
}
// have winner, 4 digit years can be compared lexicographical
var yearCompare = parts[1].compareTo(parts[2]);
if (yearCompare != 0) {
return yearCompare;
}
// map term names to orderable values
String[] termWeights = { "Spring", "Summer", "Fall", "Winter" };
var term1 = termWeights.indexOf(parts1[0]);
var term2 = termWeights.indexOf(parts2[0]);
// invalid terms prioritized
if (term1 == term2) return 0;
if (term1 == -1) return -1;
if (term2 == -1) return 1;
return term2 - term1;
}
Of course, I've not tested this at all. YMMV :)
Here is another alternative approach (based on a comment):
// swap "TermName Year" to "Year TermName"
String prioritizeYear (String i) {
return i.replaceFirst("^(\\w+)\\s+(\\d+)", "$2_$1");
}
// convert "TermName" to "TermValue"
String weightTerm (String i) {
return i.replace("Spring", "1")
.replace("Summer", "2")
.replace("Fall", "3")
.replace("Winter", "4");
}
int compare(String o1, String o2) {
// null values always prioritized
if (o1 == o2) return 0;
if (o1 == null) return -1;
if (o2 == null) return 1;
String n1 = weightTerm(prioritizeYear(o1));
String n2 = weightTerm(prioritizeYear(o2));
return n1.compareTo(n2);
}
Again, YMMV.

Categories