I have a fleet. fleet.java
Each fleet has a set of ships. ship.java
Each ship has a set of crewmembers. crewmember.java
Within Main I am adding Ships to the fleet and Crewmembers to the ships, read in from a roster of both.
From the first file I add a new ship to an arraylist.
From the second file I add a new crewmember to an arraylist.
this.addships(ship)
this.shipname.addcrewmember(crewmember)
How do I change the shipname in the above statement based on the token from the input file?
public void loadStarships( String filename ) {
File file = new File( filename );
Scanner scan;
try {
scan = new Scanner( file );
while( scan.hasNextLine() ) {
String line = scan.nextLine();
String[] tokens = line.split(",");
Starship star = null;
star = new Starship( tokens[0], tokens[1], tokens[2]);
this.addStarship( star );
}
scan.close();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void loadCrew( String filename ) {
File file = new File( filename );
Scanner scan;
try {
scan = new Scanner( file );
while( scan.hasNextLine() ) {
String line = scan.nextLine();
String[] tokens = line.split(",");
CrewMember member = null;
member = new CrewMember( tokens[0], tokens[1], tokens[2], tokens[4]);
this.tokens[3].addCrewMember( member );
}
scan.close();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public Starship( String name, String registry, String level) {
this.name = name;
this.registry = registry;
this.level = level;
this.crewmembers = new ArrayList<CrewMember>();
}
//adds new crew members
public void addCrewMember( CrewMember crewmembers ) {
this.crewmembers.add( crewmembers );
this.numberOfCrewMembers++;
}
public CrewMember( String name, String position, String rank, String species ) {
this.name = name;
this.position = position;
this.rank = rank;
this.species = species;
}
//set the fleet name and the maximum number of starships
public Fleet(String name) {
this.name = name;
this.starships = new ArrayList<Starship>();
}
The first rule of software design is "use the correct data structure for the job." As it stands, you're saving instances of ship to an ArrayList, which only allows you to retrieve elements by index.
Although you didn't provide the sample data requested, it's easy enough to infer that token[3] is the name of the ship to which the crew member belongs, so you need to figure out how to retrieve an object by name.
Luckily, Java provides you with the HashMap, which stores key-value pairs. You can use the ship's name as a key. When you pass that key to the HashMap, it'll return the value associated with that key -- in your case, the instance of ship that you need.
1) Make sure your class imports java.util.HashMap;
2) Declare your HashMap. Instead of:
ArrayList<Starship> ships = new ArrayList<Starship>();
use:
HashMap<String, Starship> = new HashMap<String, Starship>();
In this situation, a String representing the name of the ship is the key. The instance of ship with that name is the value.
3) When adding a new ship, you're going to have to update your addStarship() method to take a String arg in addition to the Starship arg it already takes:
public void addStarship(String name, Starship ship)
4) In the body of addStarship() you have to add the ship using the put() method:
ships.put(name, ship);
Remember, here, ships is the instance of HashMap that you're using to store your ships. name is a String representing the name of the ship, and ship is the ship object.
5) When you're trying to retrieve the ship, you use the get() method:
ships.get(name);
In your case, the code might look like this:
ships.get(tokens[3].addCrewMember(member));
Keep in mind that with HashMap, you can no longer retrieve values by index like you could with ArrayList. HashMap is by definition, and unordered collection, meaning the first element of the structure may or may not be the the first object you added, and even if you accessed an object at the first position, there's no guarantee that it'll still be in that slot if you try to access it later -- its position in the HashMap may have changed, so you'll typically only be retrieving values using the key.
Please make sure you read the official documentation that I linked, above.
Related
I'm working on a program for my Java class where I'm using a file of objects (clothing items) that represents inventory for a store. Each Retail_Item has four attributes: int itemNumber, String description, int numInInventory, and double price.
What I'm trying to figure out is how to read in each line from the file and turn each line into an object. My first thought was to create a while loop with vars like currentItemNumber, currentDescription, etc. So I tried this:
while (file.hasNextLine()) {
currentItemNumber = file.nextInt();
currentDescription = file.next
} // end while
But I got stuck there because every other time I've read in a String to a Scanner, I've always used nextLine. Can't use that here though, because each line contains multiple attributes of the object, not a String within a line. Is there a way to do this in the structure I'm trying to use, or should I be doing this a different way? I know I've seen and done some things where I parsed a String into separate pieces which I've seen people refer to as "tokens." Would people recommend reading each line in and then parsing it into separate tokens, then assigning each token to its appropriate attribute? Then I guess I'd have to cast those tokens into the appropriate object, since I think reading the whole line in and then parsing it would make each piece a String.
Here's a sample of what's in the text file (which can't be changed in any way, per the professor's instructions):
1000 Pants 10 19.99
2000 Jeans 2 25.95
3000 Shirt 12 12.50
Thanks in advance for your sage wisdom if you've got it.
The following code fulfills your requirement as stated in your question, namely how to create an instance of class RetailItem from a line of text from your text file. I presume it uses things that you may not have learned yet, like class Paths and try-with-resources. This is just used to scan through your file.
First, class RetailItem contains the members you described in your question. Next, I wrote a constructor for class RetailItem that creates a new instance and initializes the instance members. Then I wrote a toString() method that displays the contents of a RetailItem object in "human readable" form. Finally a main() method that reads your text file (which I named "clothes.txt"), line by line - using a Scanner. For each line read, the code splits it using a delimiter which consists of at least one whitespace character. (I presume you haven't yet learned about regular expressions in java.) Then I convert the elements of the String array returned by method split() into appropriate data types that are required by the RetailItem constructor. Then I call the constructor, thus creating an instance of class RetailItem (as you requested) and I print the created instance.
import java.io.IOException;
import java.nio.file.Paths;
import java.util.Scanner;
public class RetailItem {
private static final int FIELDS = 4;
private int itemNumber;
private String description;
private int numInInventory;
private double price;
public RetailItem(int itemNumber, String description, int numInInventory, double price) {
this.itemNumber = itemNumber;
this.description = description;
this.numInInventory = numInInventory;
this.price = price;
}
#Override // java.lang.Object
public String toString() {
return String.format("%4d %-5s %2d %2.2f", itemNumber, description, numInInventory, price);
}
public static void main(String[] args) {
try (Scanner file = new Scanner(Paths.get("clothes.txt"))) {
while (file.hasNextLine()) {
String record = file.nextLine();
String[] fields = record.split("\\s+");
if (fields.length == FIELDS) {
int itemNumber = Integer.parseInt(fields[0]);
String description = fields[1];
int numInInventory = Integer.parseInt(fields[2]);
double price = Double.parseDouble(fields[3]);
RetailItem item = new RetailItem(itemNumber, description, numInInventory, price);
System.out.println(item);
}
}
}
catch (IOException xIo) {
xIo.printStackTrace();
}
}
}
I think the way that I would do is, like you said, parse each line into separate strings and then assign each piece to instance variables of the object you are building.
I have done something like this before, maybe it can be helpful.
Scanner fileScan;
File babyNameFile = new File("yob2015.txt");
try {
fileScan = new Scanner(babyNameFile);
} catch (FileNotFoundException e) {
System.out.println("File does not exist");
return;
}
String currentLine;
int numberOfGirlsNames = 0;
while (fileScan.hasNextLine()) {
String[] values;
currentLine = fileScan.nextLine();
values = currentLine.split(",");
if (values[1].equals("F")) {
numberOfGirlsNames = numberOfGirlsNames+1;
}
}
System.out.println("Number of female names was "+numberOfGirlsNames);
So I am reading from a file with scanner it has the similar format:
title, name, age
Mr, Matthew, 20
mr, Paul, 30
miss, Anne, 24
CSV^
class person{
String name, title;
int age;
public crimeData(String csv){
String[]list = csv.split(",", -1);
name = list[0];
title = list[1];
age = list[2];
}
}
Console Program
Scanner input = new Scanner(System.in);
System.out.println("Please select what data you want to load:");
String selection = input.next();
int temp = 0;
for(int i=0; i< header.length; i++){
if(header[i].equals(selection)){
temp = i;
break;
}
}
temp will give us the index of the option specified so if it is 2 we will want to access the age property
When my console application runs I prompt them(the user) for the data that they want.
So they may enter "age" So I am lost on how I may take this "age" String and access the person object with it.
The ideal case for the program output should be: 20,30,24 going through each age and printing
I take their input so String input = scanner.nextLine();
Then I loop through my array of person objects to get the index of the input. Once I have this index I then want to access the property of person at the index. So like if my index was 1 I would want to access the property 'name'.
In javascript I could take the string and say person['age'] although java's a whole different story. I have looked into java's "reflection API" although it's a heavy learning curve.
I have looked into java's "reflection API" although it's a heavy learning curve.
Well, Reflection is the way to go. It's widely used in many frameworks.
But perhaps a simpler solution will fit your needs. Use a switch to decide which attribute to return, and encapsulate this in a method of the Person class:
class Person {
private String name, title;
private int age;
public loadData(String csv){
String[] list = csv.split(",");
name = list[0];
title = list[1];
age = Integer.parseInt(list[2]);
}
public Object attribute(String attribute) {
switch (attribute) {
case "name": return this.name;
case "title": return this.title;
case "age": return this.age;
default: throw new RuntimeException("Invalid attribute: " + attribute);
}
}
}
Encapsulating the switch inside the method is in line with OOP principles, since it hides how attributes are stored from other objects, only exposing an interface to query them. Reflection breaks all encapsulation.
Though in general I am not in favor of using Map for holding fields for an object, if the number of properties is large and could even potentially vary across CSV files (e.g., some file has the University a person attended, another does not), then using a Map to hold the properties might be appropriate.
In this case, one would define a simple Person class:
public class Person {
Map<String, String> props = new HashMap<>();
public void addProperty(String propertyName, String value) {
// could add error checking to ensure propertyName not null/emtpy
props.put(propertyName, value);
}
/**
* returns the value of the property; may return null
*/
public String getProperty(String propertyName) {
return props.get(propertyName);
}
}
If it is know that certain attributes/properties will always be loaded, then accessors such as getName() could be added:
public String getName() {
return props.get("name");
}
public int getAge() {
String age = props.get("age");
// or throw exception if missing
return (age != null ? Integer.parseInt(age) : -1);
}
Though note I would expect name to not be a single entry for most datasets, as there typically would be last name, first name, etc. Nonetheless, the pattern for a limited number of commonly expected values is the same. Also, you can adapt so that you could get integer values directly for certain well-known fields.
Then, when you parse the file, you keep the title row that has the attribute definitions. Then for each row that you subsequently read, you create a new Person object, and then add the properties in order.
List<Person> allPersons = new ArrayList<>();
while ( (line = READ_NEXT_LINE) ) {
// NOTE: this is not a safe way to handle CSV files; should really
// use a CSV reader as fields could have embedded commas
attrs[] = line.split(",");
Person p = new Person();
for (int i = 0; i < titleRow.length; ++i) {
p.addProperty(titleRow[i], attrs[i]);
}
allPersons.add(p);
}
You can then get a specific Person by Person myPerson = allPersons.get(index_of_person), and much akin to the way you would have used Javascript, you can do String val = myPerson.getProperty("age").
If you need to search by a given attribute, you can then stream/loop over the allPersons and check of equivalence based upon a given property.
// find all people of a given age
List<Person> peopleAge20 = allPersons.stream()
.filter(p -> p.getAge() == 20)
.collect(Collectors.toList());
System.out.println(peopleAge20);
// summary statics (average age) for all people
IntSummaryStatistics stats =
allPersons.stream().mapToInt(p -> p.getAge()).summaryStatistics();
System.out.printf("Average age: %f\n", stats.getAverage());
Note that this approach does break the idea of a Javabean, but that may or may not be an issue depending upon your requirements.
First thing, we should add a constructor to your Person class.
class Person {
public Person(String name, String title, int age) {
this.name = name;
this.title = title;
this.age = age;
}
}
Now while you read the input you can use a Map as follows. Here after reading each line, we create a Person object and then using that person's age we make an entry in the map with key as age and value as Person.
Map<Integer, Person> mapOfPeople = new HashMap<>();
while (input.hasNextLine()) {
String line[] = input.nextLine().split(",");
Perso person = new Perso(line[1], line[0], Integer.parseInt(line[2].trim()));
mapOfPeople.put(person.getAge(), person);
}
Now to fetch a particular Person by age just do
mapOfPeople.get(20);
Say I have a .txt file that has information being split by a comma as such:
IN,Indiana,6634007
While this is a snippet which accesses that file and splits it:
for(int i=0; i < count; i++) {
line = bufferedReader2.readLine();
String space[] = line.split(",");
String abb = space[0];
String nme = space[1];
int pop = Integer.parseInt(space[2]);
states[i] = new State(abb, nme, pop);
}
The purpose of that was so that all the information in the txt file could be accessed, so for example this code would print exactly whats present on the .txt file:
System.out.println(states[0]);
would print:
IN,Indiana,6634007
My question is, how would I have it so that I can access the specific part of the array as in how would I print lets say just the name "Indiana" or the population "6634007"?
P.S I'm sorry if the title of my question did not make sense, I did not exactly know how to word it.
Somewhere, you have a class called State. states is an Array of this class. So you can add a getter to State:
public int getPop() {
return pop;
}
And call it on your Object like this:
System.out.println(states[0].getPop());
as states[0] is simply a State object.
Add more getters to access different fields.
if you just want to print every single line, you can try this like below:
public static void main(String[] args) throws Exception {
BufferedReader reader = new BufferedReader(new FileReader("data.txt"));
String line = null;
List<String> list = new ArrayList<String>();
while((line = reader.readLine()) != null ) {
list.add(line);
}
System.out.println(list.get(0));
// TODO realease resources
}
From your question what i can realise is, you are using State class to store the information. In such case, check the state class where the first parameter value is stored. Later to print the corresponding information, access its object variable as SapuSeven mentioned.
For eg.
public class State{
public String a;
public String b;
public int c;
public State(String x, String y, int z){
a=x;
b=y;
c=z;
}
}
now u can access like
System.out.println(states[0].b);
for printing the name of city
OR
you can simply print the value using index like this
System.out.println(states[0].split(",")[2]);
So basically I have a class with a constructor, especifically saying, a constructor for states. I can only create a new State if in the constructor I have a city type region(State has 2 constructors, both accept name and Region).
The code looks like this:
public State(String Name, int Population, Region Capital) throws IllegalArgumentException {
super(Name, Population);
if(!(Capital.getRegionType() == RegionType.CITY)){
System.out.println("ERROR");
throw new java.lang.IllegalArgumentException();
}
this.Capital = Capital;
}
The type of the region is defined using a Enum class. The problem is when I create a new function in the test class(main class)
Region3 = new Region("BedRock", 23423,RegionType.VILLAGE); //
try{
State s2 = new State("Khemed", Region3); // This should not be possible because Region3 is a VILLAGE instead of a CITY
}catch(IllegalArgumentException e){
}
s2.addRegion(Region2);//Doesn´t work - Error
s2.addRegion(Region1);//Doesn´t work - Error
That's basically my problem, the 2 last lines gives me a error saying I haven´t intialized the variable s2.
I tried running the code without "try and catch" to see if the if statement I have in the constructor would work but it doesn´t.
The method "add" is a method defined in the super class of state (Contry) which simply allows to add States to it.
Hope you can help because I can´t see how I can solve this.
ADDED (Requested to answer my question)
Class Region:
public class Region {
private String name;
private int population;
private RegionType regionType;
public Region(String name, int population, RegionType regionType){
this.name = name;
this.population = population;
this.regionType = regionType;
}
//get Region Type
public RegionType getRegionType(){
return regionType;
}
Enum with Region Type:
public enum RegionType {
CITY,
VILLAGE,
TOWN,
}
Your variable s2 is only defined in your try/catch block, try to put the 2 lines in the try/catch block.
But the best approach here is to avoid catching the exception IllegalArgumentException.
Region3 = new Region("BedRock", 23423,RegionType.VILLAGE); //
try{
State s2 = new State("Khemed", Region3);
s2.addRegion(Region2);
s2.addRegion(Region1);
}catch(IllegalArgumentException e){
}
First of all you must put 2 lines in to try/catch block. And your "State" constructor has 3 paramethers. But you are using another constructor(2 params):
State s2 = new State("Khemed", Region3); // 2 params
public State(String Name, int Population, Region Capital)// 3 params
Use with population param like this in try block:
Region3 = new Region("BedRock", 23423,RegionType.VILLAGE); //
try{
State s2 = new State("Khemed", 123,Region3);
s2.addRegion(Region2);
s2.addRegion(Region1);
}catch(IllegalArgumentException e){
}
In the second code block, s2 only has scope within the try block. Instead, declare s2 outside of the try.
Edit: That's what I get for posting too quickly. s2 statements should be in the try.
Region3 = new Region("BedRock", 23423,RegionType.VILLAGE);
State s2 = null;
try{
s2 = new State("Khemed", Region3);
s2.addRegion(Region2);
s2.addRegion(Region1);
}catch(EmptyStackException e){ //You should log this or something
}
First of all, id like to thank this fourm, as I am finding myself quickly improving through all the material on this forum and all the help different members have been giving me. So this is just a big thank you for all of that. As for my question, I've been experimenting around with input out and wanted to see if this logic would work. I am trying to get the appropriate things in their appropriate array, and wanted to see if this logic would do it. Currently (and for a while) I wont be in a place where I can access any Virtual IDE effectively so all this was kinda done on the fly using notepad, word etc. *So don't be to hard on my syntax. What I am mostly concerned about is the logic (if it would work) and to a lesser mistake any major mistakes in code.*
Thanks alot.
So basically, the text file goes like this. Title, one line of space, then name, age and wage and the separator is the #. Then right below that, name, age and wage the separator bring # etc etc.
(pretend there was no line spaces between Bobby, Sandy, Roger, Eric and David..so pretend in the txt file they are right under each other, but there is a gap in between information and bobby.
Information
Bobby#24#5.75
Sandy #19#10.22
Roger #27#6.73
Eric#31#8.99
David#12#3.50**
Here is the logic i've come up with.
public class Practice {
public static void main(String[] args) throws Exception {
String Name [] = new String [5];
int Age [] = new int [5] ;
double Wage [] = new double [5];
String Blank [] = new String [5];
FileReader inputfile = new FileReader (new File(info.txt));
BufferedReader InputBuffer = new BufferedReader (inputfile);
String Title = InputBuffer.readline (); // to get the title
int count = 0;
while (InputBuffer.readline() = null) { // this while loop grabs the blank under the title
Blank [count] = count;
}
int i = 0;
while (InputBuffer.readline() !=null) {
String Getter = InputBuffer.readline (); // reads line
String splitup= Getter.split(#); // splits it
Name [i] = splitup[i]; // puts name in this array
Age [i] = splitup([i] + 1); // age in this array
Wage [i] = splitup([i] + 2); // wage in this array
}
InputBuffer.close();
}
}
Would this logic work for storing the title in the title String, the Blank line under the Blank Array, the name under the name array, age under the age array and the wage under the wage array??
Thanks alot.
P.S: Mostly concerned about the last while loop, I want to know if it will put the name in the name array, the age in the age array and the wage in the wage array.
First of all, you only need one while-loop. I don't understand why you have two, especially since the conditional in the first is nonsensical ( InputBuffer.readline() = null ).
Your loop would look something like this:
boolean isTitleParsed = false;
String title;
String line;
while ( (line = inputBuffer.readLine()) != null ) {
if (!isTitleParsed) {
// this is the title
title = line;
isTitleParsed = true;
} else if (line.isEmpty()) {
// this is a blank line; logic for dealing with blank lines here
...
} else {
// this is actual person data
String[] personData = line.split("#");
if (personData != null && personData.length == 3) {
String name = personData[0];
String age = personData[1];
String wage = personData[2];
...
}
...
}
}
Secondly, I think using arrays is entirely the wrong way to go. Like #AVD mentioned in his comment on the OP, List<T> and a POJO is probably a much better solution -- and much more extensible.
And finally: no, as you've written it, your second loop will not successfully save the name, age, and wage to the arrays. You never increment i and the syntax splitup([i] + 1) is just wrong. (You probably meant splitup[i+1].)
Using Arrays
If you're really stuck on using arrays to save your data, you'd have to do in something like this:
String[] names = new String[5];
String[] ages = new String[5];
String[] wages = new String[5];
...
int index = 0;
while ( (line = inputBuffer.readLine()) != null && index < 5) {
if (!isTitleParsed) {
...
} else if (line.isEmpty()) {
...
} else {
// this is actual person data
String[] personData = line.split("#");
if (personData != null && personData.length == 3) {
String name = personData[0];
String age = personData[1];
String wage = personData[2];
names[index] = name;
ages[index] = age;
wages[index] = wage;
index++;
} else {
System.err.println("Line " + line + " is malformed and was not saved.");
}
...
}
}
Notice that index is instantiated at 0, but is incremented every time we save something to the arrays. This way names[0] will hold the first name, names[1] will hold the second, and so on.
Notice also that we save a given record's name, age, and wage all at the same index. So we could expect names[0] to hold "Bobby", ages[0] to hold "24", and wages[0] to hold "5.75" -- all of which are related to the same record.
Finally, the condition in the while loop has been amended to be (line = inputBuffer.readLine()) != null && index < 5. This means we'll keep looping through the lines of the file until we either run out of lines (the file ends) or our index becomes greater than 5, which is the size at which we instantiated the array. This is one reason why arrays are such a bad structure to hold this data: you have to know exactly how many records you have in your file, and you may end up not filling them all the way (you allocated too much space) or not saving some records because you have no more room to store them.
Using POJOs
A much better way to save the data would be to use a POJO -- a Plain Old Java Object. This kind of object is pretty much a "data holder" object.
In your case, it would be something like this:
public class PersonData {
private String name;
private String wage;
private String age;
public PersonData() {
this(null, null, null);
}
public PersonData(String name, String wage, String age) {
this.name = name;
this.wage = wage;
this.age = age;
}
// ... getters and setters here
}
In your code, you'd replace your arrays with a List structure of PersonData objects:
List<PersonData> records = new ArrayList<PersonData>();
And in your while loop, you'd save into these objects instead of into the arrays:
// in the else in the while loop:
String[] data = line.split("#");
if (data != null && data.length == 3) {
PersonData record = new PersonData(data[0], data[1], data[2]);
records.add(record);
} else {
// error handling for malformed line
}
Now if you wanted to get data for a particular record, you'd just need to extract the PersonData object from your records list and query it:
// assuming the first record we scraped was "Bobby#24#5.75"
PersonData person = records.get(0);
person.getName(); // returns "Bobby"
person.getAge(); // returns 24
person.getWage(); // returns 5.75
Since we're using a List and not an array, we don't have to worry about knowing exactly how many records there are in the file, and we don't run the risk of losing information because we don't have anywhere to store it.
This way we can also know for certain that a name, age, and wage are all related to the same record, whereas before we were just hoping that, say, all records at index 0 in the arrays were related to the same person.
Also, if you add additional data to the records -- for example, name#age#wage#favorite food -- all you have to do is add a new field to the PersonData object and add a line in your parsing method to add that data to the object. If you were using arrays, you'd need to add a new array, and so on.
It's also much easier to create logic if, say, you have a row that only has a name or that missing a wage, and so on -- so that you're actually able to save the data in some meaningful fashion.
If you want to make good progress in Java or any OOP Language for that matter you should always approach a problem in a Object Oriented Manner.
For the problem at hand you should always consider a class to store the Person Info rather than using associative arrays.
class PersonInfo {
PersonInfo(String name,int age,float wage) {
this.name = name;
this.age = age;
this.wage = wage;
}
String name;
int age;
float wage;
}
The code is more or less the same from above...but it should give a List of PeopleInfo as output.
List<PersonInfo> peopleInfo = new ArrayList<String>();
boolean isTitleParsed = false;
while ( (line = inputBuffer.readLine()) != null ) {
if (!isTitleParsed) {
// this is the title
title = line;
isTitleParsed = true;
continue;
} else if (line.isEmpty()) {
// this is a blank line; logic for dealing with blank lines here
} else {
String[] personData = line.split("#");
if (personData != null && personData.length == 3) {
peopleInfo.add(new PersonInfo(personData[0],personData[1],personData[2]));
}
}