sort an ArrayList with multiple conditions - java

say i have an array list MyArrayList<MYObject>
myObject class looks like :
public class myObject
{
String name;
Int age;
String : city;
}
and my dummy data looks like this :
name : martin , age : 20 , city : NY
name : felix , age : 19 , city : LA
name : brian , age : 21 , city : NY
name : brian , age : 19 , city : NY
now i wanna sort myArraylist (which have the above data in it) in this order -
name : felix , lastname : 19 , city : LA
name : brian , lastname : 21 , city : NY
name : martin , lastname : 20 , city : NY
name : brian , lastname : 19 , city : NY
as you can see that the above data is sorted in two ways - firstly by cities then again by age so this is what i wanna do , i wanna sort an arrayList by an order then again i wanna sort in another order by keeping the first order
anyone knows how can i do it ?? please let me know then
if my question is not clear enough then let me know i'll fix it

You can create a Comparator to first compare by city, then by age. Something like this:
Comparator<MyObject> comparator = new Comparator<MyObject>(){
#Override
public int compare(final MyObject o1, final MyObject o2){
if(!o1.getCity().equals(o2.getCity())){
return o1.getCity().compareTo(o2.getCity());
}else{
return o1.getAge().compareTo(o2.getAge());
}
}
};
Collections.sort(myArrayList,comparator);
Edit:
I used the "compareTo" method of the Integer class, which you can't call on an int primitive type. If you use int for the age, you could just write out an if statement for the comparison.

First of all classes should have a descriptive name and the class names should start with an upper case character. So I'll call your class Person
You need to create a custom Comparator to be used by the sort utility.
For a reusable solution you could use the Bean Comparator and the Group Comparator.
The BeanComparator allows you to sort on any property in the object
The GroupComparator allows you to combine multiple Comparator into a single Comparator.
So the basic logic would be:
BeanComparator city = new BeanComparator(Person.class, "getCity");
BeanComparator age = new BeanComparator(Person.class, "getAge");
GroupComparator gc = new GroupComparator(city, age);
Collections.sort(arrayList, gc);

You have two options, which are essentially equivalent:
perform two sorts with a stable sort algorithm: first by age, then by city. A stable sort algorithm is a sorting algorithm that maintains the relative order of two elements A and B if they are equivalent under the current < function. This way, if you first sort by age, when you sort again by city and two elements have the same city, their relative order will be determined by the input order, so they will be sorted by age.
perform a single sort with a compare function that says that A < B whenever A.city != B.city && A.city < B.city or A.city = B.city && A.age > B.age.
Notice that Java's Collections.sort is guaranteed to be stable, so pick whichever strategy better suits your needs.

Related

Group by multiple values

For example, I have a list of students with their semesters and subjects.
Subject Semester Attendee
---------------------------------
ITB001 1 John
ITB001 1 Bob
ITB001 1 Mickey
ITB001 2 Jenny
ITB001 2 James
MKB114 1 John
MKB114 1 Erica
When I need to group them by one value with Stream api, I can make something like that;
Map<String, List<Student>> studlistGrouped =
studlist.stream().collect(Collectors.groupingBy(w -> w.getSubject()));
and it does work. However, when I want to group them by two or more values (like the Sql handles), I cannot figure out how I need to do.
One approach is to group by a class that holds all the fields you want to group by. This class must implement hashCode and equals using all these fields in order to work as expected.
In your example this would be:
public class SubjectAndSemester {
private final String subject;
private final int semester; // an enum sounds better
public SubjectAndSemester(String subject, int semester) {
this.subject = subject;
this.semester = semester;
}
// TODO getters and setters, hashCode and equals using both fields
}
Then, you should create this method in your Student class:
public SubjectAndSemester bySubjectAndSemester() {
return new SubjectAndSemester(subject, semester);
}
Now, you can group as follows:
Map<SubjectAndSemester, List<Student>> studlistGrouped = studlist.stream()
.collect(Collectors.groupingBy(Student::bySubjectAndSemester));
This creates a one level grouping of students.
There's another option that doesn't require you to use a Tuple or to create a new class to group by. I mean you can have an n-level grouping of students, by using downstream groupingBy collectors:
Map<String, Map<Integer, List<Student>>> studlistGrouped = studlist.stream()
.collect(Collectors.groupingBy(Student::getSubject,
Collectors.groupingBy(Student::getSemester)));
This creates a 2-level grouping, so the returned structure is now a map of maps of lists. If you can live with this, even for grouping of more levels, then you can avoid creating a class that acts as the key of the map.
You could create a calculated field that is combination of the two columns
Map<String, List<Student>> studlistGrouped =
studlist.stream().collect(Collectors.groupingBy(w -> w.getSubject() + "-" w.getAttendee()));
EDIT:
Another more "appropriate" solution: Accoridng to this blog post , you need to define a Tuple class that can hold the two columns:
class Tuple {
String subject;
String attendee;
}
Map<String, List<Student>> studlistGrouped =
studlist.stream().collect(Collectors.groupingBy(w -> new Tuple(w.getSubject(), w.getAttendee())));

Compare two lists of unequal lengths and remove partial matches?

Lets say I have two lists like:
List1 = Fulton Tax Commissioner 's Office, Grady Hospital, Fulton Health Department
List2 = Atlanta Police Department, Fulton Tax Commissioner, Fulton Health Department,Grady Hospital
I want my final list to look like this:
Final List = Fulton Tax Commissioner 's Office,Grady Hospital,Fulton Health Department,Atlanta Police Department
I can remove duplicates from these lists by adding both the lists to a set. But how do I remove partial matches like Fulton Tax Commissioner?
I suggest: Set the result to a copy of list 1. For each member of list 2:
If the result contains the same member, skip it.
If the result contains a member that starts with the list 2 member, also skip the list 2 member
If the result contains a member that is a prefix of the list 2 member, replace it by the list 2 member
Otherwise add the list 2 member to the result.
If using Java 8, the tests in the 2nd and 3rd bullets can be conveniently done with streams, for example result.stream().anyMatch(s -> s.startsWith(list2Member));.
There is room for optimization, for example using a TreeSet (if it’s OK to sort the items).
Edit: In Java:
List<String> result = new ArrayList<>(list1);
for (String list2Member : list2) {
if (result.stream().anyMatch(s -> s.startsWith(list2Member))) { // includes case where list2Member is in result
// skip
} else {
OptionalInt resultIndex = IntStream.range(0, result.size())
.filter(ix -> list2Member.startsWith(result.get(ix)))
.findAny();
if (resultIndex.isPresent()) {
result.set(resultIndex.getAsInt(), list2Member);
} else {
result.add(list2Member);
}
}
}
The result is:
[Fulton Tax Commissioner 's Office, Grady Hospital, Fulton Health Department, Atlanta Police Department]
I believe this exactly the result you asked for.
Further edit: In Java 9 you may use (not tested):
resultIndex.ifPresentOrElse(ix -> result.set(ix, list2Member), () -> result.add(list2Member));
Add to set by passing a comparator, like below:
Set s = new TreeSet(new Comparator() {
#Override
public int compare(Object o1, Object o2) {
// add the logic to say that partial match is considered same.
}
});
s.addAll(yourList);

DataStructure to use to store elements and sorted them in the order of their usage frequency/timestamp

I have one problem statement: Suppose there is Employee class and a class has to be designed which provides get and put methods to access and add employees. Employee class is a third party class so we can't make changes in it. There is a memory limit like max 50k employees can be stored. Now if while adding employee if memory limit is reached then remove one employee by following any of the below approaches and add the new employee:
on the basis of access frequency
on the basis of timestamp. one with the old timestamp should be removed
I can think of taking two hashmaps: - one for storing employee objects and one with the employees with key and timestamp or frequency as value.
But this is not the optimal solution. Please provide the valuable suggestions on which data structure can be used to solve this problem.
Just use a normal collection for storing your Employee. When adding a new employee and you detect that the memory is full, you just determine the employee with the smallest sort order criteria and remove it from the database.
Since you did not provide the Employee class I declare here an example:
class Employee {
int frequency;
Instant lastAccess;
}
Your database (can also be LinkedList):
private List<Employee> myDatabase = new ArrayList<>();
The comporator for evaluating the employee with the smallest criteria (first frequency is compared, if equals the lastAccess is compared:
public int compare( Employee emp1, Employee emp2 )
{
int result = emp1.frequency - emp2.frequency;
if ( result == 0 )
{
result = emp1.lastAccess.compareTo( emp2.lastAccess );
}
return result;
}
And here the 'main' method which inserts a new employee into your database (the implementation of isMemoryFull is left to you):
private void insertEmp( Employee emp)
{
if ( isMemoryFull() )
{
Employee minEmp = myDatabase.stream().min( this::compare ).get();
myDatabase.remove( minEmp);
}
myDatabase.add( emp );
}
Note: This works with Java8 lambdas and streams. For java < 8 you have to implement the compare method formally as Comparator and use Collections.sort instead the stream().min() method.

How to create dynamic variable names in Java? [duplicate]

This question already has answers here:
Assigning variables with dynamic names in Java
(7 answers)
Closed 8 years ago.
I have a hashmap of type Map<String, List<Integer>> empage where String is the name of the department and List is the list of age of employees who work in that department.
Now for each department I want to divide the age of employees into 5 age categories like (0-20,20-40...and so on).How can I create these 5 list variables for each department dynamically? I mean I cannot hardcode variable name like Finance_grp1 and so on for each department name? So basically I want something like:
for(each departname in empage.keyset())
{
create Arraylist departmentname_grp1
create Arraylist departmentname_grp2
create Arraylist departmentname_grp3
.
.
and so on till 5 groups
}
For Example the structure that I want is something like this:
Department Finance
grp1 for age 0-20
grp2 for age 20-40
and so on till grp5
Department HR
grp1 for age 0-20
grp2 for age 20-40
and so on till grp5
This way for all the department names, I want to group employees age into groups
Also after creating these 5 groups and processing the employee age into categories, I want to variable of type ChartSeries for each department name which then I will add to create a bar chart.So, I want something like:
for(department_name in empage)
{
ChartSeries department_name = new ChartSeries();
}
Can anyone help me in resolving this issue?
UPDATE: I know that in Java we cannot append dynamic string while creating variables. What I want is the possible solution to this issue and above problem
The basic answer to your question is you cannot dynamically assign variable names in Java. Here's another SO post that has more potential ways around this: Assigning Dynamic Variable Names
if the dept-age calculations are very important for your business logic, you could consider to create a new type like DeptAgeStat.
It has:
String name;
List<Integer> allAges;
List<Integer> getGroup1(){//return new List, or a ListView of allAges};
List<Integer> getGroup2(){//same as above};
...
List<Integer> getAgesWithWhateverPartitionCondidtions(here could have condition para see below text){...};
this will ease your future calculation. If it is necessary, e.g. you could have different criteria to filter/group ages in future, you can even design a PartitionCriteria type as parameter to those methods. again, it depends on the requirement.
Hope it helps.
I would propose you to change the List inside your Map, so you will have:
Map<String, Map<Integer,List<Integer>>>
where the outer Map has the departments's String as Keys, and the inner Map has the Integer representation per age group (eg 0,1,2,3,4).
*As #sage88 noted, you could use String instead of Integer as keys for the age groups.
Alright I think I see better what you're trying to do, but ultimately the answer to your question is still to use an appropriate collection. Try something like this:
Map<Department, Map<Integer, List<Employee>>> departmentEmployeeAgeMap;
where Integer is the age bracket they fall into 0-20 being 0, 20-40 being 1, and so on. This assumes you have a department class, if you don't you could use String to represent the department names as well.
This way when you want to store an employee they are accessible by a department key and then an Integer age range key.
So if you need to add employees to the groups you would go like this:
Map<Department, Map<Integer, List<Employee>>> departmentEmployeeAgeMap = new Map<Department, Map<Integer, ArrayList<Employee>>>();
Map<Integer, List<Employee>> currentDepartmentAgeMap;
for(department : departments) {
departmentEmployeeAgeMap.put(department, new Map<Integer, List<Employee>>());
currentDepartmentAgeMap = departmentEmployeeAgeMap.get(department);
for(int i=0; i<5; i++) {
currentDepartmentAgeMap.put(i, new ArrayList<Employee>());
}
for(employee : department) {
currentDepartmentAgeMap.get(employee.getAge()/20).add(employee);
}
}
And then accessing this datastructure to pull the employees back out is easy:
departmentEmployeeAgeMap.get(department).get(1);
Will retrieve a list of all the employees who work in a given department between the ages of 20-39.
If you really want to be able to create dynamic variable names you should consider another language other than java. This is not how Java was intended to function and it does not do this well.

How to perform sorting based on Comparator keeping original sort intact in Java

I've been going through implementation examples of Comparable vs Comparator interface.
But, I've been stuck at one point in it's implementation :
Suppose, I've a simple class : Employee which has a default sorting mechanism based on employee name.
public class Employee implements Comparable<Employee> {
private int empSalary;
private String empName;
#Override
public int compareTo(Employee e) {
return this.empName.compareTo(e.empName);
}
}
But, let's say, I've to sort based on employee name first, and then if two employess have same name, i've to sort them according to their salary.
So, I wrote a custom comparator to sort based on salary like below
public class SalaryComparator implements Comparator<Employee> {
#Override
public int compare(Employee e1, Employee e2) {
return e1.empSalary - e2.empSalary;
}
}
But, when I ran my test class to sort based on name first, salary second, the output is not as what is expected.
Collections.sort(employeeList, new SalaryComparator());
Input Order :
Name : Kumar, Salary : 40
Name : Sanket, Salary : 10
Name : Kumar, Salary : 20
Expected output :
Name : Kumar, Salary : 20
Name : Kumar, Salary : 40
Name : Sanket, Salary : 10
Actual Output :
Name : Sanket, Salary : 10 // incorrect order
Name : Kumar, Salary : 20
Name : Kumar, Salary : 40
This is not because your Employee class already has a default ordering, that using Collections.sort with a custom comparator will introduce a new layer of ordering.
For example let's say that the default ordering of your Employees is by their salary in ascending order. Now let's say you want to sort them by salary in descending order.
According to your logic how this will behave?
Collections.sort(employees, new SalaryDescendingComparator());
The fact is that when you provide a custom comparator to Collections.sort, it will use only this one and not the sorting mechanism that you implemented in your Employee class.
As the doc states:
Sorts the specified list according to the order induced by the
specified comparator.
So because the SalaryComparator compares employees only by their salary, that's why you get this output.
If you want to sort by name first and then by salary, you'll have to do this in one time, i.e :
public class Employee implements Comparable<Employee> {
private int empSalary;
private String empName;
#Override
public int compareTo(Employee e) {
int cmp = this.empName.compareTo(e.empName);
return cmp != 0 ? cmp : Integer.compare(empSalary, e.empSalary);
}
}
If you wanted to create a custom comparator to separate name-ties based on salary, have the comparator first try to compare the two employees, then if that's a tie, use salary:
public class SalaryComparator implements Comparator<Employee> {
#Override
public int compare(Employee e1, Employee e2) {
if (e1.compareTo(e2) == 0)
return e1.empSalary - e2.empSalary;
return e1.compareTo(e2);
}
}
Note that you can do it the following way in Java 8:
public class Employee {
private int empSalary;
private String empName;
public int getEmpSalary() {
return empSalary;
}
public String getEmpName() {
return empName;
}
}
Note that I added a public getter, then for simple comparison:
List<Employee> employeeList = new ArrayList<>();
// Populate it
employeeList.sort(Comparator.comparingInt(Employee::getEmpSalary));
This will create a Comparator<Employee> that compares the integer values returned by Employee.getEmpSalary(), I am using a method reference there, which is a fancy way of writing employee -> employee.getEmpSalary(), which is a lambda function mapping from Employee to int.
For reverse you could use:
employeeList.sort(Comparator.comparingInt(Employee::getEmpSalary).reversed());
Now if you want to compare first the name, and the salary, you can use:
employeeList.sort(
Comparator.comparing(Employee::getEmpName)
.thenComparingInt(Employee::getEmpSalary)
Instead of creating a new Comparator with all your conditions in it, like suggested by others, you can also sort multiple time.
So if you replace your sort part with this:
Collections.sort(employeeList, new SalaryComparator());
Collections.sort(employeeList);
Then you'll have your expected output.
It might be less efficient than sorting everything at the same time with a combined comparator, but it is quite handy when you need to sort with multiple properties.
And if you really need to have a powerful way of sort by different properties (like in a table), then you should try to create a chain of comparators.
Here is an example of such a pattern:
http://commons.apache.org/proper/commons-collections/javadocs/api-2.1.1/org/apache/commons/collections/comparators/ComparatorChain.html
(This is the old way to do what is proposed in the answer using Java8 :)

Categories