I have this class
class TimeSpentStats{
int manId;
String sessionId;
int userId;
Long timeStamp;
}
I have a List and I want to get the minimum timestamp and maximum
from the list for each (manId, sessionId, userId)
So for example, I have:
manId sessionId userId timeStamp
1 01F 5 1000
1 01F 5 1005
3 6Y 3 7
3 6Y 3 16
I need for (1 01F 5) -> min = 1000 , max = 1005
and for (3 6Y 3 ) - > min = 7 , max = 16
I need to add maybe 2 attributes in the same class?
Any ideas if I can do this? Thank you
If you have a List of TimeSpentStatus called list, the following algorithm should do what you want it to do.
HashMap<String, Pair> statsList = new HashMap<String, Pair>();
for(TimeSpentStats stats : list){
// Constructs the combination of IDs that is used as the key to a Pair object
String statsStr = stats.manId + " " + stats.sessionId + " " + stats.userId;
if(statsList.containsKey(statsStr)){
// Update min and/or max time for the current combination as necessary
statsList.get(statsStr).minTime = Math.min(statsList.get(statsStr).minTime, stats.timeStamp);
statsList.get(statsStr).maxTime = Math.max(statsList.get(statsStr).maxTime, stats.timeStamp);
}else{
// Construct a new Pair for the ID combination and add max and min times
Pair p = new Pair();
p.maxTime = stats.timeStamp;
p.minTime = stats.timeStamp;
// Adds the new combination to the HashMap, which can now be updated in the if-statement
statsList.put(statsStr, p);
}
}
statsList will now contain the max and min times for each combination with (userID + " " + manID + " " + sessionID) as the keys. You will then be able to get the Pair object for a specific combination by using statsList.get(userId + " " + manId + " " + sessionId) (as long as it exists of course).
Here is the Pair class
class Pair{
public long minTime;
public long maxTime;
}
public class Main
{
public static void main( String[] args )
{
Map< TimeSpentStats, MinMax > hashMap = new HashMap< TimeSpentStats, MinMax >();
addToMap( hashMap, new TimeSpentStats( 1, "01F", 5, 1000L ) );
addToMap( hashMap, new TimeSpentStats( 1, "01F", 5, 1005L ) );
addToMap( hashMap, new TimeSpentStats( 3, "6Y", 3, 7L ) );
addToMap( hashMap, new TimeSpentStats( 3, "6Y", 3, 16L ) );
for ( Map.Entry< TimeSpentStats, MinMax > entry : hashMap.entrySet() )
{
TimeSpentStats timeSpentStats = entry.getKey();
MinMax minMax = entry.getValue();
System.out.println( timeSpentStats.getManId() + "\t" + timeSpentStats.getSessionId() + "\t" + timeSpentStats.getUserId() + "\tMin Time Stamp :" + minMax.getMin() + "\tMax Time Stamp :" + minMax.getMax() );
}
}
private static void addToMap( Map< TimeSpentStats, MinMax > hashMap, TimeSpentStats timeSpentStats )
{
MinMax timeStampMinMax = hashMap.get( timeSpentStats );
if ( timeStampMinMax != null )
timeStampMinMax.updateValues( timeSpentStats.getTimeStamp() );
else
hashMap.put( timeSpentStats, new MinMax( timeSpentStats.getTimeStamp() ) );
}
}
class MinMax
{
private Long min;
private Long max;
MinMax( Long timeStamp )
{
this.min = timeStamp;
this.max = timeStamp;
}
public Long getMin()
{
return min;
}
public Long getMax()
{
return max;
}
public boolean updateValues( Long timeStamp )
{
if ( timeStamp < this.min )
{
this.min = timeStamp;
return true;
}
else if ( timeStamp > this.max )
{
this.max = timeStamp;
return true;
}
return false;
}
}
class TimeSpentStats
{
private final int manId;
private final String sessionId;
private final int userId;
private final Long timeStamp;
public TimeSpentStats( int manId, String sessionId, int userId, Long timeStamp )
{
this.manId = manId;
this.sessionId = sessionId;
this.userId = userId;
this.timeStamp = timeStamp;
}
public int getManId()
{
return manId;
}
public String getSessionId()
{
return sessionId;
}
public int getUserId()
{
return userId;
}
public Long getTimeStamp()
{
return timeStamp;
}
#Override
public boolean equals( Object obj )
{
if ( obj instanceof TimeSpentStats )
{
TimeSpentStats timeSpentStats = (TimeSpentStats)obj;
return this.manId == timeSpentStats.manId && this.sessionId.equals(timeSpentStats.sessionId) && this.userId == timeSpentStats.userId;
}
return false;
}
#Override
public int hashCode()
{
return sessionId.hashCode();
}
}
Edit : a small bug is fixed. Here I had forgot to use .equals(), as the sessionId you have mentioned is of String type.
If the elements of the triple are independent, then this is a problem in combinatorics as much as anything else: you need to find all of the triples. That's a pretty well-described problem, and there are recursive solutions that Java can handle, though of course you have to watch the stack if the problem gets large. If the elements of the triple are dependent, then life is easier.
In any case, the brute force approach is obvious: iterate over the comparable items and compare item to max and compare item to min. Record the max and the min.
If these are in an object, then you can make a nested HashMap, for example mapping manIds to (maps of sessionIds to (maps of Userids to (max,min))) where max, min might be the object which contains the max/min value or they might be the values themselves - that's up to you and what you need.
This map would be a static member of the class that these instantiate, essentially you'd be caching the maxen and the mins as the objects are created. Not too hard to see how this would go into the constructor. This adds some overhead as the problem gets large, but it spares you a lot of iteration, so it's probably a worthwhile tradeoff.
Related
I'll preface by saying that this is a project for a class. The logic of the code all functions and, as it stands, currently outputs close to the correct solution (I stopped working on the output when I learned that I had used the wrong interface). The problem is, the requirements very explicitly state we must use comparator. Being new to Java, I used Comparable, not realizing there was an explicit difference. This is an algorithms class in Java, my background is in Python and there are definitely some differences that are going over my head - I'm sure that will be apparent in my code.
I've sort of come to understand the difference between the two, but if you asked me to ELI5, I don't think I could. Please help me understand how exactly to implement Comparator as opposed to Comparable. I get that I need a separate class but then I'm not exactly sure how that should be formatted and what to do with it once I have it.
I'm including below the code of the working solution that implements comparable. Any guidance would be extremely appreciated. TIA.
EDIT: Also, by all means, anything else in particular about my code that stands out as going against Java conventions, I'm happy to hear about.
import java.util.PriorityQueue;
import java.util.ArrayList;
import java.io.File;
import java.util.Scanner;
import java.io.FileNotFoundException;
public class ProcessScheduling {
public static class Process implements Comparable<Process> {
private Integer priority;
private int id;
private int arrivalTime;
private int duration;
public Process(int ID, Integer Priority, int Duration, int ArrivalTime) {
this.id = ID;
this.priority = Priority;
this.duration = Duration;
this.arrivalTime = ArrivalTime;
}
public Integer getPriority() {return priority;}
public int getId() {return id;}
public int getArrivalTime() {return arrivalTime;}
public int getDuration() {return duration;}
public void setPriority(Integer priority) {this.priority = priority;}
#Override
public String toString() {
return "Process ID = " + getId()
+ "\n\tPriority = " + getPriority()
+ "\n\tArrival = " + getArrivalTime()
+ "\n\tDuration = " + getDuration();
}
#Override
public int compareTo(Process P) {
if (this.getPriority() > P.getPriority()) {return 1;}
else if (this.getPriority() < P.getPriority()) {return -1;}
return 0;
}
}
public static void main(String[] args) {
// Create ArrayList D to store new processes
ArrayList<Process> D = new ArrayList<Process>();
// Read the input file
try {
File f = new File("process_scheduling_input.txt");
Scanner reader = new Scanner(f);
while (reader.hasNextLine()) {
// Create new Processes and add them to ArrayList D
String[] data = reader.nextLine().split(" ");
Process newProcess = new Process( Integer.valueOf(data[0]),
Integer.parseInt(data[1]),
Integer.parseInt(data[2]),
Integer.parseInt(data[3])
);
D.add(newProcess);
}
reader.close();
} catch (FileNotFoundException e) {
System.out.println("An error occured. File does not exist.");
e.printStackTrace();
}
// Print all processes
for (int i = 0; i < D.size(); i++) {
Process current = D.get(i);
System.out.print("Id = " + current.getId());
System.out.print(", priority = " + current.getPriority());
System.out.print(", duration = " + current.getDuration());
System.out.println(", arrival time = " + current.getArrivalTime());
}
// Instantiate priorityQueue and some parameters
int currentTime = 10;
boolean running = false;
int maxWaitTime = 30;
float totalWaitTime = 0;
int currentEndTime = 0;
Process current = null;
PriorityQueue<Process> Q = new PriorityQueue<Process>();
// Print maxWaitTime
System.out.println("\nMaximum wait time = " + maxWaitTime);
// While D still has a process in it
while (D.isEmpty() == false) {
// Check if current running process has finished
if (running == true && currentEndTime <= currentTime) {
// Print that Process finished
System.out.print("Process " + current.getId());
System.out.println(" finished at time " + currentTime + "\n");
// Update running flag
running = false;
// Update priority of Processes in Q that have been waiting longer than max wait time
System.out.println("Update priority:");
if (Q.isEmpty() == false) {
for (Process p : Q) {
int waitTime = currentTime - p.getArrivalTime();
if (waitTime >= maxWaitTime) {
Integer priority = p.getPriority();
int id = p.getId();
System.out.print("PID = " + id);
System.out.print(", wait time = " + waitTime);
System.out.println(", current priority = " + priority);
priority -= 1;
p.setPriority(priority);
System.out.print("PID = " + id);
System.out.println(", new priority = " + priority);
}
}
}
}
// Find process with earliest arrivalTime in D
Process earliest = D.get(0);
for (int i = 1; i < D.size(); i++) {
if (D.get(i).getArrivalTime() < earliest.getArrivalTime()) {
earliest = D.get(i);
}
}
// Check if arrivalTime of earliest is <= to currentTime
if (earliest.getArrivalTime() <= currentTime) {
// Add to Q and remove from D if yes
Q.add(earliest);
D.remove(earliest);
}
// Check if Q is not empty and running flag is false
if (Q.isEmpty() == false && running == false) {
// Remove process in Q with smallest priority
current = Q.poll();
int waitTime = currentTime - current.getArrivalTime();
totalWaitTime += waitTime;
currentEndTime = currentTime + current.getDuration();
// Process removed from priority queue, print info
System.out.print("\nProcess removed from queue is: id = " + current.getId());
System.out.print(", at time " + currentTime);
System.out.print(", wait time = " + waitTime);
System.out.println(" Total wait time = " + totalWaitTime);
System.out.println(current);
running = true;
}
if (D.isEmpty() == true) {
System.out.println("\nD becomes empty at time " + currentTime + "\n");
}
currentTime++;
}
// D is now empty, all processes are in Q
while (Q.isEmpty() == false) {
// Check if current running process has finished
if (running == true && currentEndTime >= currentTime) {
// Update running flag
running = false;
// Update priority of Processes in Q that have been waiting longer than max wait time
System.out.println("Update priority:");
if (Q.isEmpty() == false) {
for (Process p : Q) {
if (p.getArrivalTime() - currentTime >= maxWaitTime) {
p.priority = p.getPriority() - 1;
}
}
}
}
// If no Process running, start a new one
if (running == false){
current = Q.poll();
int waitTime = currentTime - current.getArrivalTime();
totalWaitTime += waitTime;
currentEndTime = currentTime + current.getDuration();
// Process removed from priority queue, print info
System.out.print("\nProcess removed from queue is: id = " + current.getId());
System.out.print(", at time " + currentTime);
System.out.print(", wait time = " + waitTime);
System.out.println(" Total wait time = " + totalWaitTime);
System.out.println(current);
running = true;
}
currentTime++;
currentTime++;
}
}
}
The biggest differences between Comparator and Comparable is that a Comparator can order objects of different classes. Comparable generally can only order the same type of objects.
So, I'd say you're well on your way, it's not as big a jump as you expected.
tl;dr
Comparator.comparing( Process :: getPriority )
Details
You said:
how exactly to implement Comparator as opposed to Comparable. I get that I need a separate class
Yes, that is the difference, a separate class.
Implementing Comparable is done by adding a method compareTo within the class of the objects being sorted. That means you are limited one single approach to sorting.
Implementing Comparator is done in a separate class. So we can more than one such Comparator implementation, for as many ways as we wish to sort our objects.
For example, a class Student representing people enrolled in a school might implement Comparable by adding a compareTo method that sorts by last name, and secondarily by first name. But in some contexts we might want to sort students by their grade level, and in other contexts by the date in which they first enrolled, and in yet other contexts we might sort by the distance between their home and the schoolhouse. For this other contexts we would write various classes implementing Comparator.
If you want to compare by a getPriority method that returns an int primitive, then your separate Comparator class might look like this.
class ProcessByPriorityComparator implements Comparator< Process > {
public int compare( Process p1 , Process p2 ){
if( p1.getPriority() == p2.getPriority() )
{
return 0;
}
else if ( p1.getPriority() > p1.getPriority() )
{
return 1;
}
else
{
return -1;
}
}
}
Or simplify:
class ProcessByPriorityComparator implements Comparator< Process > {
public int compare( Process p1 , Process p2 ){
return Integer.compare( p1.getPriority() , p2.getPriority() ) ;
}
}
Example usage:
Comparator< Process > processByPriorityComparator =
new ProcessByPriorityComparator() ;
Collections.sort( listOfProcesses , processByPriorityComparator ) ;
Or simply:
Collections.sort( listOfProcesses , new ProcessByPriorityComparator() ) ;
The lambda functional functional features in Java makes it quite easy to define a Comparator locally rather than formally defining a separate class.
Greatly helping to simplify this work are the static methods on Comparator such as comparing. So defining a Comparator can be as simple as using a method reference for an accessor “getter” method to sort by a particular property of the object.
This is quite appropriate in your particular case. We can use a method reference for the Process#getPriority method.
Comparator< Process > processByPriorityComparator =
Comparator.comparing( Process :: getPriority );
Collections.sort( listOfProcesses , processByPriorityComparator ) ;
Or, more simply, drop the named variable holding the comparator.
Collections.sort( listOfProcesses , Comparator.comparing( Process :: getPriority ) )
By the way, other issues with your code…
Do yourself a favor and move that huge main method out to its own class. Create a separate class named something like Main or App.
public class App {
public static void main( String[] args ) {
…
}
}
And I see no need for Process to be static nor be nested.
I suspect you feel some compulsion to have everything squeezed into a single class or file. Not so in Java. In Java you should generally have many classes, smaller and separate.
Eventually, as your app grows, organize the many classes by using packages and possible modules.
There is Java class:
public class Item {
private String dateModified;
private Integer color;
}
where dateModified in format "hh:mm:ss",
and ArrayList<Item> list which contains 10 elements.
So i want check my list and:
if now() - dateModified > 1 min , then change color to 1
if now() - dateModified > 5 min , then change color to 2
if now() - dateModified > 10 min, then change color to 3
How to implements it with Java Stream API?
UPDATE:
I implemented my task in such code below. It works as expected, but it seems huge and non-elegance.
I forget to say that list should be mutable.
list.stream()
.map(c -> {
if (compareTime(c.getDateModified(), 600)) {
c.setColor(3);
} else if (compareTime(c.getDateModified(), 300)) {
c.setColor(2);
} else if (compareTime(c.getDateModified(), 60)) {
c.setColor(1);
}
return c;
}).collect(Collectors.toList());
private boolean compareTime(String dateModified, Integer delta) {
boolean result = false;
LocalDateTime now = LocalDateTime.now();
int hour = now.getHour();
int minute = now.getMinute();
int second = now.getSecond();
Integer secondsDateModified = Integer.parseInt(dateModified.substring(0, 2)) * 3600 +
Integer.parseInt(dateModified.substring(3, 5)) * 60 +
Integer.parseInt(dateModified.substring(6, 8)) ;
Integer secondsNow = hour * 3600 + minute * 60 + second ;
Integer delta1 = secondsNow - secondsDateModified;
if ((delta1) > delta) {
result = true;
}
return result;
}
Any suggestions to improve the code are appreciated.
Instead of storing a String as the time, store a LocalTime object. Also, instead of mutating the original item, return the item or a new item with the new color.
public static class Item {
private final LocalTime dateModified;
private final Integer color;
public Item(LocalTime dateModified, Integer color) {
this.dateModified = dateModified;
this.color = color;
}
public Item withColor(int color) {
return new Item(dateModified, color);
}
public LocalTime getDateModified() {
return dateModified;
}
public Integer getColor() {
return color;
}
}
Example
public static void main(String[] args) {
List<Item> items = new ArrayList<>(Arrays.asList(
new Item(LocalTime.parse("10:30:00"), 0),
new Item(LocalTime.parse("10:30:01"), 255)));
LocalTime now = LocalTime.now();
List<Item> modified = items.stream().map(item -> {
long minutes = Duration.between(item.dateModified, LocalTime.now())
.toMinutes();
return minutes >= 1 ?
item.withColor(minutes >= 10 ? 3 : minutes >= 5 ? 2 : 1) : item;
}).collect(Collectors.toList());
}
What about to use separate streams for update each required range of items:
public static void updateColor(List<Item> items) {
final LocalTime now = LocalTime.now();
final Function<Item, Long> getDurationInMinutes = item -> Duration.between(LocalTime.parse(item.dateModified), now).toMinutes()
final Predicate<Item> betweenOneAndFive = item -> {
long duration = getDurationInMinutes.apply(item);
return duration > ONE && duration <= FIVE;
};
final Predicate<Item> betweenFiveAndTen = item -> {
long duration = getDurationInMinutes.apply(item);
return duration > FIVE && duration <= TEN;
};
final Predicate<Item> greaterThanTen = item -> getDurationInMinutes.apply(item) > TEN;
items.stream().filter(betweenOneAndFive).forEach(item -> item.color = 1);
items.stream().filter(betweenFiveAndTen).forEach(item -> item.color = 2);
items.stream().filter(greaterThanTen).forEach(item -> item.color = 3);
}
It is a matter of proper mapping function from difference of minutes to numbers.
items.forEach(item -> item.setColor(((int) Math.floor(differenceInMinutes(item.getDateModified(), now) + 5)) / 5));
Note, that the differenceInMinutes method would return the difference in floating point arithmetic.
The steps taken are:
Find the difference in minutes from the date of the items with now.
Cast the result to an int which will work like Math.floor.
Add 5 to the result.
Divide by 5.
So, for example 1.3 minutes would result in (1+5)/5 which is 1.
9.8 minutes would result in (9+5)/5 which is 2.
And so on.
First, as Jason, said do not mutate yours items inside streams, make copies. (What is the danger of side effects in Java 8 Streams?).
You will need intermediates operations :
long getElapseTimeSinceModification(Item item) {
return ChronoUnit.MINUTES.between(LocalTime.parse(item.dateModified), LocalDate.now());
}
Optional<Integer> getNewColor(long elapseTimeSinceModification) {
if (elapseTimeSinceModification > 10) {
return Optional.of(3);
} else if (elapseTimeSinceModification > 5) {
return Optional.of(2);
} else if (elapseTimeSinceModification > 1) {
return Optional.of(1);
}
return Optional.empty();
}
Item withNewColor(int newColor, Item item) {
Item newTtem = new Item();
newTtem.color = newColor;
newTtem.dateModified = item.dateModified;
return newTtem;
}
and then you can apply them to your stream :
List<Item> itemsWithNewColor = items.stream()
.map(item -> getNewColor(getElapseTimeSinceModification(item))
.map(newColor -> withNewColor(newColor , item))
.orElse(i))
.collect(Collectors.toList());
Hey so I will do my best at explaining this (and will have to change some code sorry if it looks weird) so I currently have:
public Job(String name, int tickets, double wage) {
this.name = name;
this.tickets = tickets;
this.wage = wage;
& and getters and setters for these and also making a list here:
Job Peter = new Job (name: "Peter", tickets: 100, wage: 1.55);
System.out.println(Peter);
So my issue is that lets say the total wage is calculated by tickets * wage how do I go about doing this by automation.
I can do this, however want a simpler method where it is automated so I can just for example Peter.getTotalWage
System.out.println(Peter.getTickets() * (Peter.getWage()));
My issue is when it comes to multiplying int with double, I've tried something like this but can't seem to get it working as I know it is wrong (probably completely wrong to be honest):
}
public double totalWage(int tickets, double wage) {
int i = tickets;
Double d2=Double.valueOf(i);
double sum = d2 * wage;
return sum;
}
Right now I have two classes.
If you can help us identify how to go about doing this that'll be great!
tl;dr
Use BigDecimal for money.
Organize your classes properly. Apparently you have:
An employee with a wage rate
A job whose cost is the number of tickets multiplied by the assigned employee's wage rate.
So you need an Employee class with a member variable for wage rate. And you need a Job class with an assigned Employee object, and a getWageCost method with this logic:
BigDecimal cost =
this.employee // An `Employee` object is assigned to this `Job` object.
.getWageRate() // The `Job` object asks the `Employee` object for its current wage rate.
.multiply(
new BigDecimal( this.countOfTickets )
)
.setScale( 2 , RoundingMode.HALF_EVEN ) // Round to the penny using Banker’s Rounding.
;
Tip to students: Notice how working towards a simple clear problem description written down in plain English can lead naturally to a proper class design. Lesson learned: Prose before code.
BigDecimal
For money, never use double or Double, float or Float. The floating-point types trade away accuracy for speed of execution. Use BigDecimal for fractional money.
BigDecimal wageRate = new BigDecimal( "1.55" ) ;
Multiply.
BigDecimal cost = this.employee.getWageRate().multiply( new BigDecimal( this.countOfTickets ) ) ;
But that line above fails to address the fractional penny (assuming this is United States dollars). So round to the penny or to the tenth or hundredth of a penny as directed by your accounting staff.
Round by calling the setScale method. Use the rounding method as directed by your accounting staff. Often in business matters that would be Banker's Rounding. So to the line above we should add a call to setScale with the number of digits (2 for whole pennies of US Dollar) and the typing of rounding.
BigDecimal cost = this.employee.getWageRate().multiply( new BigDecimal( this.countOfTickets ) ).setScale( 2 , RoundingMode.HALF_EVEN ) ;
The code above is logic on the Job class. Notice how it reaches into the Employee object to obtain the current wage rate for that worker.
Example code
Here is complete example code.
Employee alice = new Employee( "Alice" , "EMP00013" , new BigDecimal( "1.55" ) );
Job whateverJob = new Job( alice , 100 , "whatever" , UUID.randomUUID() );
BigDecimal wagesCostForWhateverJob = whateverJob.getWageCost();
System.out.println( "whateverJob: " + whateverJob + " costs " + wagesCostForWhateverJob );
whateverJob: Job{ employee=Employee{ name='Alice' | employeeId='EMP00013' | wageRate=1.55 } | countOfTickets=100 | description='whatever' | id=b4e397b3-e02e-42be-b117-622cee96a192 } costs 155.00
Employee.java
package work.basil.example;
import java.math.BigDecimal;
import java.util.Objects;
public class Employee
{
// ---------| Member vars |----------------------------------
private String name, employeeId;
private BigDecimal wageRate;
// ---------| Constructors |----------------------------------
public Employee ( String name , String employeeId , BigDecimal wageRate )
{
this.name = Objects.requireNonNull( name );
this.employeeId = Objects.requireNonNull( employeeId );
this.wageRate = Objects.requireNonNull( wageRate );
}
// ---------| Accessors |----------------------------------
public String getName ( )
{
return name;
}
public String getEmployeeId ( )
{
return employeeId;
}
public BigDecimal getWageRate ( )
{
return wageRate;
}
// ---------| Object |----------------------------------
#Override
public boolean equals ( Object o )
{
if ( this == o ) return true;
if ( o == null || getClass() != o.getClass() ) return false;
Employee employee = ( Employee ) o;
return getEmployeeId().equals( employee.getEmployeeId() );
}
#Override
public int hashCode ( )
{
return Objects.hash( getEmployeeId() );
}
#Override
public String toString ( )
{
return "Employee{ " +
"name='" + name + '\'' +
" | employeeId='" + employeeId + '\'' +
" | wageRate=" + wageRate +
" }";
}
}
Job.java
package work.basil.example;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Objects;
import java.util.UUID;
public class Job
{
// ---------| Member vars |----------------------------------
private Employee employee;
private Integer countOfTickets;
private String description;
private UUID id;
// ---------| Constructors |----------------------------------
public Job ( Employee employee , Integer countOfTickets , String description , UUID id )
{
this.employee = employee;
this.countOfTickets = countOfTickets;
this.description = description;
this.id = id;
}
// ---------| Accessors |----------------------------------
public Employee getEmployee ( )
{
return employee;
}
public Integer getCountOfTickets ( )
{
return countOfTickets;
}
public String getDescription ( )
{
return description;
}
public UUID getId ( )
{
return id;
}
public BigDecimal getWageCost ( )
{
BigDecimal cost = this.employee.getWageRate().multiply( new BigDecimal( this.countOfTickets ) ).setScale( 2 , RoundingMode.HALF_EVEN );
return cost;
}
// ---------| Object |----------------------------------
#Override
public boolean equals ( Object o )
{
if ( this == o ) return true;
if ( o == null || getClass() != o.getClass() ) return false;
Job job = ( Job ) o;
return getId().equals( job.getId() );
}
#Override
public int hashCode ( )
{
return Objects.hash( getId() );
}
#Override
public String toString ( )
{
return "Job{ " +
"employee=" + employee +
" | countOfTickets=" + countOfTickets +
" | description='" + description + '\'' +
" | id=" + id +
" }";
}
}
In real work, if Job::getWageCost would be called very very often, we might consider caching the result. We could add a member variable to hold the result. But avoid falling into the trap of premature optimization.
I played around with the code a bit; maybe this helps?
public class Main {
public static void main(String[] args){
Job jobOne = new Job (100, 1.55);
Person p1 = new Person("Peter Williams");
double jobOnePay = jobOne.getTotal();
p1.setWage(jobOnePay);
System.out.println(p1.getName() + " gets paid: " + p1.getSetWage());
}
}
class Job{
private int tickets;
private double wage;
Job(int tickets, double wage) {
this.tickets = tickets;
this.wage = wage;
}
double getTotal(){
return tickets*wage;
}
}
class Person{
private String name;
private double setWage;
Person(String name){
this.name = name;
}
String getName() {
return name;
}
void setWage(double setWage) {
this.setWage = setWage;
}
double getSetWage() {
return setWage;
}
}
Output: "Peter Williams gets paid: 155.0"
I have list of Collections elements as below. I wanted to groupBy multiple fields like productCode and then for productpurchase type.
class Product{
private String productCode;
// this is ENUM with 2 possible values "ONLINE" or "INSTORE"
private String productPurchaseType;
private String productCost;
...
}
The possible output should be like
ROW1::ProductCode1, Count of ONLINE,Count of INSTORE,Min, Max
ROW2::ProductCode2, Count of ONLINE,Count of INSTORE, Min, Max
i used below piece of code, but it doesn't give the count of ONLINE and INSTORE
void groupByMerchantMCCCodeZIP(List<Product> productList) {
Map<String, Map<String, List<Product>>> output = transactionDataList.stream()
.collect(Collectors.groupingBy(Product::getProductCode,
Collectors.groupingBy(Product::productPurchaseType)));
System.out.println(output);
}
Can someone who worked on java 8 groupby let me know best way to do this?
Thanks!
To aggregate multiple values, you should write your own Collector, for best performance.
Easiest way to write a Collector is to call the Collector.of() method, in combination with a result collector class. Below is an example.
First, I redefined the Product to have better field types:
class Product {
public enum PurchaseType { ONLINE, INSTORE }
private final String code;
private final PurchaseType purchaseType;
private final BigDecimal cost;
public Product(String code, PurchaseType purchaseType, String cost) {
this.code = code;
this.purchaseType = purchaseType;
this.cost = new BigDecimal(cost);
}
public String getCode() {
return this.code;
}
public PurchaseType getPurchaseType() {
return this.purchaseType;
}
public BigDecimal getCost() {
return this.cost;
}
}
Then the result collector class:
class ProductResult {
private int onlineCount;
private int instoreCount;
private BigDecimal minCost;
private BigDecimal maxCost;
public void add(Product product) {
if (product.getPurchaseType() == Product.PurchaseType.ONLINE)
this.onlineCount++;
else if (product.getPurchaseType() == Product.PurchaseType.INSTORE)
this.instoreCount++;
if (this.minCost == null || product.getCost().compareTo(this.minCost) < 0)
this.minCost = product.getCost();
if (this.maxCost == null || product.getCost().compareTo(this.maxCost) > 0)
this.maxCost = product.getCost();
}
public ProductResult merge(ProductResult that) {
this.onlineCount += that.onlineCount;
this.instoreCount += that.instoreCount;
if (this.minCost == null || that.minCost.compareTo(this.minCost) < 0)
this.minCost = that.minCost;
if (this.maxCost == null || that.maxCost.compareTo(this.maxCost) > 0)
this.maxCost = that.maxCost;
return this;
}
#Override
public String toString() {
return "[online: " + this.onlineCount +
", instore: " + this.instoreCount +
", min: " + this.minCost +
", max: " + this.maxCost + "]";
}
public int getOnlineCount() {
return this.onlineCount;
}
public int getInstoreCount() {
return this.instoreCount;
}
public BigDecimal getMinCost() {
return this.minCost;
}
public BigDecimal getMaxCost() {
return this.maxCost;
}
}
Demo
List<Product> productList = Arrays.asList(
new Product("MILK", Product.PurchaseType.ONLINE, "3.99"),
new Product("MILK", Product.PurchaseType.ONLINE, "3.99"),
new Product("MILK", Product.PurchaseType.INSTORE, "4.95"),
new Product("BREAD", Product.PurchaseType.INSTORE, "7.48")
);
Map<String, ProductResult> result = productList.stream()
.collect(Collectors.groupingBy(Product::getCode,
Collector.of(ProductResult::new,
ProductResult::add,
ProductResult::merge,
Characteristics.UNORDERED)));
result.entrySet().forEach(System.out::println);
Output
BREAD=[online: 0, instore: 1, min: 7.48, max: 7.48]
MILK=[online: 2, instore: 1, min: 3.99, max: 4.95]
I need to implement a mechanism of highlighting duplicated values. Values are edited through delegate depending on the value type (string - line edit, long and big decimal - spin boxes). Currently, I implemented this feature with help of additional class which stores all values and their counts in two "parallel" lists. And after adding a new value I increase its count number (or decrease when repeated value is removed), but this solution seems to be too bulky. Do you guys have any other ideas on highlighting in setModelData(...) method of QItemDelegate?
/**
* Stores a delegates' existing values
*/
private final class DelegateValuesStorage {
private final List<Object> values = new ArrayList<Object>();
private final List<Integer> counts = new ArrayList<Integer>();
....
//Add value or increase a count if exists
public void add(final Object value) {
if(values.contains(value)) {
final int valueIndex = values.indexOf(value);
final int oldCount = counts.get(valueIndex);
counts.remove(valueIndex);
counts.add(valueIndex, oldCount + 1);
} else {
values.add(value);
counts.add(1);
}
}
....
//Decrease a count or remove value if it doesn't exist anymore
public void decreaseCount(final Object value) {
if(value == null) {
return;
}
final int index = values.indexOf(value);
if(index >= 0) {
final int oldCount = counts.get(index);
if(oldCount >= 2) {
counts.remove(index);
counts.add(index, oldCount - 1);
} else {
values.remove(index);
counts.remove(index);
}
}
}
/**
* Delegate
*/
private class ConcreteDelegate extends QItemDelegate {
private final DelegateValuesStorage values = new DelegateValuesStorage();
...
#Override
public void setModelData(final QWidget editor, final QAbstractItemModel model, final QModelIndex index) {
if(editor instanceof ValEditor) { // ValEditor is an abstraction of line edit and spin box over values' data types
final Object value = ((ValEditor) editor).getValue();
model.setData(index, value, Qt.ItemDataRole.UserRole);
final String newData = (value == null) ? "" : String.valueOf(value);
values.add(newData);
final String oldData = (String) model.data(index, Qt.ItemDataRole.DisplayRole);
values.decreaseCount(oldData);
model.setData(index, newData, Qt.ItemDataRole.DisplayRole);
model.setData(index, new QColor(0, 0, 0), Qt.ItemDataRole.ForegroundRole);
redrawItems(model); // runs through values and colors them red if count >= 2; or black if count == 1
} else {
super.setModelData(editor, model, index);
}
}
}
I usually use maps for those kinds of tasks:
private final class DelegateValuesStorage {
private final Map<Object, Integer> values = new HashMap<Object, Integer>();
....
//Add value or increase a count if exists
public void add(final Object value) {
Integer count = values.get(value);
if (count == null) {
values.put(value, 1);
} else {
values.put(value, count + 1);
}
}
....
//Decrease a count or remove value if it doesn't exist anymore
public void decreaseCount(final Object value) {
if(value == null) {
return;
}
Integer count = values.get(value);
if (count == null) {
// decreasing a "new" value - could be an error too
return;
}
if (count <= 1) {
// remove the value from the map
values.remove(value);
} else {
values.put(value, count - 1);
}
}
}
Highlighting now should be enabled if
values.get(value) > 1
is true.