I am working with a Stream of Person data and I've run into a partitioning issue related to the data.
I have a Stream of data which I'll represent below in a table:
ID Name Ticket IsEmployee
1 A Y Y
2 B
3 C Y
4 D
I am trying to return a List that is sorted by:
whether or not they're an Employee
if they have any Tickets
then by Name
I've looked into Collections.groupBy and Collections.partitioningBy, but so far haven't been able to come up withe the correct result.
My expectations are to return a list in the following order (by ID):
1 [name="A",Ticket="**[100,101]**", IsEmployee="**Y**"],
3 [name="C",Ticket="**[200,201]**", IsEmployee=""],
2 [name="**B**",Ticket="", IsEmployee=""],
4 [name="D",Ticket="", IsEmployee=""]
Any thoughts on how this might be accomplished without having to totally break apart the Stream?
Below is what my Person looks like:
public class Person {
private long id;
private String name;
private List<Ticket> tickets;
private String employeeType; // This is just a 'Y'/'N' value. This property has morphed into something else but I'm stuck using it.
public long getId(){
return id;
}
public String getName(){
return name;
}
public List<Ticket> getTickets(){
return tickets;
}
public String getEmployeeType(){
return id;
}
// Setters are the exact same as getters, meaning I have no transient methods
}
Since you are saying “I am trying to return a List that is sorted by…”, it’s not clear why you started looking for grouping or partitioning, instead of aiming exactly at, what your problem description is about, get a List and sort it by your criteria:
// collect into a mutable List, if it isn’t a mutable List in the first place
List<Person> list=stream.collect(Collectors.toCollection(ArrayList::new));
// sort by your specified criterie (note: false < true)
list.sort(Comparator.comparing((Person p) -> !p.getEmployeeType().equals("y"))
.thenComparing(p -> p.getTickets().isEmpty())
.thenComparing(Person::getName) );
You can also specify the operation as part of the Stream operation, e.g.
List<Person> list=stream
.sorted(
Comparator.comparing((Person p) -> !p.getEmployeeType().equals("y"))
.thenComparing(p -> p.getTickets().isEmpty())
.thenComparing(Person::getName) )
.collect(Collectors.toList());
but there is no technical benefit. The Stream implementation has to collect the entire contents into a temporary buffer internally, to sort it before it is collected (i.e. copied) into the resulting List. The in-place sorting of ArrayList may get away with lesser or even without data copying (not that it matters for four elements, but in general).
Related
I have this scenario. I have one paginated API which gives me the data for last 12 months. The response of the API is like:
public class PagedTransfersDto {
private List<Transfer> content;
private Page page;
#Getter
public static class Transfer {
private String id;
private Long transferId;
private Long transferRequestId;
private String status;
private BigDecimal accountReceivable;
private BigDecimal accountPayable;
private BigDecimal netReceivable;
private BigDecimal netPayable;
private String currency;
private Long transferDate;
}
#Getter
public static class Page {
private Integer size;
private Integer number;
private Integer totalElements;
private Integer totalPages;
}
}
Now I have to collect all the data and then calculate the sum of all the netReceivable and return as a Mono<CompanyIncome>. This pojo is like
public class CompanyIncome {
private BigDecimal inferredIncome = new BigDecimal(0);
}
To do this I have written something like:
CompanyIncome initialIncome = new CompanyIncome();
return myService.getTransfers(0, 50, fromDate, toDate)
.expand(pagedTransfersDto -> {
if (pagedTransfersDto.getPage().getNumber().equals(pagedTransfersDto.getPage().getTotalPages())) {
return Mono.empty();
}
return myService.getTransfers(pagedTransfersDto.getPage().getNumber() + 1, 50, fromDate, toDate);
})
.flatMap(pagedTransfersDto -> Flux.fromIterable(pagedTransfersDto.getContent()))
.reduce(initialIncome, ((companyIncome, transfer) -> {
companyIncome.setInferredIncome(companyIncome.getInferredIncome().add(transfer.getNetReceivable()));
return companyIncome;
}));
Now the catch is that it is possible that this data is only for 3 months in which case I have to extrapolate this to 12 months by multiplying by 4.
What I am thinking is to get the first item of transfers list and the last one and the see if the data is not for a whole year but cant think of a place where to perform this operation.
Since after reduce the transfers data is gone. Before that I cannot seem to find a way how to get this info and still reduce from transfers flux
I am a little new to reactive way and cant seem to find a way to do this. Any help will be greatly appreciated. Thanks
For that purpose, the best solution is to store the necessary "metadata" in the reduced object. You already have a CompanyIncome object, so maybe that is a good place? Otherwise I'd introduce either a Tuple2 or some intermediate business object (eg. CompanyIncomeAggregator) into which to store both the aggregated income and the information that you need to decide at the end if further processing is necessary.
Then in a map step, you'd read that information, act on it and either return the computed income as is or modified according to your criterion.
Important note: Using variables external to the reactive chain is a code smell, as it introduces leaky shared state: if two subscriptions are made to the same Mono, they'll work on the same CompanyIncome object. You can remediate here by using reduceWith, which takes a Supplier for the initial value: reduceWith(CompanyIncome::new, ...).
I am newbie to java, I have a scenario, where i need to list the organisation types from the table:
Requirement : Just listing, no add or removing the elements,
As i understand the difference between set and list:
Set:
Set is Unique collection of Objects.
Set is Un-ordered collection of Objects.
List:
List is non-unique collection of Objects.
List is ordered collection of Objects.
In my table i am having columns like:
id name is_active
1 Lab 1
2 Pharmacy 2
3 Hospital 3
Maximum 10 rows
**Controller**:
List<OrgType> orgTypeList = organizationService.getAllOrgTypes(true);
OrgTypeResponse response = new OrgTypeResponse();
List<EntityDetail> orgTypeDetailList = new ArrayList<>();
EntityDetail orgTypeDetail;
for(OrgType orgType : orgTypeList) {
orgTypeDetail = new EntityDetail();
orgTypeDetail.setId(orgType.getId());
orgTypeDetail.setName(orgType.getName());
orgTypeDetailList.add(orgTypeDetail);
}
response.setStatus(ResponseStatusCode.SUCCESS);
response.setTotalOrgTypes((long)orgTypeDetailList.size());
response.setOrgTypes(orgTypeDetailList);
return response;
**Service** Implementaion:
List<OrgType> orgTypeList = orgTypeRepository.findByActive(active);
return orgTypeList;
This is my EntityDetail class:
public class EntityDetail {
private Long id;
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
My question here is, can i use the Set instead of List
If Set is used, can i use TreeSet, because i need to show in the asc order of id
Or Leave the code, as it is
i just want the clarification,
Thanks
You can use any of them but things to be kept in consideration:
Set although provides unique data, but that also has a cost.
In case, you are sure that table has unique names of organizations then you should opt for list.
It seems like you are using Spring with JPA, if that is the case, then you can use SORT interface(org.springframework.data.domain.Sort) to get sorted data.
My question here is, can i use the Set instead of List
Yes, without problem, just implement methods equals and hashCode.
If Set is used, can i use TreeSet, because i need to show in the asc order of id
You can if class EntityDetail implements interface Comparable<EntityDetail>. This is necessary because TreeSet must know what is the natural order of the various EntityDetail objects.
For more details please see Oracle docs on object ordering and Javadoc for Comparable
yes u can use SET instead of List in this scenario because SET will ensure that duplicate entries are eliminated. But making use of SET make sure that you have overridden "equals" and "hashcode" appropriately.
This is how you need to override equals and hashcode methods and for sorting purpose you need to implement Comparable and implement compareTo method as follows:
class EntityDetail implements Comparable<EntityDetail>{
#Override
public int hashcode(){
int result = 17;
result = 31 * result + name.hashCode();
result = 31 * result + id;
return result;
}
#Override
public boolean equals(Object o){
if (o == this) return true;
if (!(o instanceof EntityDetail)) {
return false;
}
EntityDetail ed = (EntityDetail) o;
return ed.name.equals(name) &&
ed.id == id ;
}
#Override
public int compareTo(EntityDetail ed) {
int compareId = ((EntityDetail) ed).getId();
//ascending order
return this.id - compareId;
//descending order
//return compareId - this.id;
}
}
You can use List if you can make sure in your code that the details are added in it in the order that you want. If you are not sure of the order in which you add then you can use the Collections.sort method. For this you will also want to make your OrgType implement the Comparable interface to provide a strategy to order the OrgType objects. In your case it is by id.
If you use TreeSet, the sorting is done automatically whenever you insert into the set thereby eliminating the use of Collections.sortbut you will still have to provide an ordering strategy.
Have a look at this
There are costs of using a Set because it maintains unique elements but because you have a maximum of 10 rows that won't be a problem.
I have a list from some complex type and I want to figure a neat way to construct a list only from one of its fields using Java 8's streams. Let's take as an example:
public static class Test {
public Test(String name) {
this.name = name;
}
public String getName() {
return name;
}
private String name;
// other fields
}
And imagine that I have a List<Test> l;. Now I want to create a new list that contains the values of name of all elements in l. One possible solution that I found is the following:
List<String> names = l.stream().map(u ->u.getName()).
collect(Collectors.<String> toList());
But I was wondering if there is a better way to do this - map a list of a given type to another list of different type.
Using method references is shorter :
List<String> names = l.stream().map(Test::getName).
collect(Collectors.toList());
You can't avoid at least two Stream methods, since you must first convert each Test instance to a String instance (using map()) and then you must run some terminal operation on the Stream in order to process the Stream pipeline (in your case you chose to collect the Stream of Strings into a List).
I have a SearchCriteria POJO class
public class SearchCriteria{
private int empId;
private String empName;
private String empAddress;
private String empDesignation,
:
:
//getter + setters
}
I have a returnAllEmployees method in other class
public List<Employees> returnAllEmployees (){
// makes a db call which has lot of joins and returns info for all the employees
}
now my question is I have to filter out the result of returnAllEmployees() based on the search criteria passed i.e. if empName field of searchcriteria is populated as "ABC", the filter list should contain details of all the employees as ABC.
Similarly, if search criteria contains empName="ABC" and empDesignation="engineer", it should filter out the list containing all the employees having name abc and designation as engineer
I know it is possible by using if-else but that would create a lot of lines of codes
Your best solution is to use Java 8 streams. They are perfect for this:
List<Employee> listOfEngineersCalledFred = getAllEmployees().stream()
.filter(emp -> emp.getName().equals("Fred"))
.filter(emp -> emp.getDesignation().equals("Engineer"))
.collect(Collectors.toList());
A technique that I personally find useful and neat is to add static methods that return predicates instead of using getters:
class Employee {
public static Predicate<Employee> hasName(String name) {
return emp -> emp.name.equals(name);
}
}
These can then be used, for example, to find all employees not call Fred:
streamAllEmployees()
.filter(Employee.hasName("Fred").negate())
...
Which seems neater and more deliberate than exposing the field with a getter.
You also might consider converting your getAllEmployees to streamAllEmployees:
public Stream<Employee> streamAllEmployees() {
return employeeList.stream();
}
Then you are telling the user they can do things with the employee objects in the list rather than the list itself.
The nice thing about returning it as a stream is that once you have filtered it you can easily count, group, sort, remove duplicates, get first n etc. You can even trivially convert it to use multiple threads if you are filtering large numbers of items.
For example:
Map<String, Employee> employeesByDesignation = streamAllEmployees()
.collect(Collectors.groupingBy(emp -> emp.getDesignation()));
They are very powerful and worth learning and using.
This is more of a design question with implications for code simplicity vs. performance.
Lets say you want to make sure a set of values for a given user id are the same between two systems. The example here is to check that a student id has the same number of course enrollments in System A and System B.
For this we create:
List<String> studentList = new ArrayList<String>();
Set<String> sysAEnrollments = new HashSet<String>();
Set<String> sysBEnrollments = new HashSet<String>();
private Map<String, String> badEnrollList = new HashMap<String, String>();
And fill them appropriately, given a list of student ids(studentList):
studentList = getCurrentStudentList();
for (String id : studentList){
sysAEnrollments = getSysAEnrollments(id);
sysBEnrollments = getSysBEnrollments(id);
if (!sysAEnrollments.containsAll(sysBEnrollments)){
badEnrollList.put(id, getBadEnrollmentsById(id, sysAEnrollments, sysBEnrollments));
}
}
Question: What should the method 'getBadEnrollmentsById' return?
Either a concatenated string with enough meaning so it can just be printed out.
Or have a new object, for example another collection with the list of course ids that could be used for further processing but harder to use for printed output.
Is it worth designing thoroughly all expected objects or replace some of them with concatenated strings for clarity and performance?
NOTES:
System A is preferred as the authoritative source
Output from getBadEnrollmentsById should have all courses and flag those missing in system B.
PROPOSED SOLUTION: (2012-SEP-14)
EDIT (2012-SEP-17): Updated the Course class to include hashCode and equals
As suggested by user351721 I continued modelling the remaining objects that match the expected results/requirements.
Slight changes made a big difference and allowed me to go over this design flaw and finish with the implementation.
The revised collections are:
List<String> studentList = new ArrayList<String>();
Enrollment sysAEnrollments;
Enrollment sysBEnrollments;
Map<String, List<String>> badEnrollList = new HashMap<String, List<String>>();
And we populate the Enrollments:
for (String id : studentList){
sysAEnrollments = getSysAEnrollments(id);
sysBEnrollments = getSysBEnrollments(id);
if (!sysAEnrollments.getCourses().containsAll(sysBEnrollments.getCourses())){
List<String> missingCourses = getProblemEnrollmentListById(id, sysAEnrollments, sysBEnrollments);
badEnrollList.put(id, missingCourses);
}
}
So for now the output can be printed from badEnrollList by getting at each ArrayList and printing the course names. A course name with a * will mean that it's missing in sysB.
The Enrollment class looks like this:
public class Enrollment {
private Set<Course> courses = new HashSet<Course>();
public void setCourses(Set<Course> courses){
this.courses = courses;
}
public Set<Course> getCourses(){
return this.courses;
}
}
And the Course class ended up like this:
public class Course {
private String id;
private String name;
public String getId() {
return id;
}
public void setId(final String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(final String name) {
this.name = name;
}
// Must override hashCode() and equals()
#Override
public boolean equals(Object o){
if (o == this)
return true;
if (!(o instanceof Course))
return false;
Course c = (Course) o;
return c.id.equals(this.id) && c.name.equals(this.name);
}
#Override
public int hashCode(){
// Magic numbers as shown on Joshua Bloch's book "Effective Java" 2nd Edition, p.48
int result = 17;
result = 31 * this.id.hashCode();
result = 31 * this.name.hashCode();
return result;
}
}
The changes might look subtle but the important clue is that Enrollments are not a collection of strings, Enrollments are a collection of Courses AND each Course has a name and a availability property. They don't seem to do much but by using them I am defining the objects that I'm working with and documenting how these classes can be reused in the future.
"Growing Object-Oriented Software, Guided by Tests" addresses this question: chapter 7, "Value Types". Worth reading. An excerpt:
The more code we write, the more we’re convinced that we should define types to represent value concepts in the domain, even if they don’t do much. It helps to create a consistent domain model that is more self-explanatory. If we create, for example, an Item type in a system, instead of just using String, we can f ind all the code that’s relevant for a change without having to chase through the method calls
concatenated strings
would mean you have to define a pattern and corresponding set of valid strings and implement validation and translation to entity classes. Providing an interface or class would make it easier to update your code in a year or so, not to mention other programmers that might work with your application. Why not store student, enrollment or course objects in badEnrollList? How do these objects look like and what do you want to do with them?
In general: Yes, designing thoroughly all expected objects is worth it.
I feel that a collection, such as List<String> would be a desirable return value. This allows you to more efficiently capture multiple discrepancies between the two sets, and process the missing courses in your second object more intuitively. Printing the list wouldn't be that hard, either - depending on how you wished to convey the information.
It's also worth mentioning that the .equals() method for Set is a cleaner and more intuitive way to ensure equivalence between two sets.
Instead of using all these sets and maps, I'd use Plain Old Java Objects (POJOs) that reflect the actual business objects in question. From what you've indicated, you have Students who have an id of some sort, and who are enrolled in classes on System A and on System B. I would build up a set of Student objects defined like so:
public class Student {
private String id;
private List<String> enrollmentsA;
private List<String> enrollmentsB;
// appropriate getters and setters
}
Depending on if you want to do anything else with Classes, it may even be preferable to create some form of EnrolledClass object to represent that too.
Within the students class, I'd then have a method that would determine the "bad" enrollments. If all that you want to do with this data is generate an email message, it may even be as simple as a String:
public String getBadEnrollmentsMessage() {
List<String> enrolledBoth = getCommonEnrollments();
List<String> enrolledOnlyA = getAOnlyEnrollments();
List<String> enrolledOnlyB = getBOnlyEnrollments();
StringBuilder output;
// format the contents of the above lists into output
// format should be however you want it in the email.
return output.toString();
}
Then you could have a map of Students to email enrollments messages:
HashMap<Student, String> studentEmails;
for (Student s : allStudents) {
studentEmails.put(s, s.getBadEnrollmentsMessage());
}
Of course, if you have a method like getBadEnrollmentsMessage(), I'm not even sure you need the Map of students and strings in the first place. Frankly you could just create a sendEnrollmentEmail method, pass in a Student, and extract the message via getBadEnrollmentsMessage() right there.