I'm creating a feet and inches calculator. I want the user to be able to enter the information in various ways such as 1'-4-5/8" or 1 4 5/8.
When performing math, the above numbers will have to be converted to decimal (1'-4-5/8" is 16.625 in decimal inches). The final result will be in either decimal inches or then converted back to feet and inches.
How would I go about parsing the architectural measurement and converting it into decimal inches?
Any help would be greatly appreciated!
Thank you!
edit:
After way too much time and trying many different things, I think I got something that will work. I'm ultimately going to limit the way the user can enter the length so I think the following is going to work. It may not be optimized but it's the best I can get right now.
public class delim_test_cases {
public static void main(String[] args) {
double inches = 0;
double feet = 0;
double fract = 0;
String str = "2'-3-7/8";
String[] TempStr;
String delimiter = ("[-]+");
TempStr = str.split(delimiter);
for(int i=0; i< TempStr.length ; i++ ) {
for(int z=0; z< TempStr[i].length() ; z++ ) {
if (TempStr[i].charAt(z) == '\'') {
String[] FeetStr;
String feetdelim = ("[\']+");
FeetStr = TempStr[i].split(feetdelim);
feet = Integer.parseInt(FeetStr[0]);
}
else if (TempStr[i].charAt(z) == '/') {
String[] FracStr;
String fracdelim = ("[/]+");
FracStr = TempStr[i].split(fracdelim);
double numer = Integer.parseInt(FracStr[0]);
double denom = Integer.parseInt(FracStr[1]);
fract = numer/denom;
}
else if (TempStr[i].indexOf("\'")==-1 && TempStr[i].indexOf("/")==-1) {
String inchStr;
inchStr = TempStr[i];
inches = Integer.parseInt(inchStr);
}
}
}
double answer = ((feet*12)+inches+fract);
System.out.println(feet);
System.out.println(inches);
System.out.println(fract);
System.out.println(answer);
}
}
Why not just split on either the '-' or a space, then you have an array of possibly 3 strings.
So, you look at the last character, if it is a single quote, then multiply that by 12.
If a double quote, then split on '/' and just calculate the fraction, and for a number that is neither of these it is an inch, just add it to the total.
This way you don't assume the order or what if you just have fractional inches.
Just loop through the array and then go through the algorithm above, so if someone did
3'-4'-4/2" then you can calculate it easily.
UPDATE:
I am adding code based on the comment from the OP.
You may want to refer to this to get more ideas about split.
http://www.java-examples.com/java-string-split-example
But, basically, just do something like this (not tested, just written out):
public double convertArchToDecimal(String instr, String delimiter) {
String[] s = instr.split(delimiter);
int ret = 0;
for(int t = 0; t < s.length; t++) {
char c = s[t].charAt(s[t] - length);
switch(c) {
case '\'': //escape single quote
String b = s[t].substring(0, s[t].length - 1).split("/");
try {
ret += Integer.parse(s[t].trim()) * 12;
} catch(Exception e) { // Should just need to catch if the parse throws an error
}
break;
case '"': // may need to escape the double quote
String b = s[t].substring(0, s[t].length - 1).split("/");
int f = 0;
int g = 0;
try {
f = Integer.parse(b[0]);
g = Integer.parse(b[1]);
ret += f/g;
} catch(Exception e) {
}
break;
default:
try {
ret += Integer.parse(s[t].trim());
} catch(Exception e) { // Should just need to catch if the parse throws an error
}
break;
}
}
return ret;
}
I am catching more exceptions than is needed, and you may want to use the trim() method more than I did, to strip whitespace before parsing, to protect against 3 / 5 " for example.
By passing in a space or dash it should suffice for your needs.
You may also want to round to some significant figures otherwise rounding may cause problems for you later.
Related
I am creating a physics calculator and currently working on average velocity. For displacement I would like the user to input a string i.e. "5km north". Then using a method assign only the numeric values in the string to be doubles using Double.parseDouble or something of the like.
Below is my method to pull out numeric values
private double getNumericalValue(String userInput) {
double numericalOutput = 0.0;
for (int i = 0; i < userInput.length(); i++) {
if (userInput.charAt(i) >= 65 && userInput.charAt(i) <= 122) {
numericalOutput = Double.parseDouble(userInput.substring(0, i));
}
}
return numericalOutput;
}
and lastly the getAverageVelocity method
private void getAverageVelocity() {
// formula average velocity = displacement s / change in time t
double avgVelocity = 0.0;
System.out.println("Enter the displacement: [ex. 5 km north]");
String displacement = getStringInput();
System.out.println("Enter the change in time: [ex. 1 hour]");
String changeInTime = getStringInput();
// parse the string and change numerical input into double
double numericalS = getNumericalValue(displacement);
double numericalT = getNumericalValue(changeInTime);
avgVelocity = numericalS / numericalT;
System.out.println("The Average Velocity is: " + avgVelocity);
}
As you can see I figured I could simply compare chars to their ascii value. However, it is including the k or a space in my double output which is throwing this error.
Exception in thread "main" java.lang.NumberFormatException: For input string: "5 k"
at java.base/jdk.internal.math.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:2054)
at java.base/jdk.internal.math.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
at java.base/java.lang.Double.parseDouble(Double.java:556)
at Main.getNumericalValue(Main.java:122)
at Main.getAverageVelocity(Main.java:393)
at Main.kineticsIn1DCalculator(Main.java:375)
at Main.performAction(Main.java:141)
at Main.runner(Main.java:29)
at Main.main(Main.java:22)
Any help and a thorough explanation would be great. Thanks.
What you are actually testing in the if block is whether the character at i is a letter A-Z, a-z and other symbols(i.e. [ ^ ], ...etc) to check if it's a number (i.e. 0-9) the corresponding decimal range is 48-57 check this link
as for the exception it's because of substring(0, 0) will result in an empty string, and when trying to parse it, Double.parseDouble() will throw an exception.
here is a solution you can use:
private double getNumericalValue(String userInput) {
double numericalOutput = 0.0;
for (int i = 0; i < userInput.length(); i++) {
if (userInput.charAt(i) >= 48 && userInput.charAt(i) <= 57) {
numericalOutput = Double.parseDouble(userInput.substring(0, i + 1));
}
}
return numericalOutput;
}
Note: substring(i, j) i is inclusive, j exclusive
Update :
just noticed, in your case the exception happens because you are trying to parse the letter "k" since it passes the condition, resulting in Double.parseDouble() throwing an exception.
You have the error because even if the first number is found the for loop continues. You simply have to add break instruction, then the for loop ends at the first number found.
This is the final code of the method getNumericalValue:
private double getNumericalValue(String userInput) {
double numericalOutput = 0.0;
for (int i = 0; i < userInput.length(); i++) {
if (userInput.charAt(i) >= 65 && userInput.charAt(i) <= 122) {
numericalOutput = Double.parseDouble(userInput.substring(0, i));
break;
}
}
return numericalOutput;
}
You simply should input the line and parse its parts.
Best to introduce a class to hold a value with unit, where "km north" counts as a unit.
Since java 14 there exists the record class.
record ValueWithUnit(double value, String unit) {
public static ValueWithUnit from(String s) {
String v = s.replaceFirst("^(.*?)([ A-z].*)$", "$1");
String u = s.replaceFirst("^(.*?)([ A-z].*)$", "$2");
if (u == v) {
u = "";
}
double x = Double.parseDouble(v); // Replaces getNumericalValue(v);
return new Value(x, u);
}
#Override
public String toString() {
String.format("%f %s", value, unit);
}
}
You could use it directly as:
ValueWithUnit vu = new ValueWithUnit(30.0, "km");
System.out.println(vu);
The code then becomes:
private void getAverageVelocity() {
// formula average velocity = displacement s / change in time t
double avgVelocity = 0.0;
System.out.println("Enter the displacement: [ex. 5 km north]");
ValueWithUnit displacement = ValueWithUnit.from(getStringInput());
System.out.println("Enter the change in time: [ex. 1 hour]");
ValueWithUnit changeInTime = ValueWithUnit.from(getStringInput());
// parse the string and change numerical input into double
double numericalS = displacement.value;
double numericalT = changeInTime.value;
avgVelocity = numericalS / numericalT;
System.out.printf("The Average Velocity is: %f %s%n", avgVelocity,
displacement.unit + "/" + changeInTime.unit);
}
I am learning Java and wonder how I can get two numbers in same line.
Is this algorithm is okay, what can I do improve? What can you suggest me?
import java.util.Scanner;
public class Main{
public static int Separate(String Values, int Order){
String toReturn = "";
int Counter = 0;
for(int Iterator = 0; Iterator < Values.length(); Iterator = Iterator + 1){
if(Values.charAt(Iterator) == ' ') {
if(Order == Counter) break;
else{
toReturn = "";
Counter = Counter + 1;
}
}
else toReturn += Values.charAt(Iterator);
}
return Integer.parseInt(toReturn);
}
public static void main(String[] args){
Scanner Entry = new Scanner(System.in);
System.out.print("Enter two numbers separated by space: ");
String Number = Entry.nextLine();
int Frst = Separate(Number, 0);
int Scnd = Separate(Number, 1);
}
}
what can I do improve? What can you suggest me?
Adopt the Java Naming Conventions:
Method Names are camelCase, starting with a lower case letter
Field and Property Names and Method Argument Names are camelCase, too
Basically only Class and Interface Names start with an upper case letter in Java.
public static int separate(String values, int order){
String toReturn = "";
int counter = 0;
for(int iterator = 0; ...) { ...
Else I'd say: This algorithm is pretty solid for a beginner. It's easy to understand what's going on.
Of course Java provides much more sophisticated tools to solve this, using for example Regular Expressions with myString.split(...), or Streams with IntStream intStream = myString.chars().
Last but not least you could add Exception Handling: What happens if Integer.parseInt is given some non-number? It will crash.
try {
return Integer.parseInt(toReturn);
} catch (NumberFormatException e) {
// when "toReturn" cannot be parsed to an int, return a
// default value instead of crashing your application
return 0;
}
Or if crashing is the desired behavior, or you can ensure that this method is never called with an illegal String, leave it as it is (= don't add try catch)
I think what you've done is great for well-formatted input, where you have a single space character between the numbers. As others have pointer out, following Java naming conventions will greatly improve the readability of your code.
Handling sequences of space characters, possible before, between, and after your numbers is a little tricky. The general pattern would be to consume any sequences of spaces, remember the current position, consume the sequence of digits, then if we're at the correct position return the parsed number.
public static int separate(String str, int order)
{
for(int i = 0, pos = 0; ; pos++)
{
while(i < str.length() && str.charAt(i) == ' ') i += 1;
int j = i;
while(i < str.length() && str.charAt(i) != ' ') i += 1;
if(i == j) throw new IllegalStateException("Missing number!");
if(order == pos)
{
// handle NumberFormatException
return Integer.parseInt(str.substring(j, i));
}
}
}
Test:
String s = " 23432 798 44";
for(int i=0; i<3; i++)
System.out.print(separate(s, i) + " ");
Output:
23432 798 44
What are the best practices in java for extracting number from text and parse it?
for example:
String s = "Availability in 20 days";
Please don't stick just to the example I am looking for a good general practice and scenarios.
Thank you.
I am not sure of the best practices, but one way that I would do it described in this stack overflow questions.
How to extract numbers from a string and get an array of ints?
Using regular expression:
Pattern p = Pattern.compile("-?\\d+");
Matcher m = p.matcher("Availability in 20 days");
while (m.find()) {
int number = Integer.parseInt(m.group());
...
}
I'm not exactly sure what you want to do, but here are some solutions that might help you:
List item use .indexOf and .substring to find numbers in the string
example:
String s;
String str = new String("1 sentence containing 5 words and 3 numbers.");
ArrayList<Integer> integers = new ArrayList<Integer>();
for (int i = 0; i <= 9; i++) {
int start = 0;
while (start != -1) {
String sub = str.substring(start);
int x = sub.indexOf(i);
if (x != -1) {
s = sub.substring(x, x+1);
integers.add(Integer.parseInt(s));
start = x;
} else {
//number not found
start = -1;
}
}
}
extract one character at a time and try to parse it, if there's no exception, it is a number. I DEFINITELY DON'T RECOMMEND THIS SOLUTION, but it should also work. Unfortunately, I can't tell you which method is quicker, but I can imagine that - although there are fewer commands - the second version is slower, considering that there's several exceptions thrown.
String s;
int integ;
ArrayList<Integer> integers = new ArrayList<Integer>();
String str = new String("1 sentence containing 5 words and 3 numbers.");
for (int i = 0; i < str.length(); i++) {
s = str.substring(i,i+1);
try {
integ = Integer.parseInt(s);
integers.add(integ);
} catch (NumberFormatException nfe) {
//nothing
}
}
How about regex + replaceAll?
code:
String after = str.replaceAll("\\D+", "");
if there are serveral numbers then
String[] after = str.replaceAll("\\D+", " ").split("\\s+");
Thanks in advance.
I just solved Project Euler #22, a problem involving reading about 5,000 lines of text out of a file and determining the value of a specific name, based on the sum of that Strings characters, and its position alphabetically.
However, the code takes about 5-10 seconds to run, which is a bit annoying. What is the best way to optimize this code? I'm currently using a Scanner to read the file into a String. Is there another, more efficient way to do this? (I tried using a BufferedReader, but that was even slower)
public static int P22(){
String s = null;
try{
//create a new Scanner to read file
Scanner in = new Scanner(new File("names.txt"));
while(in.hasNext()){
//add the next line to the string
s+=in.next();
}
}catch(Exception e){
}
//this just filters out the quotation marks surrounding all the names
String r = "";
for(int i = 0;i<s.length();i++){
if(s.charAt(i) != '"'){
r += s.charAt(i);
}
}
//splits the string into an array, using the commas separating each name
String text[] = r.split(",");
Arrays.sort(text);
int solution = 0;
//go through each string in the array, summing its characters
for(int i = 0;i<text.length;i++){
int sum = 0;
String name = text[i];
for(int j = 0;j<name.length();j++){
sum += (int)name.charAt(j)-64;
}
solution += sum*(i+1);
}
return solution;
}
If you're going to use Scanner, why not use it for what it's supposed to do (tokenisation)?
Scanner in = new Scanner(new File("names.txt")).useDelimiter("[\",]+");
ArrayList<String> text = new ArrayList<String>();
while (in.hasNext()) {
text.add(in.next());
}
Collections.sort(text);
You do not need to strip quotes, or split on commas - Scanner does it all for you.
This snippet, including java startup time, executes in 0.625s (user time) on my machine. I suspect it should be a bit faster than what you were doing.
EDIT OP asked what the string passed to useDelimiter was. It's a regular expression. When you strip out the escaping required by Java to include a quote character into a string, it's [",]+ - and the meaning is:
[...] character class: match any of these characters, so
[",] match a quote or a comma
...+ one or more occurence modifier, so
[",]+ match one or more of quotes or commas
Sequences that would match this pattern include:
"
,
,,,,
""",,,",","
and indeed ",", what was what we were going after here.
I suggest you to run your code with profiler. It allows you to understand, what part is really slow (IO/computations etc). If IO is slow, check for NIO: http://docs.oracle.com/javase/1.4.2/docs/guide/nio/.
Appending strings in a loop with '+', like you do here:
/* That's actually not the problem since there is only one line. */
while(in.hasNext()){
//add the next line to the string
s+=in.next();
}
is slow, because it has to create a new string and copy everything around in each iteration. Try using a StringBuilder,
StringBuilder sb = new StringBuilder();
while(in.hasNext()){
sb.append(in.next());
}
s = sb.toString();
But, you shouldn't really read the file contents into a String, you should create a String[] or an ArrayList<String> from the file contents directly,
int names = 5000; // use the correct number of lines in the file!
String[] sa = new String[names];
for(int i = 0; i < names; ++i){
sa[i] = in.next();
}
However, upon checking, it turns out that the file does not contain about 5000 lines, rather, it is all on a single line, so your big problem is actually
/* This one is the problem! */
String r = "";
for(int i = 0;i<s.length();i++){
if(s.charAt(i) != '"'){
r += s.charAt(i);
}
}
Use a StringBuilder for that. Or, make your Scanner read until the next ',' and read directly into an ArrayList<String> and just remove the double quotes from each single name in the ArrayList.
5+ seconds is quite slow for this problem. My entire web application (600 Java classes) compiles in four seconds. The root of your problem is probably the allocation of a new String for every character in the file: r += s.charAt(i)
To really speed this up, you should not use Strings at all. Get the file size, and read the whole thing into a byte array in a single I/O call:
public class Names {
private byte[] data;
private class Name implements Comparable<Name> {
private int start; // index into data
private int length;
public Name(int start, int length) { ...; }
public int compareTo(Name arg0) {
...
}
public int score()
}
public Names(File file) throws Exception {
data = new byte[(int) file.length()];
new FileInputStream(file).read(data, 0, data.length);
}
public int score() {
SortedSet<Name> names = new ...
for (int i = 0; i < data.length; ++i) {
// find limits of each name, add to the set
}
// Calculate total score...
}
}
Depending on the application, StreamTokenizer is often measurably faster than Scanner. Examples comparing the two may be found here and here.
Addendum: Euler Project 22 includes deriving a kind of checksum of the characters in each token encountered. Rather than traversing the token twice, a custom analyzer could combine the recognition and calculation. The result would be stored in a SortedMap<String, Integer> for later iteration in finding the grand total.
An obtuse solution which may find interesting.
long start = System.nanoTime();
long sum = 0;
int runs = 10000;
for (int r = 0; r < runs; r++) {
FileChannel channel = new FileInputStream("names.txt").getChannel();
ByteBuffer bb = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size());
TLongArrayList values = new TLongArrayList();
long wordId = 0;
int shift = 63;
while (true) {
int b = bb.remaining() < 1 ? ',' : bb.get();
if (b == ',') {
values.add(wordId);
wordId = 0;
shift = 63;
if (bb.remaining() < 1) break;
} else if (b >= 'A' && b <= 'Z') {
shift -= 5;
long n = b - 'A' + 1;
wordId = (wordId | (n << shift)) + n;
} else if (b != '"') {
throw new AssertionError("Unexpected ch '" + (char) b + "'");
}
}
values.sort();
sum = 0;
for (int i = 0; i < values.size(); i++) {
long wordSum = values.get(i) & ((1 << 8) - 1);
sum += (i + 1) * wordSum;
}
}
long time = System.nanoTime() - start;
System.out.printf("%d took %.3f ms%n", sum, time / 1e6);
prints
XXXXXXX took 27.817 ms.
I have a number as a string like this: "9.756088256835938E-4" but I only can use a specified number of characters (in this special case 9 char). So I want to have something like this: "9.7561E-4". I already tried to convert the String to a Double and then used the format method to get a less characters but I don't got a correct solution.
The problem is that I need ten exponential output since some numbers are longer than the number of characters I have. If it is possible, the number should be displayed with no ten exponent, if not just use the ten exponent.
Also correct rounding would be good.
And it should work for negative numbers. (minus needs one character!!!)
Is there a format function where I can define the maximum length of the output string? Any ideas?
I'm having trouble findind a single format pattern that will cover all of the cases that you described. But here's a combination of logic that I think works:
public static void main(String[] args) throws Exception {
// 97.560883
System.out.println(formatNum(Double.parseDouble("9.756088256835938E+1")));
// 9.756E+11
System.out.println(formatNum(Double.parseDouble("9.756088256835938E+11")));
// 0.0009756
System.out.println(formatNum(Double.parseDouble("9.756088256835938E-4")));
// -9.8E+111
System.out.println(formatNum(Double.parseDouble("-9.756088256835938E+111")));
}
private static final int MAX_LENGTH = 9;
private static String formatNum(double number) {
String out = null;
for ( int i = 0; i < MAX_LENGTH; i++ ) {
String format = "%." + i + "G";
out = String.format(format, number);
if ( out.length() == MAX_LENGTH ) {
return out;
}
}
return out; //the best we can do
}
The "G" in the pattern instructs the formatter to forego the use of the exponent when it will allow for the same or better precision. We grow up to the maximum length and stop when our output string is 10 characters. I think you could take the same approach with a DecimalFormat, but I'm more familiar with Formatter.
Seeing the Mark's example meet your requirements, I updated my answer to show the DecimalFormat implementation. I used Mark's test cases. It is definitely an uglier option because there is no easy way to turn on/off exponents. The only advantage over the String.format option is that it handles very small numbers well.
public static void main(String[] args) throws Exception {
// 97.560883
System.out.println(formatNum(Double.parseDouble("9.756088256835938E+1")));
// 9.756E+11
System.out.println(formatNum(Double.parseDouble("9.756088256835938E+11")));
// 0.0009756
System.out.println(formatNum(Double.parseDouble("9.756088256835938E-4")));
// -9.8E+111
System.out.println(formatNum(Double.parseDouble("-9.756088256835938E+111")));
}
private static final int MAX_LENGTH = 9;
private static String formatNum(double number) {
int digitsAvailable = MAX_LENGTH - 2;
if (Math.abs(number) < Math.pow(10, digitsAvailable)
&& Math.abs(number) > Math.pow(10, -digitsAvailable)) {
String format = "0.";
double temp = number;
for (int i = 0; i < digitsAvailable; i++) {
if ((temp /= 10) < 1) {
format += "#";
}
}
return new DecimalFormat(format).format(number);
}
String format = "0.";
for (int i = 0; i < digitsAvailable; i++) {
format += "#";
}
String r = new DecimalFormat(format + "E0").format(number);
int lastLength = r.length() + 1;
while (r.length() > MAX_LENGTH && lastLength > r.length()) {
lastLength = r.length();
r = r.replaceAll("\\.?[0-9]E", "E");
}
return r;
}
This reminded me of a similar question where the OP only had 5 or so spaces for a number and wanted to show a decimal only when there was enough space. But instead of exponents, wanted to use a suffix of (k,m, etc)