I am relatively new to Java and would like to know how to store variables separately from a single line of user input.
At the minute the user is prompted to enter football results in the following format
home_name : away_name : home_score : away_score
and I am using a while loop to continue to ask user for input until they enter "stop"
(while (input != "stop))
Once the loop is broken I would like my program to output a variety of data such as total games played, but I'm struggling to store the home_name, away_name etc.. especially if the user wishes to enter multiple lines of results.
Two mainstream ways to store a "record" are:
Maps
Data objects
A map is more generic:
Map<String,String> match = new HashMap<>();
match.put("home_name", "Alvechurch Villa");
match.put("away_name", "Leamington");
match.put("home_score", "0");
match.put("away_score", "6");
You can add a map to a list:
List<Map<String,String>> matches = new ArrayList<>();
matches.add(list);
... and retrieve them:
Map<String,String> match = matches.get(0);
System.out.println(match.get("away_score"));
A data object is more tuned to your data format, but you have to write the class yourself.
public class Match {
public String homeName;
public String awayName;
public int homeScore;
public int awayScore;
}
Now you can use this class:
Match match = new Match();
match.homeName = "Studley";
// etc.
You can add and retrieve these from lists too:
List<Match> matches = new ArrayList<>();
matches.add(match);
Match aMatch = matches.get(0);
This is simple, but it's considered bad practice to have public fields like this - it's better to get at them via methods. For brevity, here's a data class with only one field:
public class Player {
private String name;
public Player(String name) {
this.name = name;
}
public String name() {
return name;
}
}
Player neilStacey = new Player("Neil Stacey");
You can use the same technique with all the fields in Match.
(A common style is to name a method like this getName(), and also to have a setName(). I have used a different style and made the object immutable, in an effort to set a good example!)
One advantage of the data object is that it has different types for different fields: homeName is a String, homeScore is an integer. All the fields in the Map are Strings. You can get around this by using Map<String,Object> but then as a consumer you have to cast to the right type when you read.
String homeName = (String) match.get("home_name");
Data objects allow the compiler to do a lot of compile-time checking that helps you know your code is correct. If you use a map, you won't find out until runtime.
Prompt the user separately for each input.
System.out.println("home_name: ");
String hN = scan.next();
System.out.println("away_name: ");
String aN = scan.next();
System.out.println("home_score: ");
String hS = scan.next();
System.out.println("away_score: ");
String aS = scan.next();
Related
So I am coding in java in which I am a newbie, let's say that I have a class Place
public class Place{
private String name;
}
and I have a class Player
public class Player{
private Place location;
public void goLoc(Place p){
this.location=p;
}
}
and I need to get from the user where they want to go, and I am using an enum to my commands list that the user can use (GO,HELP...etc) so let's say I have these two objects :
Place r = new Place("Room");
Place r2 = new Place("Room2");
when the user will enter for example go room, I need to call the method goLoc(r) but I store the input from the user with a String.
Scanner in = new Scanner(System.in);
String loc = in.next();
How could I get that r object by its attribute name? or is there any other way to do this in a better way?
final List<Place> places = Arrays.asList(r, r2);
final Place selectedPlace = places.stream()
.filter(p -> p.getName().equals(loc))
.findFirst()
.orElse(null);
Store all the objects of Place in a list and utilize the stream API.
So i am trying to sort through a large amount of data in an CSV. file. The file includes a set amount of information for companies, but there are 1000s of companies. For example, I might need to go through 1000 companies, be able to acquire their annual earnings, current stock value, CEO, ect.. each company will have the same information provided (same number of commas but different char lengths), but as the file is a CSV. the company name and information is all separated by commas.
currently i am splitting the csv file into an array via the commas between information. But i want to be able to keep the information together with companies and be able to specify, call and, sort by the given information and company names. But because i have already separated the information via the commas its all listed out already in a listarray.
So is it possible to specify, on a mass scale, that every 15 commas (or splits in the listarray) should be joined back together?? This way each part of the listarray is a separate company. Or is there another way to separate the data so that the information doesnt get split up?
note: there is no similarities in the csv file that would allow me to split information so that it splits after each companies information.
here is a sample of what one of the csv files may look like.
"Tiffany & Co. Com",964270,"+0.81","1/14/2014",88.97,93.64,"87.795 - 88.97""Asia Pacific Fund",20700,"+0.04","1/14/2014",10.23,11.37,"10.19 - 10.23""Anadarko Petroleu",4236380,"+2.47","1/14/2014",80.99,98.47,"78.40 - 80.99""Proto Labs, Inc. ",451984,"-0.18","1/14/2014",73.83,89.97,"71.00 - 73.83""Zuoan Fashion Lim",201560,"-0.02","1/14/2014",1.79,3.62,"1.71 - 1.79"
I would agree with converting each row of CSV into Java object.
But traditional parsing mechanism is too verbose for me and I might need to handle too many conditions like comma in between quotes, new line character in a column with multiline description. So I suggest you use an existing awesome solution like supercsv.
I also have written a wrapper around it to make developer life easy.
QuickOCM will let you do this way.
Create a Company class
public class Company {
/* this specifies that
* it is a mandatory field in the csv,
* of header name "Company Name" and
* of type string.
* Header is the first line of the csv.
*/
#ImportField(mandatory = true, name = "Company Name", type = "String")
public String name;
#ImportField(mandatory = true, name = "Name of the CEO", type = "String")
public String ceoName;
}
You need public getter-setter or public fields, anything works
Create csv parser, a handler to handle each row, probably add to a list to sort, and then call parse
final List<Company> companies = new ArrayList<Company>();
csvParser.process(inputStream, Company.class, new RecordHandler<Company>() {
#Override
public void execute(Company imported, int rowNumber, Map supplementaryInfo) {
companies.add(imported);
}
});
Now you can sort the list with by using a sorted list implementation or use a comparator for the same.
For detailed info, you can look into QuickOCM page.
public class Read{
String original = "";
String company = "";
String otherValue = "";
public Read(String read){
//here Split the original string into the values
}
//public void getters and setters
}
Then make an array of Read Objects and sort them as you want
One idea would be to parse the CSV into objects and then sort those objects. The object would "know" how many fields it was made up of in the CSV and how to parse each field. Using the StringTokenizer to parse and a TreeMap to sort would look something like:
...
BufferedReader reader = new BufferedReader(new FileReader("somedata.csv"));
TreeMap<String, MyObject> map = new TreeMap<>();
String line = reader.readLine();
StringTokenizer tokens = new StringTokenizer(line,",");
while(tokens.hasMoreTokens()) {
MyObject obj = new MyObject(tokens);
//add the objects to the sorted map, where field1 is what we sort on
map.put(obj.field1, obj);
}
...
}
static class MyObject {
//would need the same number of fields as you want to group
String field1;
String field2;
//... so with 2 fields, input is field1,field2,field1,field2,...
MyObject (StringTokenizer input) {
this.field1 = input.nextToken();
this.field2 = input.nextToken();
}
}
In a small project I am working on I've gotten stuck. The user enters a command that may be "xp Speed", my command handler class finds that it wants to the XP value of the Speed Instance. In this case it needs to return the value of Skill.Speed.currentXP back to the user.
Small Part of the program:
//Example Instance initialization there is over 40 of these
Skill Speed = (new SkillSpeed(Skills.SKILL_SPEED,Skills.SKILL_SPEED_MODIFIER));
//Constructor for skill class
public Skill(String skillName, double modifier) {
this.name = skillName;
this.minLevel = Skills.MIN_SKILL_LEVEL;
this.Modifier = 1f;
this.currentLevel = (int)calculateLevel();
this.currentXP = 1;
this.leaderboard = getCurrentLeaderboard();
this.ID = getNextID();
}
Now, theres one way i could do this. by having a switch statement with case value being the string entered. However I'm sure having 40+ cases in one switch statement must be avoidable. The other theory I have had is creating a array of all current instances then iterating through that list, finding if the user inputted string is equal to the name of that instance, then returning the instance itself. This is what I came up with:
//method inside another classs that attempts to return the appropriate skill Instance
public Skill getSkillFromName(String Name) {
for(int i = 0; i < Skill.SkillArray.length; i++) {
final String SkillName = Skill.SkillArray[i].getName();
if(SkillName.equalsIgnoreCase(Name)) {
return Skill.SkillArray[i];
}
}
return null;
}
So here's what I need help with:
Creating a array of all initialized instances
Creating the method that will return Skill."InsertRandomInstanceDependingOnUserInputHere".currentXP
Fixing any problems you see in the getSkillFromName() method
Or perhaps I have overlooked a far easier way of doing this, and you can help me with that.
Thanks for the help,
BigDaveNz
If the names of the skills excatly match method names you might find the aswer at "How do I invoke a Java method when given the method name as a string?".
For finding instances by name you can still use Map's.
You can use a Map for this. E.g.:
Map<String, Skill> skills = new HashMap<String, Skill>();
To insert the values you put the values into the Map:
skills.put(skill.getName(), skill);
To retrieve your skill you can get the skill by name:
Skill skill = skills.get(name);
I got a String[] which contains of multiple user details. Something like this:
Wilson#$20#$Male=#=Raymond#$25#$Male=#=Sophie#$20#$Female
I wanted to split the string up and organize it into multiple array. Such as one array for Name, one array for Age and another array for Gender. Up to this point I managed to split the String[] into something like this.
String[] User = student.split("=#=");
User[0] = Wilson#$20#$Male
User[1] = Raymond#$25#$Male
User[2] = Sophie#$20#$Female
I don't really know how to organize it from this point. Any comments and answers are highly appreciated!
EDIT
Wilson#$20#$Male=#=Raymond#$25#$Male=#=Sophie#$20#$Female
The above part is actually a value that is returned from the server and I wanted to handle this value. Thank you for all the replies. I think I understand a bit in theory wise, but I'm having slightly issue in implementing codes.
I agree with the suggestions of creating a class for each user - it's the Object Oriented way. So I included it in the example below. But you could probably change it easy enough if you want to do arrays or some other structure.
However, what I want to add is a way to use the Java classes java.util.regex.Pattern and java.util.regex.Matcher to extract both records AND fields in one go from your input string. (I haven't programmed for Android, I assume they are available though.)
The general plan for the pattern is: (record delimiter or nothing)(field1)(delim)(field2)(delim)(lastfield)(record delimiter + rest of input)
The algorithm basically loops through the input with the above pattern. The pattern extracts various groups for the fields (depending on how your record's format) and then also a last group that contains the remainder of the input string. This remainder is used as the new input string for the next loop. So each iteration of the loop does one record.
Here is more complete example code which you can run. You might need to study up on regular expressions to understand the pattern, which is the important part of the algorithm. You can start with the Javadoc for the java.util.regex.Pattern class.
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class TestPatternMatch {
public static void main(String[] args) {
List<User> result = new ArrayList<>();
String input =
"Wilson#$20#$Male=#=Raymond#$25#$Male=#=Sophie#$30#$Female";
Pattern recPatt =
Pattern.compile("^(=#=|.{0})(.+?)#\\$(\\d+)#\\$(.+?)(?==#=)(.*$)");
// ^match start of line
// ^match record delimiter or nothing
// ^match name field (reluctant)
// ^match field delim
// ^match age field
// ^match field delim
// match gender field^
// zero-width (non recording) lookahead for record delimiter^
// match rest of line until end^
Matcher recMatcher;
String workStr = input; // start with whole input
while (workStr != null && workStr.length() > 0) {
recMatcher = recPatt.matcher(workStr);
if (recMatcher.matches() && recMatcher.groupCount() >= 5) {
result.add(
new User(
recMatcher.group(2), //name
Integer.parseInt(recMatcher.group(3)), //age
recMatcher.group(4) //gender
)
);
workStr = recMatcher.group(5); // use tail for next iteration
} else {
workStr = null;
}
}
System.out.println(result); //show result list contents
}
}
class User {
String name;
int age;
String gender;
/** All argument constructor */
public User(String name, int age, String gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
/** Show contents of object fields */
public String toString() {
return "User ["+ name + ", " + age + ", " + gender + "]";
}
}
The basic pattern structure can be reused for many different record formats.
Create a User object to store all fields (name, age, gender) and create a list to hold all data.
Your best bet here, is to use an object to hold these values. Objects are the standardized way to hold values that relate to one another, in one Object. ie:
public class Person
{
private String name;
private int age;
private String gender;
// Gender could be a boolean value really, but you've stored it as a String.
}
In the constructor you would request each value and assign it to these fields. It would look something like:
public Person(String name, int age, String gender)
{
this.name = name;
// etc etc
}
That way you have one array, with no need to do any tokenizing of Strings to get to individual values :). You will also need to implement some Accessors and Mutators to get at the values within the Object.
Why not create a User class and maintain a list of User instances.
class User {
String name;
String gender;
int age;
}
The best solution would be to create an class User. If you want to avoid it, try:
String[] User = student.split("=#=");
String [][] details=new String[user.length][3];
String [] temp=new String[3];
for(int i=0;i<User.length;i++){
temp=User.split("//");
for(j=0;j<3;j++){
details[i][j]=temp[j];
}
}
This is more of a design question with implications for code simplicity vs. performance.
Lets say you want to make sure a set of values for a given user id are the same between two systems. The example here is to check that a student id has the same number of course enrollments in System A and System B.
For this we create:
List<String> studentList = new ArrayList<String>();
Set<String> sysAEnrollments = new HashSet<String>();
Set<String> sysBEnrollments = new HashSet<String>();
private Map<String, String> badEnrollList = new HashMap<String, String>();
And fill them appropriately, given a list of student ids(studentList):
studentList = getCurrentStudentList();
for (String id : studentList){
sysAEnrollments = getSysAEnrollments(id);
sysBEnrollments = getSysBEnrollments(id);
if (!sysAEnrollments.containsAll(sysBEnrollments)){
badEnrollList.put(id, getBadEnrollmentsById(id, sysAEnrollments, sysBEnrollments));
}
}
Question: What should the method 'getBadEnrollmentsById' return?
Either a concatenated string with enough meaning so it can just be printed out.
Or have a new object, for example another collection with the list of course ids that could be used for further processing but harder to use for printed output.
Is it worth designing thoroughly all expected objects or replace some of them with concatenated strings for clarity and performance?
NOTES:
System A is preferred as the authoritative source
Output from getBadEnrollmentsById should have all courses and flag those missing in system B.
PROPOSED SOLUTION: (2012-SEP-14)
EDIT (2012-SEP-17): Updated the Course class to include hashCode and equals
As suggested by user351721 I continued modelling the remaining objects that match the expected results/requirements.
Slight changes made a big difference and allowed me to go over this design flaw and finish with the implementation.
The revised collections are:
List<String> studentList = new ArrayList<String>();
Enrollment sysAEnrollments;
Enrollment sysBEnrollments;
Map<String, List<String>> badEnrollList = new HashMap<String, List<String>>();
And we populate the Enrollments:
for (String id : studentList){
sysAEnrollments = getSysAEnrollments(id);
sysBEnrollments = getSysBEnrollments(id);
if (!sysAEnrollments.getCourses().containsAll(sysBEnrollments.getCourses())){
List<String> missingCourses = getProblemEnrollmentListById(id, sysAEnrollments, sysBEnrollments);
badEnrollList.put(id, missingCourses);
}
}
So for now the output can be printed from badEnrollList by getting at each ArrayList and printing the course names. A course name with a * will mean that it's missing in sysB.
The Enrollment class looks like this:
public class Enrollment {
private Set<Course> courses = new HashSet<Course>();
public void setCourses(Set<Course> courses){
this.courses = courses;
}
public Set<Course> getCourses(){
return this.courses;
}
}
And the Course class ended up like this:
public class Course {
private String id;
private String name;
public String getId() {
return id;
}
public void setId(final String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(final String name) {
this.name = name;
}
// Must override hashCode() and equals()
#Override
public boolean equals(Object o){
if (o == this)
return true;
if (!(o instanceof Course))
return false;
Course c = (Course) o;
return c.id.equals(this.id) && c.name.equals(this.name);
}
#Override
public int hashCode(){
// Magic numbers as shown on Joshua Bloch's book "Effective Java" 2nd Edition, p.48
int result = 17;
result = 31 * this.id.hashCode();
result = 31 * this.name.hashCode();
return result;
}
}
The changes might look subtle but the important clue is that Enrollments are not a collection of strings, Enrollments are a collection of Courses AND each Course has a name and a availability property. They don't seem to do much but by using them I am defining the objects that I'm working with and documenting how these classes can be reused in the future.
"Growing Object-Oriented Software, Guided by Tests" addresses this question: chapter 7, "Value Types". Worth reading. An excerpt:
The more code we write, the more we’re convinced that we should define types to represent value concepts in the domain, even if they don’t do much. It helps to create a consistent domain model that is more self-explanatory. If we create, for example, an Item type in a system, instead of just using String, we can f ind all the code that’s relevant for a change without having to chase through the method calls
concatenated strings
would mean you have to define a pattern and corresponding set of valid strings and implement validation and translation to entity classes. Providing an interface or class would make it easier to update your code in a year or so, not to mention other programmers that might work with your application. Why not store student, enrollment or course objects in badEnrollList? How do these objects look like and what do you want to do with them?
In general: Yes, designing thoroughly all expected objects is worth it.
I feel that a collection, such as List<String> would be a desirable return value. This allows you to more efficiently capture multiple discrepancies between the two sets, and process the missing courses in your second object more intuitively. Printing the list wouldn't be that hard, either - depending on how you wished to convey the information.
It's also worth mentioning that the .equals() method for Set is a cleaner and more intuitive way to ensure equivalence between two sets.
Instead of using all these sets and maps, I'd use Plain Old Java Objects (POJOs) that reflect the actual business objects in question. From what you've indicated, you have Students who have an id of some sort, and who are enrolled in classes on System A and on System B. I would build up a set of Student objects defined like so:
public class Student {
private String id;
private List<String> enrollmentsA;
private List<String> enrollmentsB;
// appropriate getters and setters
}
Depending on if you want to do anything else with Classes, it may even be preferable to create some form of EnrolledClass object to represent that too.
Within the students class, I'd then have a method that would determine the "bad" enrollments. If all that you want to do with this data is generate an email message, it may even be as simple as a String:
public String getBadEnrollmentsMessage() {
List<String> enrolledBoth = getCommonEnrollments();
List<String> enrolledOnlyA = getAOnlyEnrollments();
List<String> enrolledOnlyB = getBOnlyEnrollments();
StringBuilder output;
// format the contents of the above lists into output
// format should be however you want it in the email.
return output.toString();
}
Then you could have a map of Students to email enrollments messages:
HashMap<Student, String> studentEmails;
for (Student s : allStudents) {
studentEmails.put(s, s.getBadEnrollmentsMessage());
}
Of course, if you have a method like getBadEnrollmentsMessage(), I'm not even sure you need the Map of students and strings in the first place. Frankly you could just create a sendEnrollmentEmail method, pass in a Student, and extract the message via getBadEnrollmentsMessage() right there.