0

I am working on an exercise with the following criteria:

"The input consists of pairs of tokens where each pair begins with the type of ticket that the person bought ("coach", "firstclass", or "discount", case-sensitively) and is followed by the number of miles of the flight."

The list can be paired -- coach 1500 firstclass 2000 discount 900 coach 3500 -- and this currently works great. However, when the String and int value are split like so:

firstclass        5000  coach         1500         coach
100 firstclass

2000    discount 300

it breaks entirely. I am almost certain that it has something to do with me using this format (not full)

while(fileScanner.hasNextLine())
{
    StringTokenizer token = new StringTokenizer(fileScanner.nextLine(), " ")
    while(token.hasMoreTokens())
    {
        String ticketClass = token.nextToken().toLowerCase();
        int count = Integer.parseInt(token.nextToken());
        ...
    }
}

because it will always read the first value as a String and the second value as an integer. I am very lost on how to keep track of one or the other while going to read the next line. Any help is truly appreciated.

Similar (I think) problems:

Kemper Lee
  • 276
  • 4
  • 10
  • 1
    The Javadoc for `StringTokenizer` says _"...StringTokenizer is a legacy class that is retained for compatibility reasons although its use is discouraged in new code. ..."_ and I don't think you need it. You should be able to get by, just by calling `hasNext()`, `next()` and `nextInt()` on your `Scanner`. No need even for `nextLine()`. – Dawood ibn Kareem May 09 '22 at 01:12

3 Answers3

1

If you can afford to read the text file in all at once as a very long String, simply use the built-in String.split() with the regex \\s+, like so

String[] tokens = fileAsString.split("\\s+"); 

This will split the input file into tokens, assuming the tokens are separated by one or more whitespace characters (a whitespace character covers newline, space, tab, and carriage return). Even and odd tokens are ticket types and mile counts, respectively.

If you absolutely have to read in line-by-line and use StringTokenizer, a solution is to count number of tokens in the last line. If this number is odd, the first token in the current line would be of a different type of the first token in the last line. Once knowing the starting type of the current line, simply alternating types from there.

int tokenCount = 0;
boolean startingType = true; // true for String, false for integer
boolean currentType;
while(fileScanner.hasNextLine())
{
    StringTokenizer token = new StringTokenizer(fileScanner.nextLine(), " ");
    startingType = startingType ^ (tokenCount % 2 == 1); // if tokenCount is odd, the XOR ^ operator will flip the starting type of this line
    tokenCount = 0;
    while(token.hasMoreTokens())
    {
        tokenCount++;
        currentType = startingType ^ (tokenCount % 2 == 0); // alternating between types in current line
        if (currentType) {
            String ticketClass = token.nextToken().toLowerCase();
            // do something with ticketClass here
        } else {
            int mileCount = Integer.parseInt(token.nextToken());
            // do something with mileCount here
        }
        ...
    }
}
bui
  • 1,576
  • 1
  • 7
  • 10
  • I have never used `.split()` before. Does `\s+` translate to "any size string" or something to that effect? – Kemper Lee May 09 '22 at 02:14
  • 1
    No, `\s+` is a [regular expression](https://www.regular-expressions.info/) i.e., a search pattern. `\s` means a whitespace character and `+` means one or more of whatever comes before it. Overall `\s+` means one or more whitespace characters. The `split()` method then uses this regex as a delimiter for separating the tokens in the string. – bui May 09 '22 at 02:18
  • Awesome! Didn't realize I could click on that at first, though I think I got it now. Thank you for that explanation and bookmark ;) – Kemper Lee May 09 '22 at 02:21
0

I found another way to do this problem without using either the StringTokenizer or the regex...admittedly I had trouble with the regular expressions haha.

I declare these outside of the try-catch block because I want to use them in both my finally statement and return the points:

int points = 0;
ArrayList<String> classNames = new ArrayList<>();
ArrayList<Integer> classTickets = new ArrayList<>();

Then inside my try-statement, I declare the index variable because I won't need that outside of this block. That variable increases each time a new element is read. Odd elements are read as ticket classes and even elements are read as ticket prices:

try
{
    int index = 0;
    // read till the file is empty
    while(fileScanner.hasNext())
    {
        // first entry is the ticket type
        if(index % 2 == 0)
            classNames.add(fileScanner.next());
        // second entry is the number of points
        else
            classTickets.add(Integer.parseInt(fileScanner.next()));
        index++;
    }
}

You can either catch it here like this or use throws NoSuchElementException in your method declaration -- As long as you catch it on your method call

catch(NoSuchElementException noElement)
{
    System.out.println("<###-NoSuchElementException-###>");
}

Then down here, loop through the number of elements. See which flight class it is and multiply the ticket count respectively and return the points outside of the block:

finally
{
    for(int i = 0; i < classNames.size(); i++)
    {
        switch(classNames.get(i).toLowerCase())
        {
            case "firstclass":    // 2 points for first
                points += 2 * classTickets.get(i);
                break;
            case "coach":         // 1 point for coach
                points += classTickets.get(i);
                break;
            default:
                // budget gets nothing
        }
    }
}
return points;

The regex seems like the most convenient way, but this was more intuitive to me for some reason. Either way, I hope the variety will help out.

Kemper Lee
  • 276
  • 4
  • 10
  • 1
    This works too, but may slow down your code because if an exception happens (which can be quite frequent in your example string), the [performance cost to create the stack trace](https://stackoverflow.com/questions/36343209/which-part-of-throwing-an-exception-is-expensive) can be significant. In general, I think it's best to use exception to handle rare cases, not frequent ones. – bui May 10 '22 at 01:40
  • Where would be the optimal place to test for/ throw exceptions in this case? Would it be in the method declaration and then up the stack to wherever that was called? I read that -- and a couple of other threads -- and am still not certain. This was another one that I read: https://stackoverflow.com/questions/164613/why-are-try-blocks-expensive – Kemper Lee May 10 '22 at 02:07
  • I don't think *throwing* already instantiated exceptions costs much in your code, but *instantiating* them does, as exceptions capture the stack trace at instantiation point. You can read more at the [article](https://shipilev.net/blog/2014/exceptional-performance/) linked at the end of the accepted answer of the linked question (specifically, the Lil' Exception is *Born* and Lil' Exception is *Thrown* sections). – bui May 10 '22 at 02:27
0

simply use the built-in String.split() - @bui

I was finally able to wrap my head around regular expressions, but \s+ was not being recognized for some reason. It kept giving me this error message:

Invalid escape sequence (valid ones are \b \t \n \f \r " ' \ )Java(1610612990)

So when I went through with those characters instead, I was able to write this:

int points = 0, multiplier = 0, tracker = 0;
while(fileScanner.hasNext())
{
    String read = fileScanner.next().split(
            "[\b  \t  \n  \f  \r  \"  \'  \\ ]")[0];
    if(tracker % 2 == 0)
    {
        if(read.toLowerCase().equals("firstclass"))
            multiplier = 2;
        else if(read.toLowerCase().equals("coach"))
            multiplier = 1;
        else
            multiplier = 0;
    }else
    {
        points += multiplier * Integer.parseInt(read);
    }
    tracker++;
}

This code goes one entry at a time instead of reading a whole array void of whitespace as a work-around for that error message I was getting. If you could show me what the code would look like with String[] tokens = fileAsString.split("\s+"); instead I would really appreciate it :)

you need to add another "\" before "\s" to escape the slash before "s" itself – @bui

Kemper Lee
  • 276
  • 4
  • 10
  • My bad, I forgot that you need to add another "\" before "\s" to escape the slash before "s" itself. I edited my answer. – bui May 10 '22 at 01:27
  • I wish I could double upvote it haha. Thank you for helping me understand how to use the regular expressions...if only my professor did that ;p – Kemper Lee May 10 '22 at 01:43