Iterate over two TreeMap at the same time in Java - java

I have two maps:
Map<Date, List<Journey>> journeyMap = new TreeMap<Date, List<Journey>>
Map<Date, List<Job>> jobMap = new TreeMap<Date, List<Job>>
I used TreeMap because that means they're sorted by date but I want to go through both maps at the same time, get the values of Journey/Job, then do some work.
I think i could use generics, storing the Job/Journey as an Object, then checking the instanceOf but I'm not sure if thats the solution?
Thanks.

Even though the others are right, that there are better, safer and more comfortable ways to achive whatever you want, it is possible to iterate over (the entries of) two Maps (aka Collections) at the same time.
//replace keySet() with your favorite method in for-each-loops
Iterator<Date> journeyIterator = journeyMap.keySet().iterator()
Iterator<Date> jobIterator = jobMap.keySet().iterator();
while(journeyIterator.hasNext() && jobIterator.hasNext()){
Date journeyDate = journeyIter.next()
Date jobDate = jobIterator.next();
//... do whatever you want with the data
}
This code does explicitly, what a for-each-loop can do implicitly for one Collection. It retrieves the Iterator and gets the element from the Collection from it, much like reading a file.

You're making an assumption that these maps are having values sorted in the very same way, but this is definitely not correct. At least if you want to write a logic like this you need to declare the same implementing class as a reference:
TreeMap<Date, List<Journey>> journeyMap = new TreeMap<Date, List<Journey>>
TreeMap<Date, List<Job>> jobMap = new TreeMap<Date, List<Job>>
but believe me you don't want to do it.
You're right! Instead doing 2 maps create 1, holding pair of Job/Journey objects - create a JobJourneyHolder class which holds both objects, this will be a good solution.

Yes, defining a new class for that is definitely the solution, because it composes related objects together, which is very welcomed in OOP. And you should not forget to implement hashCode() and equals() methods to make such classes work properly in Java collections:
public final class JourneyJob {
final Journey journey;
final Job job;
public JourneyJob(Journey journey, Job job) {
if (journey == null || job == null)
throw new NullPointerException();
this.journey = journey;
this.job = job;
}
public int hashCode() {
return Objects.hash(journey, job);
}
public boolean equals(JourneyJob other) {
return other.job.equals(job) && other.journey.equals(journey);
}
}
To add elements to common Map:
Map<Date, List<JourneyJob>> map = new TreeMap<>();
...
if (map.contains(date)) {
map.get(date).add(new JourneyJob(journey, job));
} else {
map.put(date, new ArrayList<>(Arrays.asList(new JourneyJob(journey, job)));
}
...
To retrieve JourneyJob objects:
for (List<JourneyJob> jjList : map.values()) {
for (JourneyJob jj : jjList) {
journey = jj.journey;
job = jj.job;
//... do your work here
}
}
Or, if you use Java 8, this can be done using nested forEach():
map.values().stream().forEach(list ->
list.stream().forEach(jj -> {
Journey journey = jj.journey;
Job job = jj.job;
//... do your work here
})
);

Related

How do manipulate list of objects by date parameters in a hashmap?

I have a hashmap with key and object like
HashMap<String,List<Object,> > profileMap= new HashMap<>();
ArrayList eventList = new ArrayList();
for(Profile profile:Plist) {
> profileMap.putIfAbsent(profile.getprofileID(),eventList );
cpToEvent.get(event.getContact_profile()).add(event);
}
Profile object contains information about different events, event date, and profileID associated with that event.
I need to delete the events of the profile where the gap between two events in a profile is more than 1 yrs.
For that, I need to sort the list so that I can calculate the gap between them before deleting them.
How do achieve this?
If you are trying to have the elements in your List sorted, I recommend using a natively existing type such as a "SortedSet" implementation. E.g. a TreeSet
Map<String, TreeSet<Object>> profileMap = new HashMap<>();
This will have you implementing the Comparator Interface in which you can define to sort by Date.
public class Objekt implements Comparator<Objekt> {
#Override
public int compare(Objekt o1, Objekt o2) {
if (o1.getDate().before(o2.getDate())) {
return -1;
} else if (o1.getDate().after(o2.getDate())) {
return 1;
} else {
return 0;
}
}
More on how to implement that here: Compare Object by dates ( implements Comparator)
You can try to iterate over the HashMap item and filter the element with Date that is older than 1 year.
Given the Profile class as below
public class Profile {
private Date createdAt;
public Date getCreatedAt() {
return createdAt;
}
public void setCreatedAt(Date createdAt) {
this.createdAt = createdAt;
}
}
And our List is
HashMap<String, Profile> profiles = new HashMap<>();
Then we can simply do as below to get the list of Map.Entry that matches your requirement.
List<Map.Entry<String, Profile>> matchProfile = profiles.entrySet().stream().filter(item -> item.getValue().getCreatedAt().getYear() > 2015)
.collect(Collectors.toList());
There are several constraints you should have in mind, mostly regarding modifying your existing objects.
The simplest code that processes your items is this:
Map<String, List<Profile>> profileMap= ...;
profileMap.forEach((k, v) -> {
v.sort(Comparator.comparing(Profile::getDate));
// additional processing on "v" here (v is the value in the Map.Entry, i.e. the list of profiles)
});
But the code above modifies the List which exists in your map.
If you need to preserve the existing List as-is, then instead of sorting v, you should create a new List and then process that.
profileMap.entrySet().forEach(e -> {
List<Profile> profiles = new ArrayList<>(e.getValue());
profiles.sort(Comparator.comparing(Profile::getDate));
e.setValue(profiles);
});
The code above modifies the profileMap, it now maps the original keys to new values.
Again, if that is not ok, and you want to preserve the original profileMap entirely, then in the forEach above you need to fill a new Map instead of setValue-ing the existing entries.
Make sure to focus on solving or improving the overall product, not just on a small piece of the processing. Sometimes, the best way to improving a process is to eliminate some parts of it entirely and adjust the remaining pieces.
Why do you have a huge list of events that is both unsorted and containing obsolete entries? Can you sort the events when receiving them? Or when reading them from the database?

How to store multiple objects from a hashmap that has the same key?

EDIT
I've tried this HashMap with multiple values under the same key, and my hashMap now looks like this HashMap<String, List<Place>> placeMap = new HashMap<>();
Also tried to put Object instead of Place(place is my superclass). But when I now create my subclasses and wants to add them to the HashMap I get:
The method put(String, List) in the type HashMap<String,List<Place>> is not applicable for the arguments (String, NamedPlace)
and
The method put(String, List) in the type HashMap<String,List<Place>> is not applicable for the arguments (String, DescPlace)
here is my adding which created the error:
NamedPlace p = new NamedPlace(x,y,answer,col,cat);
placeMap.put(answer, p);
DescPlace dp = new DescPlace(x,y,answer, desc, col, cat);
mp.add(dp);
placeMap.put(answer, dp);
NamedPlace and DescPlace are both subclasses to Place, and I want them both in the same HashMap..
OP
I'm working on a little project here. The thing is that I need to use a HashMap instead of a ArrayList on this part of the project because HashMap is alot faster for searching. I've created a HashMap like this:
HashMap<String, Object> placeMap = new HashMap<>();
The String is the name of the Object, but the thing is that more than one object can have the same name. So I search for a object in my searchfield and I want to store all those objects that has that name into an ArrayList so I can change info in just them.
The object have alot of different values, like name, position, some booleans etc.
Do I need to create a HashCode method into my object class which shall create a unique hashcode?
When using a standard Map<String, List<YourClassHere>> instance, it is important to remember that the map's values for each entry will be a List<YourClassHere>, and will not handle it in any special way. So in your case, if you have
private Map<String, List<Place>> placeMap = new HashMap<>();
Then to store values you will need to do as follows:
NamedPlace p = new NamedPlace(x,y,answer,col,cat);
List<Place> list = placeMap.get (answer);
list.add(p);
However, this piece of code has some underlying problems.
It doesn't take into account that answer might not be present in placeMap.
It assumes that there's always a List<Place> instance for each key you query.
So the best way to fix those potential problems is to do as follows (Java 7 and later):
NamedPlace p = new NamedPlace(x,y,answer,col,cat);
if (placeMap.containsKey (answer) && placeMap.get (answer) != null) {
placeMap.get (answer).add(p);
} else {
List<Place> list = new ArrayList<Place> (); // ..or whatever List implementation you need
list.add (p);
placeMap.put (answer, list);
}
If you want to scna through the list of places, the code would look like this:
if (placeMap.containsKey (key) && placeMap.get (answer) != null) {
for (Place p: placeMap.get (key)) {
// Do stuff
}
}

TreeMap filtered view performance

I have a class that has (among other things):
public class TimeSeries {
private final NavigableMap<LocalDate, Double> prices;
public TimeSeries() { prices = new TreeMap<>(); }
private TimeSeries(NavigableMap<LocalDate, Double> prices) {
this.prices = prices;
}
public void add(LocalDate date, double price) { prices.put(date, price); }
public Set<LocalDate> dates() { return prices.keySet(); }
//the 2 methods below are examples of why I need a TreeMap
public double lastPriceAt(LocalDate date) {
Map.Entry<LocalDate, Double> price = prices.floorEntry(date);
return price.getValue(); //after some null checks
}
public TimeSeries between(LocalDate from, LocalDate to) {
return new TimeSeries(this.prices.subMap(from, true, to, true));
}
}
Now I need to have a "filtered" view on the map where only some of the dates are available. To that effect I have added the following method:
public TimeSeries onDates(Set<LocalDate> retainDates) {
TimeSeries filtered = new TimeSeries(new TreeMap<> (this.prices));
filtered.dates().retainAll(retainDates);
return filtered;
}
The onDates method is a huge performance bottleneck, representing 85% of the processing time of the program. And since the program is running millions of simulations, that means hours spent in that method.
How could I improve the performance of that method?
I'd give ImmutableSortedMap a try, assuming you can use it. It's based on a sorted array rather then a balanced tree, so I guess its overhead is much smaller(*). For building it, you need to employ biziclop's idea as the builder supports no removals.
(*) There's a call to Collection.sort there, but it should be harmless as the collection is already sorted and TimSort is optimized for such a case.
In case your original map doesn't change after creating onDates, maybe a view could help. In case it does, you'd need some "persistent" map, which sounds rather complicated.
Maybe some hacky solution based on sorted arrays and binary search could be fastest, maybe you could even convert LocalDate first to int and then to double and put everything into a single interleaved double[] in order to save memory (and hopefully also time). You'd need your own binary search, but this is rather trivial.
The view idea is rather simple, assuming that
you don't need all NavigableMap methods, but just a couple of methods
the original map doesn't change
only a few elements are missing in retainDates
An example method:
public double lastPriceAt(LocalDate date) {
Map.Entry<LocalDate, Double> price = prices.floorEntry(date);
while (!retainDates.contains(price.getKey()) {
price = prices.lowerEntry(price.getKey()); // after some null checks
}
return price.getValue(); // after some null checks
}
The simplest optimisation:
public TimeSeries onDates(Set<LocalDate> retainDates) {
TreeMap<LocalDate, Double> filteredPrices = new TreeMap<>();
for (Entry<LocalDate, Double> entry : prices.entrySet() ) {
if (retainDates.contains( entry.getKey() ) ) {
filteredPrices.put( entry.getKey(), entry.getValue() );
}
}
TimeSeries filtered = new TimeSeries( filteredPrices );
return filtered;
}
Saves you the cost of creating a full copy of your map first, then iterating across the copy again to filter.

How to check if a string is in an arrayList inside a hashmap

So I have a hashmap
HashMap<String, ArrayList<String> gMap = new HashMap<String, ArrayList<String>();
And when someone creates a group, the group leader is added to the key of the hashmap and then all the users inside the group are added to the arraylist
gMap.get(groupLeader).add(user);
I'm trying to make it so that only group leaders are allowed to invite players, but if a player is not part of any group and invites another user then a group is automatically created and the player becomes the group leader.
So, normally I would just do this
for(ArrayList<String> list : gMap.values()){
if(list.contains(user)){
//do something since the player is not part of the list
}
}
But I cannot do that since there could be multiple arrayLists, so even though the user is not part of one arrayList it does not mean that they aren't inside another.
So I'm curios how I would check all the arrayLists and do something only if the user is not part of any of them.
You're actually creating a Multimap (a mapping of keys to collections of values) data structure here, and you'll find if you use one directly you won't have to reinvent the wheel as you are now. Guava defines a very nice Multimap interface, including an ArrayListMultimap that stores the data you want, and has a containsValue() method that cleanly does what you need. As mentioned by others, contains checks against a list are slow, you could use HashMultimap to do these contains checks more efficiently if you don't actually care about order, or LinkedHashMultimap if you really do.
And if you aren't already using Guava, you're missing out - it provides countless excellent utilities and good practices.
Use a boolean value that you only change the value of if a list contains the user - and break out of the loop if it does.
boolean notInAnyList = true;
for(ArrayList<String> list : gMap.values()){
if(list.contains(user)){
notInAnyList = false;
break; // No point in iterating any further
}
}
if (notInAnyList) {
// Handle user not being in any of the lists here
}
So here is a code sample
boolean userIsPartOfGroup = false
for(ArrayList<String> list : gMap.values()){
if(list.contains(user)){
userIsPartOfGroup = true;
break;
}
}
if(!userIsPartOfGroup){
gMap.add(user).add(new ArrayList(user));
}
First, I would use a set instead of a list (Java 7 syntax):
Map<String, Set<String> gMap = new HashMap<>();
When I understand your goal right, this may be a solution:
if (isLeader(groupLeader, gMap)) {
gMap.get(groupLeader).add(user);
} else if (isMember(groupLeader, gMap)) {
throw new UnsupportedOperationException("Member " + groupLeader + " is not a leader and must not invite " + user);
} else {
addNewGroup(groupLeader, gMap).add(user);
}
Here are the helper methods:
private boolean isLeader(String player, Map<String, Set<String> gMap) {
return gMap.keys().contains(player);
}
private boolean isMember(String player, Map<String, Set<String> gMap) {
for (Set<String> members : gMap.values()) {
if (members.contains(player)) {
return true;
}
}
return false;
}
private Set<String> addNewGroup(String leader, Map<String, Set<String> gMap) {
Set<String> players = new HashSet<>();
gmap.put(leader, players);
return players;
}

Java Collections

I need to create a Hallway class which will have inside 2 ArrayLists of Stand objects , one for the stands on the right, and the other for the ones on the left.
My intention is to put these ArrayLists inside another collection in this class.
I don't know if I should use a Hashtable, a Map, etc.
More importantly, my intention is to then access these ArrayLists using a method like:
TheHashTable["Right"].add(standObject); // Add a Stand to the Right Stands ArrayList which is inside a Hashtable.
Example:
public class Hallway {
private Hashtable< String, ArrayList<<Stand> > stands;
Hallway(){
// Create 2 ArrayList<Stand>)
this.stands.put("Right", RightStands);
this.stands.put("Left", LeftStands);
}
public void addStand(Stand s){
this.stands["Right"].add(s);
}
}
Would this be possible?
It is possible, but I would advise against it. If you have only two stand locations, it would be much and clearer to simply have two variables of type List<Stand>: leftStands and rightStands, and to have corresponding methods: addLeftStand(Stand), addRightStand(Stand), etc. The code would be much clearer, simpler and safer.
If you really want to go your way, the keys of the map shouldn't be Strings. The caller wouldn't know which key to pass to your methods (there are an infinity of Strings), and ven if he knows that the keys are "Right" and "Left", he could make a typo which would go unnoticed by the compiler. You should use an enum instead, which would make the code self-documented and safer:
public enum Location {
LEFT, RIGHT
}
private Map<Location, List<Stand>> stands = new HashMap<Location, List<Stand>>();
public Hallway() {
for (Location location : Location.values()) {
stands.put(location, new ArrayList<Stand>());
}
}
public void addStand(Location location, Stand stand) {
stands.get(location).add(stand);
}
if you only have right and left, you could for example just create 2 array lists.
private ArrayList<Stand> rightStands;
private ArrayList<Stand> leftStands;
If I understood your question clearly, then this is what you want:
public void addStand(Stand s){
this.stand.get("Right").add(s);
}
But a better approach would be to use Map instead of Hashtable.
public class Hallway {
private Map< String, ArrayList<<Stand> > stands;
private List<Stand> RightStands;
private List<Stand> LeftStands;
Hallway(){
stands = new HashMap();
RightStands = new ArrayList();
LeftStands = new ArrayList();
this.stands.put("Right", RightStands);
this.stands.put("Left", LeftStands);
}
public void addStand(Stand s){
this.stands.get("Right").add(s);
}
}
You need a multi-map, for example from Commons Collections or Guava.
Those will let you map multiple values (Stand1, Stand2, ...) to a single key (e.g. "right").
For example (with Commons Collections):
MultiMap stands = new MultiHashMap();
stands.put("left", new Stand());
stands.put("left", new Stand());
stands.put("right", new Stand());
stands.put("right", new Stand());
stands.put("right", new Stand());
Collection standsOnLeftSide = (Collection) stands.get("left");
I think though that Guava is preferrable because it supports Generics.
Don't use HashTable. It has been deprecated long back. Use TreeMap or HashMap.
List<Stand> right=new ArrayList<Stand>(),left=new ArrayList<Stand>();
Map<String,List<Stand> > stands= new HashMap<String, List<Stand> >();
stands.put("right",right);
stands.put("left",left);
To learn about maps and to decide which Map suits you best read Precisely Concise: Java Maps

Categories

Resources