I have a Mentor class in which I have an ID for each mentor and an ArrayList of mentee IDs, like this:
public class Mentor {
int mentorId;
ArrayList<Integer> mentees = new ArrayList<>();
public Mentor(int mentorId, ArrayList<Integer> mentees) {
this.mentorId = mentorId;
this.mentees = mentees ;
}
}
The problem is that some mentees can be mentors too.
I would like to somehow get a count of all of the mentees associated to the top mentor as well as how many mentors are under the top mentor.
So, basically, if a mentor has a mentee, who is also a mentor, then this mentor's mentees are also associated to the top mentor.
So, my thinking was to loop through the mentee-list and see if any of the id's match an ID of Mentor. If true, Add this mentor's list of mentees to a list and loop again, but this will not work dynamically.
My main class looks something like this:
ArrayList<Mentor> mentors = new ArrayList<>();
ArrayList<Integer> mentees = new ArrayList<>();
ArrayList<Integer> mentees2 = new ArrayList<>();
mentees.add(2);
mentees.add(3);
mentees2.add(4);
mentees2.add(5);
//[1,{2,3}]
mentors.add(new Mentor(1, mentees));
//[2,{4,5}]
mentors.add(new Mentor(2, mentees2));
int mentorCount = 0;
int menteeCount = 0;
for (Mentor mentor : mentors) {
for (Integer mentee : mentees) {
mentorCount++;
if (mentee == mentor.mentorId){
mentorCount++;
//add each mentee to arraylist and start the process again, but is there an easier way to do this.
}
}
}
I was wondering if there is a way of solving this, maybe using streams?
I would recommend using good object oriented design, you shouldn't just use integer id's like that, because in this situation you could simply make an ArrayList of Person objects Where Mentees, and Mentors Inherit from Person. Then you can check if a Person is an instance of Mentee versus Mentor:
for (Person p : people) {
if (p instanceof Mentor)
{
// Mentor logic
}
if (p instanceof Mentee)
{
// Mentee Logic
}
}
Firstly, let's briefly recap the task.
You have a set of mentors, each mentor has a collection of mentees. Some of them might happen also to be mentors and so on.
Class design
From the perspective of class design, the solution is pretty simple: you need only one class to describe this relationship - Mentor. And each Mentor should hold a reference to a collection of Mentors (not integer ids).
In your domain model, as you described it, there's no substantial difference between a mentor and a mentee. Mentor points to other mentors - is a simplest way to model that. Mentee is just an edge case, a mentor which has an empty collection of mentors.
You don't need to include classes and features in your application that doesn't bring benefit.
Data structure
From the perspective of data structures, this problem can be described very well with an acyclic disjointed Graph.
Acyclic because if we consider the relationship between mentors when a mentor could indirectly point at themself (i.e. a mentor N has a mentee, with in tern points to another mentee that happens to be also a mentor of mentor N) the task becomes ambiguous. Therefore, I'm making an assumption that no one can be a mentor to himself.
I also depicted this data structure as disjointed because mentors in this model (as well as in real life) can form isolated groups, which in graph theory called connected components. That means that there could be several mentors with the same count of mentee, which happens to be the largest.
Depth first search
In order to find all the vertices (mentors) connected with a particular mentor, we have a classic traversal algorithm, which is called Depth first search (DFS). The core idea of DFS is to peek a single neighbor of the given vertex, then in turn peek one of its neighbors and so on until the hit the vertex that doesn't point to the other vertex (i.e. maximum depth is reached). And then it should be done with every other neighbors of the previously visited vertices.
There are two ways to implement DFS.
Iterative approach
For that, we need a stack. It will store all unvisited neighbors of the previously encountered vertexes. The last vertex from each list of neighbors in every branch will be explored first because it will be placed on the top of the stack. Then it will get removed from the stack and it's neighbors will be placed on the stack. This process repeats in a loop until the stack contains at least one element.
The most performant choice of collection for the stack is ArrayDeque.
Because this approach require continuous modification of the stack by adding and removing vertices, it isn't suitable to be implemented with Stream IPA.
Recursive approach
The overall principle is the same as described above, but we don't need to provide a stack explosively. The call stack of the JVM will be utilized for that purpose.
With this approach, there's also some room to apply streams. For that reason, I've chosen the recursive implementation. Also, its code is probably a bit easier to understand. But keep in mind that recursion has certain limitations, especially in Java, and not suitable for processing a large set of data.
Recursion
A quick recap on recursion.
Every recursive implementation consists of two parts:
Base case - that represents a simple edge-case for which the outcome is known in advance. For this task, the base case is the given vertex has no neighbors. That means menteesCount of this vertex needs to be set to 0 because it has no mentee. And the return value for the base case is 1 because this vertex, in turn, is a valid mentee of another vertex that holds a reference to it.
Recursive case - a part of a solution where recursive calls a made and where the main logic resides.
The recursive case could be implemented using streams and entails recursive invocation of the for every neighbor of the given vertex. Each of these values will contribute to the menteesCount of the given vertex.
The value returned by the method will be menteesCount + 1 because for the vertex which triggered this method call, the given vertex will be a mentee as well as its mentees.
Implementation
Class mentor basically serves as a vertex of the graph. Each vertex has a unique id and collection of adjacent vertexes.
Also, in order to reuse values obtained by performing DFS I've added a field menteesCount which is initially initialized to -1 in order to distinguish between vertices that has no adjacent vertices (i.e. menteesCount has to be 0) and vertices which value wasn't calculated. Every value will be established only ones and then reused (another approach will be to utilize a map for that purpose).
Method getTopMentors() iterates over the collection of vertices and invokes DFS for every vertex which value wasn't calculated yet. This method returns a list of mentors with the highest number of associated mentees
Method addMentor() that takes a vertex id, and id of its neighbors (if any) was added in order to interact with the graph in a convenient way.
Map mentorById contains every vertex that was added in the graph and, as its name suggests, allows retrieving it based on the vertex id.
public class MentorGraph {
private Map<Integer, Mentor> mentorById = new HashMap<>();
public void addMentor(int mentorId, int... menteeIds) {
Mentor mentor = mentorById.computeIfAbsent(mentorId, Mentor::new);
for (int menteeId: menteeIds) {
mentor.addMentee(mentorById.computeIfAbsent(menteeId, Mentor::new));
}
}
public List<Mentor> getTopMentors() {
List<Mentor> topMentors = new ArrayList<>();
for (Mentor mentor: mentorById.values()) {
if (mentor.getCount() != -1) continue;
performDFS(mentor);
if (topMentors.isEmpty() || mentor.getCount() == topMentors.get(0).getCount()) {
topMentors.add(mentor);
} else if (mentor.getCount() > topMentors.get(0).getCount()) {
topMentors.clear();
topMentors.add(mentor);
}
}
return topMentors;
}
private int performDFS(Mentor mentor) {
if (mentor.getCount() == -1 && mentor.getMentees().isEmpty()) { // base case
mentor.setCount(0);
return 1;
}
int menteeCount = // recursive case
mentor.getMentees().stream()
.mapToInt(m -> m.getCount() == -1 ? performDFS(m) : m.getCount() + 1)
.sum();
mentor.setCount(menteeCount);
return menteeCount + 1;
}
public static class Mentor {
private int id;
private Set<Mentor> mentees = new HashSet<>();
private int menteesCount = -1;
public Mentor(int id) {
this.id = id;
}
public boolean addMentee(Mentor mentee) {
return mentees.add(mentee);
}
// getters, setter for menteesCount, equeals/hashCode
}
}
An example of the graph used as a demo.
main() - the code models the graph shown above
public static void main(String[] args) {
MentorGraph graph = new MentorGraph();
graph.addMentor(1, 3, 4);
graph.addMentor(2, 5, 6, 7);
graph.addMentor(3, 8, 9);
graph.addMentor(4, 10);
graph.addMentor(5, 11, 12);
graph.addMentor(6);
graph.addMentor(7, 13, 14);
graph.addMentor(8);
graph.addMentor(9, 16, 17, 18);
graph.addMentor(10);
graph.addMentor(11, 18);
graph.addMentor(12);
graph.addMentor(13);
graph.addMentor(14, 19, 20);
graph.addMentor(15);
graph.addMentor(16, 21, 22);
graph.addMentor(17);
graph.addMentor(18);
graph.addMentor(19);
graph.addMentor(20);
graph.addMentor(21);
graph.addMentor(22);
graph.getTopMentors()
.forEach(m -> System.out.printf("mentorId: %d\tmentees: %d\n", m.getId(), m.getCount()));
}
Output
mentorId: 1 mentees: 10
mentorId: 2 mentees: 10
Use Person and Mentor and Mentee subclasses as suggested by acornTime, define mentees as a list of Person and the information you want becomes simple to get:
import java.util.*;
import java.util.stream.Stream;
public class Main{
public static void main(String[] args) {
ArrayList<Person> mentees = new ArrayList<>();
mentees.add(new Mentee(11));
mentees.add(new Mentee(12));
mentees.add(new Mentor(2, new ArrayList<>()));
mentees.add(new Mentee(13));
mentees.add(new Mentee(14));
mentees.add(new Mentor(3, new ArrayList<>()));
mentees.add(new Mentor(4, new ArrayList<>()));
mentees.add(new Mentor(5, new ArrayList<>()));
Mentor mentor = new Mentor(1, mentees);
System.out.println(mentor.menteesCount());
System.out.println(mentor.mentorsInMentees().count());
}
}
interface Person {
int getId();
}
class Mentor implements Person{
private final int mentorId;
private List<Person> mentees = new ArrayList<>();
public Mentor(int id, ArrayList<Person> mentees) {
mentorId = id;
this.mentees = mentees ;
}
#Override
public int getId() {
return mentorId;
}
public List<Person> getMentees() {
return mentees;
}
public int menteesCount() {
return mentees.size();
}
public Stream<Person> mentorsInMentees(){
return mentees.stream().filter(m -> (m instanceof Mentor));
}
}
class Mentee implements Person{
private final int menteeId;
public Mentee(int id) {
menteeId = id;
}
#Override
public int getId() {
return menteeId;
}
}
Test it online here
You should do something like a depth-first or breadth-first search (*):
Maintain a Set<Integer> containing all the people you have already seen.
Maintain a queue of some kind (e.g. an ArrayDeque), of people you are going to check.
Put the first person (or any number of people, actually) into this queue.
Then, while the queue is not empty:
Take the next person in the queue
If you've already seen them, go to the next item in the queue
If you've not already seen them, put the person into the seen set; add all of their mentees into the queue
That's it. The number of people at the end is the size of the seen set.
(*) Whether you do depth-first or breadth-first search depends on which end of the queue you add mentees to: adding them to the same end that you remove them from results in depth-first search; adding them to the other end results in breadth-first search. If you don't care which, choose either.
I'm trying to find if multiple HashMaps are empty.
To give some context. I have a hashmap declared here.
static Map<Integer, College> tblColleges = new HashMap<Integer, College>();
For each college object:
Map<Integer, Department> tblDepartments = new HashMap<Integer, Department>();
I'm trying to add a major. Majors can only exist as an attribute of Department.
Here's what I have right now.
int numberofColleges = Databases.tblColleges.size();
int emptyColleges = 0;
for(int key: Databases.tblColleges.keySet()) {
if(Databases.getTblColleges(key).tblDepartments.isEmpty()) {
emptyColleges++;
}
}
if(numberofColleges == emptyColleges) {
System.out.println("Invalid. Requires at least 1 department.");
}
I should only be able to create a Major if at least 1 college has a department.
Essentially for each college object that exists in the tblColleges, I'm checking to see if it's department hashmap is empty. If it is empty, then I increment the number of empty colleges.
Afterward, I compare the number of college objects with empty college objects found, if they are equal then I print an error.
I was wondering if there was a better more efficient way to do this, maybe with some function that exists that I'm not familiar with rather than using variables.
Q: Can you do the check "more efficiently"?
A: You could optimize it a bit:
boolean nonEmptyColleges = false;
for (int key: Databases.tblColleges.keySet()) {
if (!Databases.getTblColleges(key).tblDepartments.isEmpty()) {
nonEmptyColleges = true;
break;
}
}
The above short circuits as soon as it finds a College with a Department. That will be a substantial improvement in a lot of cases.
Then, assuming that Databases.tblColleges is a Map:
boolean nonEmptyColleges = false;
for (int college: Databases.tblColleges.values()) {
if (!college.tblDepartments.isEmpty()) {
nonEmptyColleges = true;
break;
}
}
Q: Can you do the check with less code?
A: Using Java 8 streams you could write the last as:
boolean nonEmptyColleges = Databases.tblColleges.values().stream()
.anyMatch(c -> !c.tblDepartments.isEmpty());
(I think ...)
Q: But is this the right approach?
A: IMO, no.
It seems that you intend to do this check each time you add a major. That's not necessary.
Majors can only exist as an attribute of Department.
The key thing that you need to check is that the Department you want to add the major for exists.
If the Department doesn't exist you can't add the major to it.
If the Department does exist you can the major to it, whether or not it is currently a department of a college1.
The bigger point here is that any data model is going to have a variety of data integrity rules / constraints on it. But that does mean that you need to explicitly check all of them each time the model is changed. You only need to check the preconditions for the change (e.g. that the Department exists) and any constraints that could be invalidated by the change.
1 - The "not" case assumes that there may be some other way of finding a Department. It could be a separate table of Department objects, or it could be that you are in the process of creating and building a new Department and haven't added it to its College yet.
I made a simple plugin on actions. I have one issue.
When player press shift and quickly close inventory, he can get item without pay. I need to fix this, what do I need to do?
Code Below:
#EventHandler public void inventoryHandler(InventoryClickEvent e) throws Exception {
for (AuctionInventory[] inventories : inventoryMap.values()){
for (AuctionInventory inventory : inventories){
if (inventory.getInventoryType().equals(AuctionInventoryType.MAIN)) {
if (e.getClickedInventory().equals(inventory.getSource())) {
e.setResult(Event.Result.DENY);
e.setCancelled(true);
if (e.getCurrentItem().getType() != Material.AIR) {
Player p = (Player) e.getWhoClicked();
for (Button button : buttonList){
if (button.getItem().equals(e.getCurrentItem())){
button.doLogic(inventory.getSource(), p);
e.setResult(Event.Result.DENY);
}
}
IProduct product = InventoryUtil.getProductByItem(getProducts().values(), e.getCurrentItem());
if ((product != null)) {
buyProduct(p, product);
}
}
}
}
}
}
}
#Override #Deprecated #SuppressWarnings("all")
public boolean buyProduct(Player p, IProduct product) throws Exception {
if (!product.getSeller().equals(p.getUniqueId())) {
if (Economy.getMoney(p.getName()) < product.getPrice()) {
return false;
}
p.getInventory().addItem(product.getItem());
removeProduct(product);
Economy.setMoney(p.getName(), Economy.getMoney(p.getName()) - product.getPrice()); Economy.setMoney(Bukkit.getOfflinePlayer(product.getSeller()).getName(), Economy.getMoney(Bukkit.getOfflinePlayer(product.getSeller()).getName()) + product.getPrice());
return true;
}
return false;
}
It is hard to say, if your logic related to checking inventory is fine I would say that you missed InventoryDragEvent. You need to implement that event too, and block any interactions related to your inventories.
Also in your ClickEvent you should block interaction if your inventory is open - including clicks inside player inventory, as there are interactions that can move items from opened inventory by clicking inside own inventory.
Also getClickedInventory() can return null.
So you should just check event.getView().getTopInventory() check if it is not a null, and if it is your inventory.
Also your for loop with buttons does not break/return, so even if player will hit a button you will still try to find item to sell, that might cause some issues too.
Also I noticed few others problems here, you should not use .setMoney function if you are using Vault API as this might break compatibility with other plugins, same with checking money by getMoney.
There is special public boolean has(OfflinePlayer player, double amount); method to check if player have enough money, as this function will work well with plugins that allows for negative amounts, or paying in different way.
Then you should take money from player by: public EconomyResponse withdrawPlayer(OfflinePlayer player, double amount); also you should then check response to ensure that it was successful.
p.getInventory().addItem(product.getItem()); what if player does not have enough space in inventory? Note that this method returns Map<Integer, ItemStack> where key is index of item from method argument (as it is varargs, in your case key can be only a 0, as you are only passing single argument) and item that didn't fit into inventory. (note that one part of stack might be added, like 12 from 43 items in stack)
I have a list of sessions expirations time and session timeout value.
So I can get session start time subtracting timeout from expiration time.
I know how to check if two dates is overlapping:
public boolean isOverlapped(LocalDateTime start1, LocalDateTime end1,
LocalDateTime start2, LocalDateTime end2) {
return (start1.isBefore(end2) || start1.equals(end2))
&& (start2.isBefore(end1) || start2.equals(end2));
}
but have no idea how to do this with for list of dates.
In result I want to have list with the longest chain of overlapped(concurrent) sessions.
Appreciate any help!
First off, make a new class that represents these sessions (if you don't have one already):
class Session {
private LocalDateTime start;
private LocalDateTime end;
public boolean isOverlapped(Session other) {
return (start.isBefore(other.end) || start.equals(other.end))
&& (end.isAfter(other.start) || end.equals(other.start));
}
...
}
Your input list will have to be a list of Sessions.
Next, here is an algorithm that does what you asked for; It takes in a list and checks for each element if it overlaps with any other element in the list (except for itself). If that is the case, it puts it in the result list:
public static List<Session> filter(List<Session> in) {
List<Session> result = new ArrayList<>();
for(Session current : in) {
for(Session other : in) {
if(current != other && current.isOverlapped(other)) {
result.add(current);
break;
}
}
}
return result;
}
Here is also an example program: Ideone
The result will be a list containing sessions that were concurrent with any other session.
This is a rather classic problem. You didn't specify if you want the longest session in terms of time or in terms of number of intervals, but they both work the same way.
First sort all your sessions by start time. Then the logic would be the following:
current_chain = []
best_chain = []
for session in sessions_sorted_by_start:
if session doesn't overlap with any session in curent_chain: [1]
update best_chain if current_chain is better [2]
current_chain = []
current_chain.insert(session) [3]
update best_chain if current_chain is better [2]
The idea here is the following: we maintain a current chain. If a new session overlaps with any other session in the chain, we just add it to the chain. If it doesn't overlap with any session in the current chain, then its start is to the right from the end of the furthest session in the current chain, so no other remaining session will overlap with anything in the current chain (since they are sorted by the start date). That means that the current chain is as long as it will ever get, so we can check if it is better than the best chain so far ([2]) based on whichever criteria (time or number of sessions), and reset it.
Now, to make it linear in time it would be cool to do overlap check of a session and a chain at [1] in constant time. This is easily done if for the chain you always maintain the furthest session in it. To do it, when you insert a new session at [3], if its end extends beyond the end of the current furthest session, update the furthest session, otherwise do not. This way at [1] you only need to check the overlap with the furthest session, instead of checking all of them, which makes that particular check constant time, and the entire algorithm linear (if you do not account for initial sorting, which is of course O(n log n)).
I am working on website with games and I have map of players and their virtual tables.
private final ConcurrentMap<Player, List<Table>> tableOfPlayers = new ConcurrentHashMap<>();
and method to remove table
private void removeTable(Player player,Table table) {
if(tableOfPlayers.get(player).size() == 1) {
tableOfPlayers.remove(player);
} else {
tableOfPlayers.get(player).remove(table);
}
}
Is there any good way to solve this check-then-act idiom, because now it isn't thread-safe.
I know that I can synchronize both add and remove method, but I am wondering if it is possible to make it better. The reason why I check if size is equal to 1 is that if player have only one active table and I decide to remove I no longer need this player in my map.