0

I'm rather new in programming and I need help to finish my task. In .txt file there is info, which is divided in 5 colums and have several rows. It looks loke this:

rubber 2 0,3 3,08 4016920000
pen 3 0,3 5,04 9608101000
pen 2 0,2 2,5 9608101000
pencil 5 0,24 6,3 9609101000
pencil 3 0,18 4,8 9609101000

1st comumn is description, 2nd - quantity, 3rd - weight, 4th - price, 5th - tariff code. Columns are divided by spaces. I have to rearrange these lines in according tariff code, in other words I have to count quantity, weight and price for each tariff code.

For this example the result has be to like this:

rubber 2 0,3 3,08 4016920000
pen 5 0,5 7,54 9608101000
pencil 8 0,42 11,1 9609101000

For my point of view I need to create 2d array, fill it with this data and to make computing of proper lines, but I don't know how to do it. Below is the code I have now. I would appreciate any of your assistance. Thanks in advance.

import java.io.File;
import java.io.FileInputStream;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.io.IOException;
import java.util.Scanner;
import java.util.Iterator;
import java.util.ArrayList;
import java.io.FileWriter;
import java.util.Arrays;
import java.io.*;

public class Mail {
    
    public static void main(String args[]) throws Exception{
           
          File file1 = new File("C: \"Test for java input.txt\");
          if (file1.length() == 0) {  System.out.println("File is empty"); System.exit(0);}    
          FileInputStream fis = new FileInputStream(file1);
          byte[] byteArray = new byte[(int)file1.length()];
          fis.read(byteArray);
          String data = new String(byteArray);
          String[] stringArray = data.split("\r\n");
          System.out.println("Number of lines in the file are ::"+stringArray.length);
          fis.close();
          
        
          
          BufferedReader br = new BufferedReader (new FileReader ( "C: Test for java input.txt"));
          String line;
          char[][] maze = new char[stringArray.length][5];
                      
          while ((line = br.readLine() ) != null ){

                char[] row = line.toCharArray();
                    int x = 0;
                    for (int i = 0; i < row.length; i++) {
                    maze[x][i] = row[i];

                    System.out.print(maze[i]);
                    x++;
                    }
                    System.out.printf("%n");  //mention this
                }
          
          
          
          File file2 = new File("C:  "Test for java output.txt");
            int text = stringArray.length;
     
            try (PrintWriter out = new PrintWriter(file2, StandardCharsets.UTF_8))
            {
                out.print(text);
                System.out.println("Successfully written data to the file");
            } catch (IOException e) {
                e.printStackTrace();
            }


}
}  

I have tried this, but it is wrong.

BufferedReader br = new BufferedReader (new FileReader ( "C:Test for java input.txt"));
          String line;
          char[][] maze = new char[stringArray.length][5];
                      
          while ((line = br.readLine() ) != null ){

                char[] row = line.toCharArray();
                    int x = 0;
                    for (int i = 0; i < row.length; i++) {
                    maze[x][i] = row[i];

                    System.out.print(maze[i]);
                    x++;
                    }
                    System.out.printf("%n");  //mention this
                } 

  • Not sure if those pipe characters are actually *in* the file or not. But if you have "columns", you should post the file contents as formatted code – g00se Apr 26 '23 at 14:15
  • Plus points for formatting as a table, but we still need to know what delimiter you're using – g00se Apr 26 '23 at 14:24
  • Spaces are used between rows in .txt file. – Pavel Jefimovich Apr 26 '23 at 14:33
  • I'm talking about between columns. Back to the substance: Java is an object-oriented language so you should probably begin by making an object that will encapsulate the data of each row. Off the top of my head, you could call it `WritingKit` – g00se Apr 26 '23 at 14:35
  • Rows and columns are divided by spaces. – Pavel Jefimovich Apr 26 '23 at 14:37
  • 1
    Yes, it appears as though your input data is one line per multi-column row, with columns being separated by whitespace. You've imported Scanner but you're not using it. If robustness is not required (i.e., you can assume consistent data types and no null values), then you should use the `Scanner(String)` constructor and let it parse the input for you as you process each line in the while loop. – Jeff Holt Apr 26 '23 at 14:51
  • Is it guaranteed the data is sorted by tariff code, or all rows with the same tariff codes are together? A solution in which the data source has all rows with the same tariff codes is simpler than one in which tariff codes are not grouped. For example, could the order be *pen, rubber, pencil, pen, pencil*? – Old Dog Programmer Apr 26 '23 at 16:56
  • *"From my point of view, you have different data types for the columns: String, int, float, float, and String (or long)."* Since the 4th column is price, `BigDecimal` would be better than `float`. – Old Dog Programmer Apr 26 '23 at 17:00
  • 1
    That file path is not valid. I suspect you were having difficulty with the backslashes. In Java, in code, a single backslash needs escaping with a preceding backslash. But in point of fact, there's no need to use ugly doubled backslashes for file paths. Single *forward* slashes work just fine: `File file1 = new File("C:/Test for java input.txt");`, though for convenience, I would avoid spaces in paths – g00se Apr 26 '23 at 17:25
  • From my point of view, you have different data types for the columns: 'String', 'int', 'float', 'float', and 'String' (or 'long'). That suggests you want to create a new 'class' With recent versions of Java, you have the option of using a `record`. Each row in the table corresponds to one instance. That will allow use of a 1D array of that type of Object. If you want to have a 2D array, it would be a 2D array of `String`, not of `char`. – Old Dog Programmer Apr 27 '23 at 00:02
  • Off-topic: Please change `File file1 = new File("C: \"Test for java input.txt\");` to `File file1 = new File("C: \"Test for java input.txt\"");`. (Add a `"`). It will make the code sample easier to read. – Old Dog Programmer May 01 '23 at 15:45
  • Both look quite wrong actually ;) – g00se May 01 '23 at 16:00

2 Answers2

0

Since you're new to programming, I'm going to post a 'two-level' example. The commented out bit in the main class is what I suggest you use to start you off. You can run the code as is to set what it does and then comment it out and produce a more basic solution.

Once you have a collection of objects, you can manipulate that to produce your desired result, but we start with that object, since Java is an object-oriented language. For that reason, we can start with your 'business object' which I've called WritingKit. The NumberFormat stuff is just so that your input file with the comma as decimal point parses right for me. You can probably discard it:

import java.text.NumberFormat;
import java.text.ParsePosition;
import java.util.Locale;

public class WritingKit {
    private String description;
    private int quantity;
    private double weight;
    private double price;
    private String tariffCode;
    private static NumberFormat nf = NumberFormat.getNumberInstance(Locale.GERMAN);

    public WritingKit() {
    }

    public WritingKit(String description, int quantity, double weight,
        double price, String tariffCode) {
        this();
        this.description = description;
        this.quantity = quantity;
        this.weight = weight;
        this.price = price;
        this.tariffCode = tariffCode;
    }

    public static WritingKit combine(WritingKit wk1, WritingKit wk2) {
        WritingKit combined = new WritingKit(
                wk1.getDescription(),
                wk1.getQuantity() + wk2.getQuantity(),
                wk1.getWeight() + wk2.getWeight(),
                wk1.getPrice() + wk2.getPrice(),
                wk1.getTariffCode());
        return combined;
    }
    public static WritingKit valueOf(String[] attributes) {
        return new WritingKit(
                attributes[0].trim(),
                Integer.parseInt(attributes[1].trim()),
                nf.parse(attributes[2].trim(), new ParsePosition(0)).doubleValue(),
                nf.parse(attributes[3].trim(), new ParsePosition(0)).doubleValue(),
                attributes[4].trim());
    }

    public String getDescription() {
        return this.description;
    }

    public int getQuantity() {
        return this.quantity;
    }

    public double getWeight() {
        return this.weight;
    }

    public double getPrice() {
        return this.price;
    }

    public String getTariffCode() {
        return this.tariffCode;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public void setQuantity(int quantity) {
        this.quantity = quantity;
    }

    public void setWeight(double weight) {
        this.weight = weight;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    public void setTariffCode(String tariffCode) {
        this.tariffCode = tariffCode;
    }

    public String toCsv(String sep) {
        return String.format("%s%s%d%s%.2f%s%.2f%s%s", 
            description, sep, quantity, sep, weight, sep, 
            price, sep, tariffCode);
    }

    public String toString() {
        return String.format("%s=%s,%s=%d,%s=%.2f,%s=%.2f,%s=%s", "description",
            description, "quantity", quantity, "weight", weight, "price",
            price, "tariffCode", tariffCode);
    }
}

So this is how we can get your result:

import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.Optional;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.stream.Collectors;

public class WritingItems {
    public static void main(String[] args) {
        try {
            /*
            List<String> lines = Files.readAllLines(Path.of(args[0]));
            List<WritingKit> wkItems = new ArrayList<>();
            for (String line : lines) {
                wkItems.add(WritingKit.valueOf(line.split(" ")));

            }
            System.out.println(wkItems);
            */

            List<WritingKit> items = Files.lines(Path.of(args[0]))
                .map(line ->line.split(" "))
                .map(WritingKit::valueOf)
                .collect(Collectors.toList());
            System.out.println("=================== ORIGINAL ITEMS ======================");

            for(WritingKit wk : items) {
                System.out.println(wk.toCsv("\t"));
            }


            Map<String, Optional<WritingKit>> groupedReduced = items.stream()
                .collect(Collectors.groupingBy(WritingKit::getDescription, Collectors.reducing(WritingKit::combine)));
            System.out.println("=============== GROUPED AND TOTALLED ITEMS ==============");
            groupedReduced.values()
                .forEach(wkReduced -> System.out.println(wkReduced.get().toCsv("\t")));
        }
        catch(Throwable t) {
            t.printStackTrace();
        }
    }
}
g00se
  • 3,207
  • 2
  • 5
  • 9
0

Your code samples show a lot of confusion.

  • You are correct that, in Java, a file can be accessed in a manner similar to an array of byte. As in your code sample, you can even read an entire file into a byte []. But, when a file is treated as an array of byte, it's up to the programmer to provide code that assembles the individual byte into various fields, whether primitives or Objects. That can be cumbersome.

  • Treating a file as an array of byte isn't needed when the data is in a text file. Java offers several APIs that make it easier to process data in text files. FileInputStream is NOT one of them. But, BufferedReader is.

  • Why do you have a variable maze in your code? Along with the 2D array of byte, this makes it look like you took existing code, perhaps for a maze, and inappropriately attempted to adapt it to an unrelated problem.

  • If you are going to treat the data as a 2D array, it should be an array of String. Each row in the array represents one row in the table / source data. Each element in a row represents one element. Think about it: You can't hold all 10 characters of a tariff code in one byte. If you use an array of byte, it would have to be a 3D array. And that would be worse. Again, you would have to provide code to build fields from 'bye []`.

  • In the second code sample, you found a way, using BufferedReader to read the file in as String for each line. But, the code following goes backward: It converts the result into a char[]. What you want to do is parse line to extract the values of the fields it represents.

Java has a lot of tools you can use. One is the split method of String:

 while ((line = br.readLine() ) != null ){
    String [] = line.split (" "); 

line.split(" ") will be based on whitespace, as you wish. You could put the results into a 2D array you had in mind in the first place. But, you still have to parse to get numeric data so you can do the arithmetic.

To parse numeric data from a String, you can use methods that take String and return the type of value you want in these APIs:

When updating the array, you would need to convert updated numeric values back to String. Example: foo [i][1] = Integer.toString (totalQuantity);

If the data is kept in a 2D String array, the code might work like this:

  • Read a line
  • Parse the numeric values
  • Search the array
  • If a match is found, do the following:
    • Parse the numeric fields in the matching row
    • Do the arithmetic
    • Update the matching row.
  • If a match is not found, add the new data as a new row in the array.
  • Repeat as long as there are lines to be read.

You could reduce the amount of parsing by keeping separate arrays:

 String [] name;
 String [] tariffCode;
 float [] weight;
 int [] quanity;
 BigDecimal [] price;

Now, you have 5 tightly coupled arrays, also known as parallel arrays. That is regarded as poor design, especially in an Object-Oriented Programming language, such as Java. It is better to create a class, and use Objects.

The class:

import java.math.BigDecimal;
import java.util.Objects;
import java.util.Scanner;

public class Product {

   private String name;
   private int quantity;
   private float weight;
   private BigDecimal price;
   private String tariffCode;
   
   public Product(String name, int qty, float wght, BigDecimal price,
            String tariffCode) {
       this.name = name;
       this.quantity = qty;
       this.weight = wght;
       this.price = price;
       this.tariffCode = tariffCode;
   }

   // generated by IDE
   @Override
   public int hashCode() {
       int hash = 5;
       hash = 47 * hash + Objects.hashCode(this.tariffCode);
       return hash;
   }

   // generated by IDE
   @Override
   public boolean equals(Object obj) {
       if (this == obj) {
          return true;
       }
       if (obj == null) {
           return false;
       }
       if (getClass() != obj.getClass()) {
           return false;
       }
       final Product other = (Product) obj;
       return Objects.equals(this.tariffCode, other.tariffCode);
   }

   // this method allows a Product Object to represent 
   // inventory totals
   public Product add (Product detail) {
      this.quantity += detail.quantity;
      this.weight += detail.weight;
      this.price = price.add (detail.price);
      return this;
   }
}

You can add the getters and setters you need. I omitted the default constructor, i.e., public Product (). It would be a good idea to add it.

Note the class has a method public boolean equals (Object obj). That will be needed for searches. Code like if (inventory [i].tariffCode == target.tariffCode) won't work. See this question .

It also has a public int hashCode () method. hashCode should always be overridden when equals is. In fact, it will be needed for a version of this answer.

This class allows use of one array instead of 5:

 Product [] inventory; 

When you instantiate arrays, you want to know how many lines there will be in the file you are reading. How do you know that, unless you already read the file?

One way to avoid reading the file twice is to set some maximum value.

Here is what we have so far, using the Product class above:

public static void productSum1(String fileName) {
    try {
        FileReader dataFile = new FileReader(fileName);
        BufferedReader dataSource = new BufferedReader(dataFile);
         
        final int MAX_SIZE =  30_000;
        Product [] inventory = new Product [MAX_SIZE];
        int actualSize = 0;
        String line;
        String [] lineData; 
        Product lineItem;
        while ((line = dataSource.readLine() ) != null ) {
            lineData = line.split(" ");
            lineItem = new Product (  lineData [0]
                                    , Integer.parseInt(lineData[1])
                                    , Float.parseFloat(lineData[2])
                                    , new BigDecimal (lineData[3])
                                    , lineData [4]
            );
            
            actualSize = update (inventory, actualSize, lineItem);
        }
    } 
     // catch blocks 
}

public static int update (Product [] invent, int size, Product item) {
    // code to search array and update
    return size;
}

The code to search and update the array is left to you.

If the update method does not find a matching item in the inventory totals, it will add it to the array. This makes it necessary to update actualSize.

Note: Java is pass by value. Updating the local variable size in the update method will not result in an update of actualSize in the calling method. For this reason, update returns size.

One of the comments noted the code sample has import java.util.Scanner; but the code didn't use a Scanner. Scanner is a popular tool for extracting data from text sources. You could use a Scanner to wrap the file, and let Scanner methods parse the data for you.

You could have a constructor in the Product class like this:

public Product (Scanner dataSource) {
    this.name = dataSource.next();
    this.quantity = dataSource.nextInt ();
    this.weight = dataSource.nextFloat();
    this.price = dataSource.nextBigDecimal();
    this.tariffCode = dataSource.next ();
} 

The code that reads the file

public static void productSum1(String fileName) {
    try {
        FileReader dataFile = new FileReader(fileName);
        Scanner dataSource = new Scanner (dataFile);
         
        final int MAX_SIZE =  30_000;
        Product [] inventory = new Product [MAX_SIZE];
        int actualSize = 0;
        Product lineItem;
        while (dataSource.hasNext()) {
            lineItem = new Product (dataSource );                
            actualSize = update (inventory, actualSize, lineItem);
        }
        // rest of code omitted 

Instead of an array, consider using a Collection. Like an array, a Collection can hold a number of related elements. But, a Collection is more flexible than an array. A popular type of Collection is an ArrayList. Note that an ArrayList and other Collection Objects can change their own size.

Another popular Collection is a HashMap. When an search by key for a specific element is done on a HashMap, it is almost always done in O(1) time.

Add this method to class Product:

    public String getKey () { return tariffCode; }

The code to update the inventory:

public static void productSum2(String fileName) {
    try {
        FileReader dataFile = new FileReader(fileName);
        Scanner dataSource = new Scanner(dataFile);

        Map<String, Product> inventory = new HashMap<>();
        Product lineItem;
        while (dataSource.hasNext()) {
            lineItem = new Product(dataSource);
            update(inventory, lineItem);
        }
    } // insert catch blocks here
}

   
public static void update (Map<String, Product> inventory, Product item) {
    Product total = inventory.get (item.getKey());
    if (total == null) {
        inventory.put (item.getKey(), item);
    } else { 
        inventory.put (total.getKey (), total.add(item));
    }
}

You might not be familiar with HashMap, so I did include the code for the update method.

Note that HashMap relies on a proper implementation of .equals and .hashCode in the Objects it stores and retrieves.

I omitted code to output the results to either a file or a console. If you used the HashMap, you might find the .values() method useful.

Notes:

  • I did not attempt to run the code samples in this answer.
  • This is not robust. It cannot handle errors in the input file.
  • If you find errors in this answer, you may leave a comment or edit this answer.
Old Dog Programmer
  • 901
  • 1
  • 7
  • 9