Java Multiple Sets within a Map - java

I am reading from a CSV file which contains data about Hills, all the reading etc works fine but now I want to create a map which associates the "County Name" (A column header in the file) with all the hills that have the county name in it.
I am along the right path, because my code works for the first countyName, but the problem is when my for loop If statement goes to the else statement, I want it to technically create another set (clearing the previous values) and assigning the new hill data with the county name
My output is:
{Perth and Kinross=[16,Knock of Crieff,Perth and Kinross,279.0,56.389329,-3.826973, 3,Creag Uchdag,Perth and Kinross,879.0,56.465278,-4.098107]}
So it has all the hills with "Perth and Kinross" in it. So now my next county name is "Stirling" for example so the map should end up like
{Perth and Kinross=[16,Knock of Crieff,Perth and Kinross,279.0,56.389329,-3.826973, 3,Creag Uchdag,Perth and Kinross,879.0,56.465278,-4.098107], Stirling=[7,Meall Buidhe,Stirling,719.0,56.419004,-4.308645]}
What I'm unsure about is how to create another set without clearing the values stored for Perth and Kinross
My code is:
Map<String, Set<Hill>> hillsByCounty = new HashMap<>();
if (h.getCounty().equals(countyName)) {
hillsByCounty.get(countyName);
currentSet.add(h);
hillsByCounty.put(countyName, currentSet);
} else {
countyName = h.getCounty();
currentSet.clear();
currentSet.add(h);
}
}
return hillsByCounty;
}
Where exactly is it going wrong. I have a feeling it is the clear function but i'm not sure how else i would do this.
My current code prints out:
{Perth and Kinross=[7,Meall Buidhe,Stirling,719.0,56.419004,-4.308645], Stirling=[7,Meall Buidhe,Stirling,719.0,56.419004,-4.308645]}
As its overwriting the set. What would be my workaround?

You reuse the same set. So if your county changes, you clear the set and fill it with the next county. Instead you should create a new set. As long as you reuse the same set, well, you reuse the same set, empty it and fill something else in it. hillsByCounty.put(...) does not clone the set, but just stores a reference.

The value for each entry of the map is the same set so every modification on one set will also appear at the other entries.
You can check for an existing set in the loop body:
for (Hill h : hills) {
Set<Hill> currentSet = hillsByCountry.get(h.getCountry());
if (currentSet == null) {
currentSet = new HashSet<>();
hillsByCountry.put(h.getCountry(), currentSet);
}
currentSet.add(h);
}

Related

Checking if multiple hashmaps are empty

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.

How do I take a "slice" of a List that only has an iterator?

I have a CSV file full of data downloaded from Fitbit. The data inside the CSV file follows a basic format:
<Type of Data>
<Columns-comma-separated>
<Data-related-to-columns>
Here is a small example of the layout of the file:
Activities
Date,Calories Burned,Steps,Distance,Floors,Minutes Sedentary,Minutes Lightly Active,Minutes Fairly Active,Minutes Very Active,Activity Calories
"2016-07-17","3,442","9,456","4.41","12","612","226","18","44","1,581"
"2016-07-18","2,199","7,136","3.33","10","370","93","12","46","1,092"
...other logs
Sleep
Date,Minutes Asleep,Minutes Awake,Number of Awakenings,Time in Bed
"2016-07-17","418","28","17","452"
"2016-07-18","389","26","10","419"
Now, I am using CSVParser from Apache Common's library to go through this data. My goal is to turn this into Java Objects that can turn relevant data into Json (I need the Json to upload into a different website). CSVParser has an iterator that I can use to iterate through the CSVRecords in the file. So, essentially, I have a "list" of all of the data.
Because the file contains different types of data (Sleep logs, Activity logs, etc), I need to get a subsection/sub-list of the file, and pass it into a class to analyse it.
I need to iterate over the list and look for the keyword that identifies a new section of the file (e.g. Activities, Foods, Sleep, etc). Once I have identified what the next part of the file is, I need to select all of the following rows up until the next category.
Now, for the question in this Question: I don't know how to use an iterator to get the equivalent of List.sublist(). Here is what I have been trying:
while (iterator.hasNext())
{
CSVRecord current = iterator.next();
if (current.get(0).equals("Activities"))
{
iterator.next(); //Columns
while (iterator.hasNext() && iterator.next().get(0).isData()) //isData isn't real, but I can't figure out what I need to do.
{
//How do I sublist it here?
}
}
}
So, I need to determine if the next CSVRecord begins with a quote/has data, and loop until I find the next category, and finally pass a subsection of the file (using the iterator) to another function to do something with the correct log.
Edit
I considered converting it first to a List with a while loop, and then sub-listing, but that seemed wasteful. Correct me if I am wrong.
Also, I can't assume that each section will have the same amount of rows following it. They might have similar, but there is also the food logs, which follow a completely different pattern. Here are two different days. Foods follows the normal pattern, but the Food Logs do not.
Foods
Date,Calories In
"2016-07-17","0"
"2016-07-18","1,101"
Food Log 20160717
Daily Totals
"","Calories","0"
"","Fat","0 g"
"","Fiber","0 g"
"","Carbs","0 g"
"","Sodium","0 mg"
"","Protein","0 g"
"","Water","0 fl oz"
Food Log 20160718
Meal,Food,Calories
"Lunch"
"","Raspberry Yogurt","190"
"","Almond Sweet & Salty Granola Bar","140"
"","Goldfish Baked Snack Crackers, Cheddar","140"
"","Bagels, Whole Wheat","190"
"","Braided Twists Honey Wheat Pretzels","343"
"","Apples, raw, gala, with skin - 1 medium","98"
"Daily Totals"
"","Calories","1,101"
"","Fat","21 g"
"","Fiber","13 g"
"","Carbs","202 g"
"","Sodium","1,538 mg"
"","Protein","28 g"
"","Water","24 fl oz"
The easiest way to do what you want is to simply remember that previous category data, and when you hit a new category, process that previous category data and reset for the next category. This should work:
String categoryName = null;
List<List<String>> categoryData = new ArrayList<>();
while (iterator.hasNext()) {
CSVRecord current = iterator.next();
if (current.size() == 1) { //start of next category
processCategory(categoryName, categoryData);
categoryName = current.get(0);
categoryData.clear();
iterator.next(); //skip header
} else { //category data
List<String> rowData = new ArrayList<>(current.size());
CollectionUtils.addAll(rowData, current.iterator()); //uses Apache Commons Collections, but you can use whatever
categoryData.add(rowData);
}
}
processCategory(categoryName, categoryData); //last category of file
And then:
void processCategory(String categoryName, List<List<String>> categoryData) {
if (categoryName != null) { //first category of the file, skip
//do stuff
}
}
The above assumes that a List<List<String>> is the data structure that you want to deal with, but you can tweak as you see fit. I might even recommend simply passing List<Iterable<String>> to the process method (CSVRecord implements Iterable<String>) and handling the row data there.
This can definitely be cleaned up further, but it should get you started.

Bukkit plugin - check if specific data from a file exists, and declare it

I'm having an issue with a bukkit plugin I'm updating. In what I'm doing, when a player uses a bed, it saves the following data to a file named beds.yml:
ThePlayersUsername:
X: (double)
Y: (double)
Z: (double)
world: worldName
When the plugin is initialized, I need to declare a few variables, because they are used later on in the code.
Player p = (Player)sender;
String dn = p.getDisplayName();
dn = ChatColor.stripColor(dn);
double x = (Double) yml.get(dn + ".x");
double y = (Double) yml.get(dn + ".y");
double z = (Double) yml.get(dn + ".z");
World bw = Bukkit.getWorld((yml.get(dn + ".world").toString()));
Location l = new Location(bw, x, y, z);
I can't do this if they don't exist, but, I can't reset to any defaults because new items get added to the config each time someone enters a bed, and all data is user-specific.
How would I go about checking if the player has some data in the config, if not, telling them to sleep in a bed, and when they do have data in the config, how to declare it so they can use a command to teleport using data from the config. Thanks.
Create 2 list or array:
-The online players
-The players in the config
A simple string list is do the magic just store the names. Then compare them. Something like that:
List<String> online_players = new List<String>();
//fill list code here
List<String players_in_config = new List<String>();
//fill list code here
List<String> output = new List<String>();
for(String s : online_players){
for(String a : players_in_config){
if(s.equals(a)){
output.add(s);
break;
}
}
}
//and now output contains the names of the players that online and have a bed in the config
You are going to want to use a Map here. Maps are a data structure that creates key-value pairs for your data. You can ask a Map for the data associated with a given key and you will get the value you put into it, or null if they key was not found.
Your map would look something like this:
private Map<Player, Location> bedLocations = new HashMap<Player, Location>();
We are going to store this data inside your JavaPlugin class since it needs access inside your listener and command handler. You will also need a way to get this Map from within your listener. An accessor method will do the job here. See the below method for how to do this. You will need to do it later as well.
public Map<Player, Location> getBedLocations() {
return bedLocations;
}
In case you were wondering, a HashMap is a type of map that used the int hashCode() method to quicken key value access, but that's a more high level topic that's irrelevant to the issue at hand.
That map should store a key-value pair of all Players currently online that have a bed location associated with them. Since you don't need someone's info until they come online, you shouldn't load out of the configuration yet, but you should still open the configuration like so to prevent reading the file every time. This should be done in the onEnable method.
//Near your map and other class variables
private FileConfiguration bedConfiguration;
//Inside your onEnable
bedConfiguration = YamlConfiguration.loadConfiguration(new File(PLUGIN_VARIABLE.getDataFolder(), "beds.yml"));
Don't forget to store that bedConfiguration in a class variable. Also create an accessor method for it. I would name it getBedConfiguration().
Now you have a loaded configuration and a place to store your data, what next? You need to populate your Map with player data when they join.
Inside your listener, you should write a method to listen for the PlayerJoinEvent. Inside here you will load your player data.
Firstly, we need to get the YML file from our JavaPlugin class.
FileConfiguration bedConfig = plugin.getBedConfiguration();
That will get you the FileConfiguration that you opened up earlier. Now we check to see if the player's data is in the config.
if (bedConfig.isSet(event.getPlayer().getName()) {
//Next section here
}
If the isSet method returns true, then something exists there. Now we load the data.
String playerName = event.getPlayer.getName(); //You can do this above the if statement if you wish.
double x = (Double) bedConfig.get(playerName + ".x");
double y = (Double) bedConfig.get(playerName + ".y");
double z = (Double) bedConfig.get(playerName + ".z");
World bw = Bukkit.getWorld((bedConfig.get(dn + ".world").toString()));
Location loc = new Location(bw, x, y, z);
Your data is loaded! Excellent. Now we store it in the map for later access. First, get the Map from the JavaPlugin class. Then we put our data into the Map.
Map<Player, Location> map = plugin.getBedLocations();
map.put(event.getPlayer(), loc);
The data is in the map! To access it, make sure you get the map first and then call map.get(PLAYER);
Other things that need to be done are... saving the data when a player leaves / the server shuts down and removing the player from the map when they leave (Keeping players in a map is a very bad practice, you can use their name or UUID as a key if you wish to prevent that issue).
To save your Map to the config, you can either change the config whenever a bed location changes, or you can wait and loop over all the Map entries and save them all at the end. To loop over these you can use an advanced for loop.
for(Player player : map.keySet()) {
Location loc = map.get(player);
//save here
}
This will loop over all players in the map and let you access their location with the map.get(player) method.
To remove a player from the map, listen for the PlayerQuitEvent and use map.remove(player); to unload their data. Don't forget to save it first if you don't save it every update.
If you have any questions, please leave a comment. I tried to be very thorough.

Creating a for loop to do a to.String to my array list

Im currently making a shopping store application, I have 6 classes. 1 for products where it defines the fields for products in the store, another for the shopping basket, one for the GUI and the rest for listeners.
I need to be able to run a method that runs through an array list and running the to.String method on it and returning it as String. Here is what I have at the moment,
private ArrayList<OrderItem> basket = new ArrayList<OrderItem>();
public String createArrayText () {
for (int i = 0; i < OrderItem.size(); i++){
if (i == OrderItem.size() - 1){
return ordersText ;
}
}
}
ordersText is a variable I made at the top of my shopping cart class.
This was my first start at it however I'm getting a error on the .size and obviously missing some key components.
One thing Extra is that each item created is added to the array list, each item has a unique order number.
Arrays.toString(basket);
Is that what you're looking for? If not, you need to explain a little better.
You generally speaking loop over a List like this (Java 7, it's called enhanced for loop):
for (TYPE TEMP_NAME : COLLECTION) {
}
That's the overall syntax. TYPE is the type of item in the list, yours are Object's in the given code. TEMP_NAME is the temporary name you want each entry to be referred as. COLLECTION is the list/array/stack/queue or other Collection.
Concretely:
for (Object o : basket) {
// if basket contains 10 entries the line will run 10 times. each new run contains a different object with name o
}
Normally when building strings it's preferred to use StringBuilder. We can skip that as it's "only" performance that you gain from it. We'll do it with a regular String. So:
Create an empty string that will get longer and longer
Loop the collection/array/list
Append each object's .toString() to the string from 1)
e.g.
String myReturnString = "";
for (Object o : basket) {
myReturnString = myReturnString + " // " + o.toString();
}
return myReturnString;
Notes:
Your loop with an index is fine. You can do it that way too, if you want to.
The line of code that appends the string has a " // " separator between each entry. You can pick whatever you like. You can also simplify it to be myReturnString += " // " + o.toString();

Trying to compare a HashSet element with an element in a List

I have a HashSet that I created and this is what it contains. It will contain more later on, this is pasted from standard out when I did a toString on it. Just to show the contents.
foo.toString(): Abstractfoo [id=2, serial=1d21d, value=1.25, date=2012-09-02 12:00:00.0]
INFO [STDOUT] price.toString(): Abstractfoo [id=1, serial=1d24d, value=1.30, date=2012-09-19 12:00:00.0]
I have a List that I also have and I need to compare the two. One of the elements in List is:
Bar.toString(): Bar [id=1d21d, name=Dell, description=Laptop, ownerId=null]
Here is what I am trying to do...
Bar contains all of the elements I want foo to have. There will only be one unique serial. I would like my program to see if an element in the list that is in HashSet contains the id for bar. So serial == id.
Here is what I've been trying to do
Removed code and added clearer code below
I've verified the data is getting entered into the HashSet and List correctly by viewing it through the debugger.
foo is being pulled from a database through hibernate, and bar is coming from a different source. If there is an element in bar I need to add it to a list and I'm passing it back to my UI where I'll enter some additional data and then commit it to the database.
Let me know if this makes sense and if I can provide anymore information.
Thanks
EDIT: Here is the class
#RequestMapping(value = "/system", method = RequestMethod.GET)
public #ResponseBody
List<AbstractSystem> SystemList() {
// Retrieve system list from database
HashSet<AbstractSystem> systemData = new HashSet<AbstractSystem>(
systemService.getSystemData());
// Retrieve system info from cloud API
List<SystemName> systemName= null;
try {
systemName = cloudClass.getImages();
} catch (Exception e) {
LOG.warn("Unable to get status", e);
}
// Tried this but, iter2 only has two items and iter has many more.
// In production it will be the other way around, but I need to not
// Have to worry about that
Iterator<SystemName> iter = systemName.iterator();
Iterator<AbstractSystem> iter2 = systemData .iterator();
while(iter.hasNext()){
Image temp = iter.next();
while(iter2.hasNext()){
AbstractPricing temp2 = iter2.next();
System.out.println("temp2.getSerial(): " + temp2.getSerial());
System.out.println("temp.getId(): " + temp.getId());
if(temp2.getSerial().equals(temp.getId())){
System.out.println("This will be slow...");
}
}
}
return systemData;
}
If N is the number of items in systemName and M is the number of items in systemData, then you've effectively built an O(N*M) method.
If you instead represent your systemData as a HashMap of AbstractSystem by AbstractSystem.getSerial() values, then you just loop through the systemName collection and lookup by systemName.getId(). This becomes more like O(N+M).
(You might want to avoid variables like iter, iter2, temp2, etc., since those make the code harder to read.)
EDIT - here's what I mean:
// Retrieve system list from database
HashMap<Integer, AbstractSystem> systemDataMap = new HashMap<AbstractSystem>(
systemService.getSystemDataMap());
// Retrieve system info from cloud API
List<SystemName> systemNames = cloudClass.getImages();
for (SystemName systemName : systemNames) {
if (systemDataMap.containsKey(systemName.getId()) {
System.out.println("This will be slow...");
}
}
I used Integer because I can't tell from your code what the type of AbstractSystem.getSerial() or SystemName.getId() are. This assumes that you store the system data as a Map elsewhere. If not, you could construct the map yourself here.

Categories

Resources