I am a new Java programmer and I am working on a project that requires me to read a text file that has Movie Reviews.
Once I've read the file, I am asked to search and sort through the Array of movies and return the total number of reviews for each movie as well as the average rate for each movie.
The portion I am currently stuck on is iterating through the Array list.
I am using an inner and outer for loop and I seem to be getting an infinite loop.
I would appreciate a second set of eyes. I have been staring at this project for a few days now and starting to not see mistakes.
Here is the code:
import java.io.*;
import java.util.*;
import java.lang.*;
public class MovieReviewApp {
public static void main(String[] args)
{
String strline = "";
String[] result = null;
final String delimit = "\\s+\\|\\s+";
String title ="";
//int rating = (Integer.valueOf(- 1));
ArrayList<MovieReview> movies = new ArrayList<MovieReview>();
//ArrayList<String> titles = new ArrayList<String>();
//ArrayList<Integer> ratings = new ArrayList<Integer>();
//HashMap<String, Integer> hm = new HashMap<String, Integer>();
//ListMultimap<String, Integer> hm = ArrayListMultimap.create();
try
{
BufferedReader f = new BufferedReader(new FileReader("/Users/deborahjaffe/Desktop/Java/midterm/movieReviewData.txt"));
while(true)
{
strline = f.readLine(); // reads line by line of text file
if(strline == null)
{
break;
}
result = strline.split(delimit, 2); //creates two strings
//hm.put(result[0], new Integer [] {Integer.valueOf(result[1])});
//hm.put(result[0], Integer.valueOf(result[1]));
// titles.add(result[0]);
//ratings.add(Integer.valueOf(result[1]));
MovieReview m = new MovieReview(result[0]);
movies.add(m);
MovieReview m2 = new MovieReview();
int rating = Integer.valueOf(result[1]);
int sz = movies.size();
for (int i = 0; i < sz; i++)
{
for (int j = 0; j < sz; j++)
{
m2 = movies.get(i);
if (movies.contains(m2))
{
m2.addRating(rating);
}
else
{
movies.add(m2);
m2.addRating(rating);
}
}
}
movies.toString();
//Collections.sort(movies);
} //end while
f.close();
//Set<String> keys = hm.keySet();
//Collection<Integer> values = hm.values();
} //end of try
catch(FileNotFoundException e)
{
System.out.println("Error: File not found");
}
catch(IOException e)
{
System.out.println("Error opening a file.");
}
} // end main
} // end class
Read the file first and then iterate through the list or map for searching, sorting etc. In the above code, close the while loop before iterating through the list.
If you want to iterate through the ArrayList you can use an enhanced for-loop to iterate through it. Note: while in an enhanced for-loop you cannot make changes to the ArrayList as the enhanced for-loop makes the ArrayList essentially (and temporarily) read-only. This would work for iterating through to pull values, but not for adding values. Because you are changing the ArrayList this won't work, but I just thought that you should know about it, if you don't already. The enhanced for-loop works like this, I will put separate parts in squiggle brackets,
for({Object Type of ArrayList} {Dummy Value} : {name of ArrayList}), so it would look like this: for(MovieReview x: movies).
Regarding the inner part of this nested for-loop:
for (int i = 0; i < sz; i++)
{
for (int j = 0; j < sz; j++)
{
m2 = movies.get(i);
if (movies.contains(m2))
{
m2.addRating(rating);
}
else
{
movies.add(m2);
m2.addRating(rating);
}
}
}
Why do you have the inner part? The j variable is never used for anything so the for-loop seems kind of useless. Unless of course you made a mistake at the top of the inner loop and meant to have m2 = movies.get(j); but that seems unlikely.
In regard to the infinite loop, the way you wrote the for-loops you should not be getting an infinite loop because they all increment to a certain value that is reachable. Your while-loop seems to run infinitely, but I do notice that you have a break if strline points to null. I assume this is guaranteed to occur at the end of the file, but I would suggest that you make the condition for your while-loop be while(scannerName.hasNext()). This will allow your while-loop to eventually terminate without having the extra code plus having a Scanner instead of a BufferedReader will be slightly more efficient and still do everything the BufferedReader can do and much more, like that method hasNext().
I hope this has helped. If you have other questions, let me know. Good luck.
Related
My question is how I would implement multithreading to this task correctly.
I have a program that takes quite a long time to finish executing. About an hour and a half. I need to generate about 10,000 random and unique number codes. The code below is how I first implemented it and have it right now.
import java.util.Random;
import java.util.ArrayList;
public class Main
{
public static void main(String[] args) {
Random random = new Random();
// This holds all the codes
ArrayList<String> database = new ArrayList<>();
int counter = 0;
while(counter < 10000){
// Generate a 10 digit long code and append to sb
StringBuilder sb = new StringBuilder();
for(int i = 0; i < 10; i++){
sb.append(random.nextInt(10));
}
String code = String.valueOf(sb);
sb.setLength(0);
// Check if this code already exists in the database
// If not, then add the code and update counter
if(!database.contains(code)){
database.add(code);
counter++;
}
}
System.out.println("Done");
}
}
This of course is incredibly inefficient. So my question is: Is there is a way to implement multithreading that can work on this single piece of code? Best way I can word it is to give two cores/ threads the same code but have them both check the a single ArrayList? Both cores/ threads will generate codes but check to make sure the code it just made doesn't already exist either from the other core/ thread or from itself. I drew a rough diagram below. Any insight, advice, or pointers is greatly appreciated.
Using a more appropriate data structure and a more appropriate representation of the data, this should be a lot faster and easier to read, too:
Set<Long> database = new HashSet<>(10000);
while(database.size() < 10000){
database.add(ThreadLocalRandom.current().nextLong(10_000_000_000L);
}
Start with more obvious optimizations:
Do not use ArrayList, use HashSet. ArrayList contains() time complexity is O(n), while HashSet is O(1). Read this question about Big O summary for java collections framework. Read about Big O notation.
Initialize your collection with appropriate initial capacity. For your case that would be:
new HashSet<>(10000);
Like this underlying arrays won't be copied to increase their capacity. I would suggest to look/debug implementations of java collections to better understand how they work under the hood. Even try to implement them on your own.
Before you delve into complex multithreading optimizations, fix the simple problems - like bad collection choices.
Edit: As per suggestion from #Thomas in comments, you can directly generate a number(long) in the range you need - 0 to 9_999_999_999. You can see in this question how to do it. Stringify the resulting number and if length is less than 10, pad with leading zeroes.
Example:
(use ConcurrentHashMap, use threads, use random.nextLong())
public class Main {
static Map<String,Object> hashMapCache = new ConcurrentHashMap<String,Object>();
public static void main(String[] args) {
Random random = new Random();
// This holds all the codes
ArrayList<String> database = new ArrayList<>();
int counter = 0;
int NumOfThreads = 20;
int total = 10000;
int numberOfCreationsForThread = total/NumOfThreads;
int leftOver = total%NumOfThreads;
List<Thread> threadList = new ArrayList<>();
for(int i=0;i<NumOfThreads;i++){
if(i==0){
threadList.add(new Thread(new OneThread(numberOfCreationsForThread+leftOver,hashMapCache)));
}else {
threadList.add(new Thread(new OneThread(numberOfCreationsForThread,hashMapCache)));
}
}
for(int i=0;i<NumOfThreads;i++){
threadList.get(i).start();;
}
for(int i=0;i<NumOfThreads;i++){
try {
threadList.get(i).join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for(String key : hashMapCache.keySet()){
database.add(key);
}
System.out.println("Done");
}}
OneThread:
public class OneThread implements Runnable{
int numberOfCreations;
Map<String,Object> hashMapCache;
public OneThread(int numberOfCreations,Map<String,Object> hashMapCache){
this.numberOfCreations = numberOfCreations;
this.hashMapCache = hashMapCache;
}
#Override
public void run() {
int counter = 0;
Random random = new Random();
System.out.println("thread "+ Thread.currentThread().getId() + " Start with " +numberOfCreations);
while(counter < numberOfCreations){
String code = generateRandom(random);
while (code.length()!=10){
code = generateRandom(random);
}
// Check if this code already exists in the database
// If not, then add the code and update counter
if(hashMapCache.get(code)==null){
hashMapCache.put(code,new Object());
counter++;
}
}
System.out.println("thread "+ Thread.currentThread().getId() + " end with " +numberOfCreations);
}
private static String generateRandom(Random random){
return String.valueOf(digits(random.nextLong(),10));
}
/** Returns val represented by the specified number of hex digits. */
private static String digits(long val, int digits) {
val = val > 0 ? val : val*-1;
return Long.toString(val).substring(0,digits);
}
}
I am struggling to get my code in Java to do the following:
Inventory Update program.
The first set of records is the master file which reflects an item inventory at the start of the business day. Each master file record contains a part number and a corresponding item stock quantity. Let us assume that the master file contains no more than 5 records.
The second set of records reflects the transactions for each of various items during that day. Transaction records contain have the same format as the master file records: they contain the item number and the corresponding total number of items sold that day. There is unknown number of such transaction records in the input file.
Write a program to update the master file against the transaction file to produce the new master file at the end of the day. A record notice should accompany any item number for which fewer than 10 items remain in the stock.
The actual files look like this. (Master.txt)
444 40
111 30
222 15
134 20
353 5
And the second looks like this (Sales.txt)
134 03
111 29
353 02
222 10
And the updated version of Master.txt should look like this.
444 40
111 1 reorder
222 5 reorder
134 17
353 3 reorder
Here is my code.
import java.io.*;
import java.util.*;
public class inventoryUpdate {
//Exception included due to mOrganize, sOrganize, and PrintWriter
public static void main(String[] args) throws Exception {
Scanner inKey = new Scanner(System.in);
//Preparing the document based on how many item types were sold.
System.out.println("How many types of products were sold?");
int sold = inKey.nextInt();
int[][] inMaster = fileCheck(mOrganize(), sOrganize(sold), sold);
PrintWriter printFile = new PrintWriter("Master.txt");
printFile.println("Item No. \tQuantity");
for (int i = 0; i<5;i++){
if (inMaster[i][1] < 10){
printFile.printf("%-5s %17s You are low in inventory on this item. Please order more.\n", inMaster[i][0], inMaster[i][1]);
}
else{
printFile.printf("%-5s %17s\n", inMaster[i][0], inMaster[i][1]);
}
}
printFile.close();
System.out.println(Arrays.deepToString(inMaster));
}
private static int[][] mOrganize() throws Exception {
File fileRip = new File("Master.txt");
Scanner masterRead = new Scanner(fileRip);
masterRead.nextLine();
int[][] masterData = new int [5][2];
for (int i = 0; i < 5; i++){
for (int i2 = 0; i2 < 2; i2++){
masterData[i][i2] = masterRead.nextInt();
if (masterData[i][i2] < 10){
masterRead.nextLine();
}
}
}
masterRead.close();
return masterData;
}
private static int[][] sOrganize(int sold) throws Exception{
File fileRip = new File ("Sales.txt");
Scanner saleRead = new Scanner(fileRip);
saleRead.nextLine();
int [][] salesData = new int [sold][2];
for (int i = 0; i < sold; i++){
for (int i2 = 0; i2 < 2; i2++){
salesData[i][i2] = saleRead.nextInt();
}
}
saleRead.close();
return salesData;
}
private static int[][] fileCheck(int[][] master, int[][] sales, int sold){
int columnBase = 0;
for(int i = 0; i < 5; i++){
for(int i2 = 0; i2 < sold; i2++){
if (master[i][columnBase] == sales[i2][columnBase]){
master[i][columnBase + 1] -= sales[i2][columnBase + 1];
}
}
}
return master;
}
}
My output is as follows.
How many types of products were sold?
4
Exception in thread "main" java.util.NoSuchElementException
at java.base/java.util.Scanner.throwFor(Scanner.java:937)
at java.base/java.util.Scanner.next(Scanner.java:1594)
at java.base/java.util.Scanner.nextInt(Scanner.java:2258)
at java.base/java.util.Scanner.nextInt(Scanner.java:2212)
at inventoryUpdate.mOrganize(inventoryUpdate.java:39)
at inventoryUpdate.main(inventoryUpdate.java:14)
The code appears to break at line 14 and is related to my inMaster array.
int[][] inMaster = fileCheck(mOrganize(), sOrganize(sold), sold);
I am sure I am overlooking a syntax error of sorts, but I am failing to wrap my brain around it.
I would appreciate any help possible.
Your problem is probably a lack of making your methods too specific, and you expect too much precision in the input.
In mOrganize, you are explicitly reading away the new lines. However, the last line doesn't contain any newline, so it fails.
To be perfectly honest, nextInt happily skips whitespace, so none of the nextLine statements are necessary at all and your code runs if you remove them.
mOrganize should just be called organize if you follow Java naming conventions, and just readInventory with a File parameter if you want to let it make any sense.
I'm not sure how much you've learned about programming, but things like 5 and 10 in mOrganize are called magic values and should be avoided. What you really want is to create a class e.g. ItemInventory and ItemSales, make a Java List out of those and then read the files until the end. That way your code will work even if there are more or fewer lines in the files.
Here is a bit more advanced example that uses a Map rather than a List:
private static Map<Integer, Integer> readInventory(File file) throws IOException {
var inventory = new HashMap<Integer, Integer>();
try (var lineScanner = new Scanner(file)) {
while (lineScanner.hasNextLine()) {
// we assume each item is on it's own line
var line = lineScanner.nextLine();
try (var itemScanner = new Scanner(line)) {
var no = itemScanner.nextInt();
var amount = itemScanner.nextInt();
if (inventory.put(no, amount) != null) {
// a more specific exception such as InventoryException might be better
throw new RuntimeException("Duplicate item in inventory file");
}
}
}
}
return inventory;
}
an item number is unique after all, and you would not expect it twice in the same inventory.
As you can see, it would be easy to put more information in the same line now, although that would mean changing the return value to something other than Map<Integer, Integer> - for instance Map<Item, Integer> if you define a new Item class.
It is now completely independent of the amount of items in the file, no magic numbers anymore. It also is able to read from any file, rather than just Master.txt.
I hope it is abundantly obvious that rewriting "Master.txt" won't help you with debugging, as you need to restore it each time you run the code.
I posted this question up earlier and it was pretty much a lazy post as I didn't provide the code I had and as a result got negged pretty badly. Thought I'd create a new one of these ....
I have an array dogArray which takes ( name, secondname, dogname )
I want to be able to change the dogname:
here's my attempt :
public void changeName(Entry [] dogArray) {
Scanner rp = new Scanner(System.in);
String namgeChange = rp.next(); {
for (int i = 0; i < dogArray.length; i++){
for (int j = 0; j < dogArray[i].length; j++){
if (dogArray[i][j] == name){
dogArray[i][j] = nameChange;
}
}
}
}
For a start it doesn't like the fact I've used ' name ' although it is defined in the dogArray. I was hoping this would read input so that users are able to change 'name' to whatever they input. This will change the value of name in the array.
Do you guys think I'm getting anywhere with my method or is it a pretty stupid way of doing it?
Only one loop is necessary, also move your call to next.
public void changeName(Entry [] dogArray) {
Scanner rp = new Scanner(System.in);
for (int i = 0; i < dogArray.length; i++){
String nameChange = rp.next(); {
if(!dogArray[i].name.equals(nameChange)){
dogArray[i].name = nameChange;
}
}
}
You might want to make this function static as well and use accessors and mutators to change and get the name. Might want to tell the user what you want them to type as well. Also there is no point in testing if the name changed, if it changed then set it, if it didnt change then set it (it doesnt hurt). Just get rid of if and keep the code inside.
Im currently working on a program and any time i call Products[1] there is no null pointer error however, when i call Products[0] or Products[2] i get a null pointer error. However i am still getting 2 different outputs almost like there is a [0] and 1 or 1 and 2 in the array. Here is my code
FileReader file = new FileReader(location);
BufferedReader reader = new BufferedReader(file);
int numberOfLines = readLines();
String [] data = new String[numberOfLines];
Products = new Product[numberOfLines];
calc = new Calculator();
int prod_count = 0;
for(int i = 0; i < numberOfLines; i++)
{
data = reader.readLine().split("(?<=\\d)\\s+|\\s+at\\s+");
if(data[i].contains("input"))
{
continue;
}
Products[prod_count] = new Product();
Products[prod_count].setName(data[1]);
System.out.println(Products[prod_count].getName());
BigDecimal price = new BigDecimal(data[2]);
Products[prod_count].setPrice(price);
for(String dataSt : data)
{
if(dataSt.toLowerCase().contains("imported"))
{
Products[prod_count].setImported(true);
}
else{
Products[prod_count].setImported(false);
}
}
calc.calculateTax(Products[prod_count]);
calc.calculateItemTotal(Products[prod_count]);
prod_count++;
This is the output :
imported box of chocolates
1.50
11.50
imported bottle of perfume
7.12
54.62
This print works System.out.println(Products[1].getProductTotal());
This becomes a null pointer System.out.println(Products[2].getProductTotal());
This also becomes a null pointer System.out.println(Products[0].getProductTotal());
You're skipping lines containing "input".
if(data[i].contains("input")) {
continue; // Products[i] will be null
}
Probably it would be better to make products an ArrayList, and add only the meaningful rows to it.
products should also start with lowercase to follow Java conventions. Types start with uppercase, parameters & variables start with lowercase. Not all Java coding conventions are perfect -- but this one's very useful.
The code is otherwise structured fine, but arrays are not a very flexible type to build from program logic (since the length has to be pre-determined, skipping requires you to keep track of the index, and it can't track the size as you build it).
Generally you should build List (ArrayList). Map (HashMap, LinkedHashMap, TreeMap) and Set (HashSet) can be useful too.
Second bug: as Bohemian says: in data[] you've confused the concepts of a list of all lines, and data[] being the tokens parsed/ split from a single line.
"data" is generally a meaningless term. Use meaningful terms/names & your programs are far less likely to have bugs in them.
You should probably just use tokens for the line tokens, not declare it outside/ before it is needed, and not try to index it by line -- because, quite simply, there should be absolutely no need to.
for(int i = 0; i < numberOfLines; i++) {
// we shouldn't need data[] for all lines, and we weren't using it as such.
String line = reader.readLine();
String[] tokens = line.split("(?<=\\d)\\s+|\\s+at\\s+");
//
if (tokens[0].equals("input")) { // unclear which you actually mean.
/* if (line.contains("input")) { */
continue;
}
When you offer sample input for a question, edit it into the body of the question so it's readable. Putting it in the comments, where it can't be read properly, is just wasting the time of people who are trying to help you.
Bug alert: You are overwriting data:
String [] data = new String[numberOfLines];
then in the loop:
data = reader.readLine().split("(?<=\\d)\\s+|\\s+at\\s+");
So who knows how large it is - depends on the success of the split - but your code relies on it being numberOfLines long.
You need to use different indexes for the line number and the new product objects. If you have 20 lines but 5 of them are "input" then you only have 15 new product objects.
For example:
int prod_count = 0;
for (int i = 0; i < numberOfLines; i++)
{
data = reader.readLine().split("(?<=\\d)\\s+|\\s+at\\s+");
if (data[i].contains("input"))
{
continue;
}
Products[prod_count] = new Product();
Products[prod_count].setName(data[1]);
// etc.
prod_count++; // last thing to do
}
this is my first question on stack overflow but I have some experience in Java. I am making a Java application at the moment (575 lines and counting!) and am trying to search through an ArrayList for a string. But I do not want it to be exact! Let me clarify: I want to iterate through each ArrayList element and search that string for another string. if the string is found in the ArrayList element, (for now) I want it printed to the console. I hope I have been clear enough.
Following is the relevant code. All variables are defined and the code compiles, just no output (from the search function) is printed. I am pretty sure that it is because the for loop doesn't execute, but I am puzzled as to why.
//the keylistener that calls the search() function, attached to a JTextField that the query is entered into
class searchFieldListener implements KeyListener {
searchFieldListener() {
}
public void keyTyped(KeyEvent event) {
if (event.getID() == KeyEvent.KEY_TYPED) {
query = searchField.getText()+Character.toString(event.getKeyChar());
System.out.println(query);
for (i = 0; i == nameList.size(); i++) {
search(query, i);
}
}
}
public void keyReleased(KeyEvent event) {
}
public void keyPressed(KeyEvent event) {
}
}
//the troublesome search() function
void search(String query, int iter) {
searchString = nameList.get(iter);
System.out.println(searchString);
if (searchString.indexOf(query) != -1) {
System.out.println(Integer.toString(iter));
} else {
System.out.println("not found \n");
}
}
Variables/Objects and uses:
searchFieldListener
The KeyListener for the JTextField called searchField for obvious reasons.
query
The string of the text to be searched for.
i
Why does everyone use i in loops? I guess it's a coding tradition.
nameList
The ArrayList of names (well, duh).
searchString
The string to be searched in (as in, try to find query in searchString).
iter
The number of iterations the for loop has been through so far.
Once again I hope I have been clear enough. Thanks!
The reason why your for loop is not executing is because of the condition used in the loop:
for (i = 0; i == nameList.size(); i++)
^^
Since the size method of the ArrayList class returns the number of elements you might want to have
i < nameList.size() instead.
for (i = 0; i == nameList.size(); i++)
should be
for (i = 0; i < nameList.size(); i++)
Not sure if you need < or <= though, you just modify it to you needs.
have have a typo in your for-loop. shouldn't this:
for (i = 0; i == nameList.size(); i++) {
look like this:
for (i = 0; i < nameList.size(); i++) {
Several correct answers, but one aspect is missing:
for (i = 0; i < nameList.size(); i++)
This is an old-school loop. Starting from Java 1.5, you should use this idiom to iterate over an array or iterable (a List is an Iterable):
for(String s: strings){
}
This simpler syntax is a) much less error-prone and b) a common way to iterate over many different data structures, including arrays and collections.
Internally this is a shortcut for this code:
for(Iterator<String> it = strings.iterator(); it.hasNext();){
String s = it.next();
// your code here
}