Java - Most efficient way to convert string to double - java

Hi I am reading from a text file and saving each line (split by a comma) into an array. The only problem is that most of the elements in the array are double values where as two elements are strings. As a result of this I had to make the array a String[] array. Due to this, whenever I want to perform some equations on the double values in the array, I have to first parse them as a double value. I am literally running 1000+ iterations of these equations, therefore my code is constantly parsing the strings into a double. This is a costly way which is slowing down my program. Is there a better way I can convert the values from the string array to double values or is there a better approach I should take when saving the lines from the text file? Thanks
Here is what one of the arrays looks like after I have read from the text file:
String[] details = {"24.9", "100.0", "19.2" , "82.0", "Harry", "Smith", "45.0"};
I now need to multiply the first 2 elements and add that to the sum of the 3rd, 4th and 7th elements. In other words I am only using the numerical elements (that are ofcourse saved as strings)
double score = (Double.parseDouble(details[0]) * Double.parseDouble(details[1])) + Double.parseDouble(details[2]) + Double.parseDouble(details[3]) + Double.parseDouble(details[6]);
I have to do this for every single line in the text file (1000+ lines). As a result of this my program is running very slowly. Is there a better way I can convert the string values into a double? or is there a better way I should go about storing them in the first place?
EDIT: I have used profiler to check which part of the code is the slowest and it is indeed the code that I have shown above

Here's an example of generating an input file like the one you describe that's 10000 lines long, then reading it back in and doing the calculation you posted and printing the result to stdout. I specifically disable any buffering when reading the file in order to get the worst possible read performance. I'm also not doing any caching at all, as others have suggested. The entire process, including generating the file, doing the calculation, and printing the results, consistently takes around 520-550 ms. That's hardly "slow", unless you're repeating this same process for hundreds or thousands of files. If you see drastically different performance from this, then maybe it's a hardware problem. A failing hard disk can drop read performance to nearly nothing.
import java.io.*;
import java.util.Random;
public class ReadingDoublesFromFileEfficiency {
private static Random random = new Random();
public static void main(String[] args) throws IOException {
long start = System.currentTimeMillis();
String filePath = createInputFile();
BufferedReader reader = new BufferedReader(new FileReader(filePath), 1);
String line;
while ((line = reader.readLine()) != null) {
String[] details = line.split(",");
double score = (Double.parseDouble(details[0]) * Double.parseDouble(details[1])) + Double.parseDouble(details[2]) + Double.parseDouble(details[3]) + Double.parseDouble(details[6]);
System.out.println(score);
}
reader.close();
long elapsed = System.currentTimeMillis() - start;
System.out.println("Took " + elapsed + " ms");
}
private static String createInputFile() throws IOException {
File file = File.createTempFile("testbed", null);
PrintWriter writer = new PrintWriter(new FileWriter(file));
for (int i = 0; i < 10000; i++) {
writer.println(randomLine());
}
writer.close();
return file.getAbsolutePath();
}
private static String randomLine() {
return String.format("%f,%f,%f,%f,%s,%s,%f",
score(), score(), score(), score(), name(), name(), score());
}
private static String name() {
String name = "";
for (int i = 0; i < 10; i++) {
name += (char) (random.nextInt(26) + 97);
}
return name;
}
private static double score() {
return random.nextDouble() * 100;
}
}

You'd do better to create a proper object and store the values in that - this gives you two major benefits, 1) your code will be faster since you avoid needlessly recomputing double values and 2) your code will be clearer, since the fields will be named rather than making calls like details[0] where it's completely unclear what [0] is referring to.
Due to 2) I don't know what the fields are supposed to be, so obviously your class will look different, but the idea's the same:
public class PersonScore {
private double[] multipliers = new double[2];
private double[] summers = new double[3];
private String first;
private String last;
// expects a parsed CSV String
public PersonScore(String[] arr) {
if(arr.length != 7)
throw new InvalidArgumentException("Must pass exactly 7 fields");
multipliers[0] = Double.parseDouble(arr[0]);
multipliers[1] = Double.parseDouble(arr[1]);
summers[0] = Double.parseDouble(arr[2]);
summers[0] = Double.parseDouble(arr[3]);
summers[0] = Double.parseDouble(arr[6]);
first = arr[4];
last = arr[5];
}
public double score() {
double ret = 1;
for(double mult : multipliers)
ret *= mult;
for(double sum : summers)
ret += sum;
return ret;
}
public String toString() {
return first+" "+last+": "+score();
}
}
Notice there's an additional benefit, that the score method is now more robust. Your implementation above hard-coded the fields we wanted to use, but by parsing and storing the fields as structure content, we're able to implement a more readable, more scalable score calculation method.

Related

Is there a way to concatenate Java strings in less than O(n) time?

My homework question involves joining strings in a particular sequence. We are first given the strings, followed by a set of instructions that tell us how to concatenate them; finally we print the output string.
I have used the Kattis FastIO class to handle buffered input and output. Below is my algorithm, which iterates through the instructions to concatenate the strings. I have tried making the array of normal strings, StringBuffers and StringBuilders.
The program seems to work as intended, but it gives a time limit error on my submission platform due to inefficiency. It seems like appending the way I did is O(n); is there any faster way?
public class JoinStrings {
public static void main(String[] args) {
Kattio io = new Kattio(System.in, System.out);
ArrayList<StringBuilder> stringList = new ArrayList<StringBuilder>();
int numStrings = io.getInt();
StringBuilder[] stringArray = new StringBuilder[numStrings];
for (int i = 0; i < numStrings; i++) {
String str = io.getWord();
stringArray[i] = new StringBuilder(str);
}
StringBuilder toPrint = stringArray[0];
while (io.hasMoreTokens()) {
int a = io.getInt();
int b = io.getInt();
stringArray[a-1].append(stringArray[b-1]); // this is the line that is done N times
toPrint = stringArray[a-1];
}
io.println(toPrint.toString());
io.flush();
}
}
The StringBuilder.append() copy char from new string to existing string. It's fast but not free.
Instead of keeping appending the String to the StringBuilder array, keep track of the String indexes need to appended. Then finally append the Strings stored in the print out indexes list.

java.lang.StringIndexOutOfBoundsException Error while reading Binary String

I have a long String with binary values. And i have a hash map that has the Binary digits as a key and char as a value. I have a function that supposed to read the binary string using 2 pointers and compare with hashmap and store the corresponding char in main.decodedTxt. However, im getting string out of bound exception for this. I don't know how to solve this. I'm getting exception on "String temp =" line. I have a picture link of the console output to see better picture.
public static void bitStringToText (String binText){
String bs = binText;
int from =0;
int to = 1;
while(bs != null){
String temp = bs.substring(from, to);
if (main.newMapDecoding.containsKey(temp)){
main.decodedTxt += main.newMapDecoding.get(temp);
from =to;
to = from +1;
} else {
to = to + 1;
}
}
}
Image of console exception is here
First of all there is no need to check if bs is null because no part of your code changes the value of bs. Your current code will cross the possible index of your binText at some point. It's better to loop just binText and check if you find something within it. After all you have to traverse the complete string anyways. Change your code as follows
public static void bitStringToText (String binText){
//no need to do this if you are not modifying the contents of binText
//String bs = binText;
int from =0;
int to = 1;
int size = binText.length();
String temp = "";
while(to <= size ){
temp = binText.substring(from, to);
if (main.newMapDecoding.containsKey(temp)){
main.decodedTxt += main.newMapDecoding.get(temp);
from =to;
to = from +1;
} else {
to = to + 1;
}
}
}
Hope it helps.
First, give it a try to practice debugging. It is an easy case. Either use run in debug mode (place break point on String temp = bs.substring(from, to); line) or print values of from and to before the same line. It will help to understand what is going on.
Solution:
If bs is not null you will always have StringIndexOutOfBoundsException. Because you are not checking if to is pointing to not existed index of bs String. Easiest example of the first one will be empty String: bs == "".
One of the solution could be to replace condition in while to while (to <= bs.length()).

How to import .dat file into multiple arrays

Alright so I'm working on a program that reads a periodic table and you can search elements based on number or abbreviation.
Anyway, I'm a bit stuck trying to read the periodic table file into 4 different arrays: Atomic Number, Abbreviation, Element Name, and Atomic Weight.
I dunno how to write a single method to import all that info into each array in one go. I want to have a class that holds all these arrays and that I can call to later when I need each one.
Here is what I got so far, I'm a bit rusty by the way... I thought working on this program would refamiliarize me with the basics.
class PeriodicTable{
private String fileName = "periodictable.dat";
private int[] atomicNumTable = new int[200];
private String[] abbreviationTable = new String[200];
private String[] nameTable = new String[200];
private double[] atomicWeightTable = new double[200];
PeriodicTable(String fileName){
readTable(fileName);
}
public int[] readTable(String fileName){
Scanner inFile = null;
try{
inFile = new Scanner(new File(fileName));
}catch(FileNotFoundException nf){
System.out.println(fileName + " not found");
System.exit(0);
}
atomicNumTable = new int[200];
int i = 0;
while(inFile.hasNext() && i < atomicNumTable.length){
int number = inFile.nextInt();
atomicNumTable[i] = number;
i++;
}
inFile.close();
return atomicNumTable;
}
}
Here is what each line of the table looks like:
1 H Hydrogen 1.00794
Simply use java.lang.String.split(' ') (assuming that your columns are separated using spaces; if it is using something else; you just need to adapt that regular expression parameter!)
That will return an array of Strings; and you basically now: first column should be an int, then you got two Strings, and then a double value. Or lets be precise: you get strings, that mean something else; thus you have to look into methods like Integer.valueOf(); and similar for Double.
Shouldn't be too hard to work your way from there.
But I recommend some changes to your logic: having 4 different tables doesn't make sense at all. Good OO programming is about creating helpful abstractions. Without abstractions, your program becomes abstract itself.
Meaning: you should introduce a class like
public class Element {
private final int id;
private final String abbreviation;
private final String fullName;
private final double atomicWeight;
... with one constructor that takes all 4 parameters
... with getter methods for the fields of this class
... and meaningful overrides for equals() and hashcode()
}
And then, instead of creating 4 arrays; you create one array, or even better an ArrayList<Element>. And instead of pushing your 4 values into 4 different arrays, you create one new Element object in each loop iteration; and you add that new object to your list.
The major difference to your solution would be: you can deal with Elements as a whole; whereas in your solution, a single "Element" is basically an index that points into 4 different tables.
You can simplify this code a lot. Try something like this.
1) Read the file line by line, split lines as you go,
add values to some ArrayList containing String[]
2) Close your file
3) Turn the ArrayList into a String[][]
4) Print the result
Also, note that arrays in Java are indexed starting at 0 not at 1.
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.util.ArrayList;
import java.util.Arrays;
public class Test {
static public void main(String[] args) throws Exception {
File file = new File("periodictable.dat");
FileReader reader = new FileReader(file);
BufferedReader buffReader = new BufferedReader(reader);
String s = null;
ArrayList<String[]> lst = new ArrayList<String[]>();
String[][] res = null;
while((s = buffReader.readLine()) != null){
String[] arr = s.split("[\\s]+");
lst.add(arr);
}
buffReader.close();
res = new String[lst.size()][lst.get(0).length];
res = lst.toArray(res);
System.out.println();
// System.out.println(res);
// String result = Arrays.deepToString(res);
// System.out.println(result);
System.out.println();
for (int i=0; i<res.length; i++){
for (int j=0; j<res[i].length; j++){
System.out.println("res[" + (i+1) + "][" + (j+1) + "]=" + res[i][j]);
}
}
System.out.println();
}
}
OUTPUT:
res[1][1]=1
res[1][2]=H
res[1][3]=Hydrogen
res[1][4]=1.00794
value iterates indexing for each line
You can distinguish four cases in the loop:
i%4 == 0
i%4 == 1
i%4 == 2
i%4 == 3
Depending on this you know the kind of next value you have to read. So, you can search you an integer, string or floating point number and put the value in the right place.
I support the recommendation of GhostCat to only have one array and a class that contains all four values of a line instead of having four arrays.

Reading a data file and processing the information, with two values on each line

I have to produce a program that can read a file I have been given. The file contains 365 lines of data that is supposed to resemble temperature values. The first value on each line is the low temp, and the second is the high temp. From these values, I have to determine the number of days, the lowest of the low temps, highest of the max temps, and then the average of the lows and the average of the highs.
Here is what I have so far. The problem I am having is splitting the two values on each line to deal with them individually so I can turn them into a double so I can do computations on them
import TextIO.*;
public class Temperature {
public static void main(String[] args) {
TextIO.readFile("temperatures.dat");
//double salesTotal; // Total of all sales figures seen so far.
int dayCount, string1, a;
double tempTotal=0;
String dataString; // Number of cities for which data is missing.
dayCount = 0;
while ( ! TextIO.eof() ) { // process one line of data.
dayCount++;
dataString = TextIO.getln(); // Get the rest of the line.
a = dataString.indexOf(" ");
TextIO.put("\n" + a);
}
TextIO.put(dayCount);
}
}
I get the feeling your question comes from a homework assignment so I wont take all the fun out of it but... What you are looking at is most likely a tab represented by\t in java.
You have so far:
dataString = TextIO.getln();
You will then need to separate those 2 numbers, there are several ways to do this but Ill take your lead and use the indexOf method.
String low = dataString.substring(0, dataString.indexOf("\t"));
String high= dataString.substring(dataString.indexOf("\t")+1);
you then need to convert those strings to numbers, but im sure you can figure that out :).
If you ever find yourself wonder what is that character you can do something like this
public static void printStringChars(String str) {
for(int i=0;i<str.length();i++)
System.out.println("(" + str.charAt(i) + ") " + (int)str.charAt(i));
}
Then go look up the number it prints out in an ASCII table such as this one http://www.asciitable.com/

Remove chars from string in Java from file

How would I remove the chars from the data in this file so I could sum up the numbers?
Alice Jones,80,90,100,95,75,85,90,100,90,92
Bob Manfred,98,89,87,89,9,98,7,89,98,78
I want to do this so for every line it will remove all the chars but not ints.
The following code might be useful to you, try running it once,
public static void main(String ar[])
{
String s = "kasdkasd,1,2,3,4,5,6,7,8,9,10";
int sum=0;
String[] spl = s.split(",");
for(int i=0;i<spl.length;i++)
{
try{
int x = Integer.parseInt(spl[i]);
sum = sum + x;
}
catch(NumberFormatException e)
{
System.out.println("error parsing "+spl[i]);
System.out.println("\n the stack of the exception");
e.printStackTrace();
System.out.println("\n");
}
}
System.out.println("The sum of the numbers in the string : "+ sum);
}
even the String of the form "abcd,1,2,3,asdas,12,34,asd" would give you sum of the numbers
You need to split each line into a String array and parse the numbers starting from index 1
String[] arr = line.split(",");
for(int i = 1; i < arr.length; i++) {
int n = Integer.parseInt(arr[i]);
...
try this:
String input = "Name,2,1,3,4,5,10,100";
String[] strings = input.split(",");
int result=0;
for (int i = 1; i < strings.length; i++)
{
result += Integer.parseInt(strings[i]);
}
You can make use of the split method of course, supplying "," as the parameter, but that's not all.
The trick is to put each text file's line into an ArrayList. Once you have that, move forwars the Pseudocode:
1) Put each line of the text file inside an ArrayList
2) For each line, Split to an array by using ","
3) If the Array's size is bigger than 1, it means there are numbers to be summed up, else only the name lies on the array and you should continue to the next line
4) So the size is bigger than 1, iterate thru the strings inside this String[] array generated by the Split function, from 1 to < Size (this will exclude the name string itself)
5) use Integer.parseInt( iterated number as String ) and sum it up
There you go
Number Format Exception would occur if the string is not a number but you are putting each line into an ArrayList and excluding the name so there should be no problem :)
Well, if you know that it's a CSV file, in this exact format, you could read the line, execute string.split(',') and then disregard the first returned string in the array of results. See Evgenly's answer.
Edit: here's the complete program:
class Foo {
static String input = "Name,2,1,3,4,5,10,100";
public static void main(String[] args) {
String[] strings = input.split(",");
int result=0;
for (int i = 1; i < strings.length; i++)
{
result += Integer.parseInt(strings[i]);
}
System.out.println(result);
}
}
(wow, I never wrote a program before that didn't import anything.)
And here's the output:
125
If you're not interesting in parsing the file, but just want to remove the first field; then split it, disregard the first field, and then rejoin the remaining fields.
String[] fields = line.split(',');
StringBuilder sb = new StringBuilder(fields[1]);
for (int i=2; i < fields.length; ++i)
sb.append(',').append(fields[i]);
line = sb.toString();
You could also use a Pattern (regular expression):
line = line.replaceFirst("[^,]*,", "");
Of course, this assumes that the first field contains no commas. If it does, things get more complicated. I assume the commas are escaped somehow.
There are a couple of CsvReader/Writers that might me helpful to you for handling CSV data. Apart from that:
I'm not sure if you are summing up rows? columns? both? in any case create an array of the target sum counters int[] sums(or just one int sum)
Read one row, then process it either using split(a bit heavy, but clear) or by parsing the line into numbers yourself (likely to generate less garbage and work faster).
Add numbers to counters
Continue until end of file
Loading the whole file before starting to process is a not a good idea as you are doing 2 bad things:
Stuffing the file into memory, if it's a large file you'll run out of memory (very bad)
Iterating over the data 2 times instead of one (probably not the end of the world)
Suppose, format of the string is fixed.
String s = "Alice Jones,80,90,100,95,75,85,90,100,90,92";
At first, I would get rid of characters
Matcher matcher = Pattern.compile("(\\d+,)+\\d+").matcher(s);
int sum = 0;
After getting string of integers, separated by a comma, I would split them into array of Strings, parse it into integer value and sum ints:
if (matcher.find()){
for (String ele: matcher.group(0).split(",")){
sum+= Integer.parseInt(ele);
}
}
System.out.println(sum);

Categories