6

So it's winter break for colleges, and I'm trying to stay sharp on my coding, so I'm just writing code for programs and algorithms we only talked about but never coded in class. Anyway, one I'm working on today is a program where you give the computer a scrambled word, and it outputs all the words (from the EnglishWordList file we are given) that can be made from those letters.

Anyway, here is the code I have so far for it:

import java.io.*;
import java.util.*;

public class ProdFinder {
    private HashMap<Character, Integer> prodFinder = new HashMap<Character, Integer>();
    private HashMap<Integer, LinkedList<String>> findWord = new HashMap<Integer,     LinkedList<String>>();

    private static final char[] letters =     {'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r',
            's','t','u','v','w','x','y','z'};
    private static final int[] primes =     {2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101}; 

    public ProdFinder() throws FileNotFoundException {
        for (int i = 0; i < letters.length; i++) {
            prodFinder.put(letters[i], primes[i]);
        }

        Scanner sc = new Scanner(new File("EnglishWordList.txt"));


        while (sc.hasNextLine()) {
            String str = sc.nextLine();
            int strProd = findProduct(str);

            if (findWord.containsKey(strProd)) {
                LinkedList<String> wordList = findWord.get(strProd);
                wordList.add(str);
                findWord.put(strProd, wordList);
            }
            else {
            LinkedList<String> wordList = new LinkedList<String>();
            wordList.add(str);
            findWord.put(strProd, wordList);
        }
    }

    sc.close();
}

public int findProduct(String x) {
    int product = 1;
    char[] str = x.toCharArray();

    for (Character val: str) {
        product = product*prodFinder.get(val);
    }

    return product;
}

public void descramble(String x) {
    int prod = findProduct(x);

    if (findWord.containsKey(prod)) {
        System.out.println("The words that can be formed from the letters in " + x + " are: " +
                            findWord.get(prod));
    }
    else {
        System.out.println("No words can be formed from the letters in " + x + ".");
    }
}

}

Now, the error originates at the line where I start putting all of the prime products of numbers into my HashMap (trying to map each prime product to a LinkedList of words whose letters multiply into that number.) Somehow this is throwing the exception, as when I comment out that section of code and just run the findProduct method, it works fine on any word I give it to output the product of the lettesr in prime form.

Any ideas on where the exception is coming from?

EDIT: Sorry, the stacktrace is as follows:

Exception in thread "main" java.lang.NullPointerException
at ProdFinder.findProduct(ProdFinder.java:44)
at ProdFinder.<init>(ProdFinder.java:22)
at Descramble.main(Descramble.java:7)

From what I can tell, the error originates when I try to call findProd on the str, at this line:

int strProd = findProduct(str);
Bill L
  • 2,576
  • 4
  • 28
  • 55
  • 16
    stacktrace please! (Exception => stacktrace => fast help) – A4L Jan 06 '14 at 19:18
  • 4
    Or at least, indicate which line you are talking about (which method, etc.) – MxLDevs Jan 06 '14 at 19:19
  • Or tell us what you want to use the primes for... how do letters multiply into a number (and what does that have to do with finding your word(s) in the dictionary)? – Elliott Frisch Jan 06 '14 at 19:21
  • 1
    I'd say you're going to get integer overflow here, unless your words are all very short. Is there a reason why you're trying to convert words to products of primes? If this is a necessary feature, then please use `BigInteger`, not `int`. – Dawood ibn Kareem Jan 06 '14 at 19:21
  • @DavidWallace The motivation is clear: to achieve O(1) lookup of word by the set of its constituent characters. – Marko Topolnik Jan 06 '14 at 19:22
  • In class this was the method we talked about for finding words with the same numbers in them, assign a to 2, b to 3, c to 5, etc. all the way to z assigned to 101, so a word like 'cat' becomes 3*2*71 = 710, and that is the only three letters that multiply to 710. – Bill L Jan 06 '14 at 19:23
  • I think the scheme originates with Kurt Godel, who used it to encode statements about numbers into numbers, and bring down the aedifice of mathematics. – Marko Topolnik Jan 06 '14 at 19:24
  • if that's true, then you shouldn't be doing both map.containsKey and then map.get. that's 2 lookups on every "success". do map.get and if it returns null, the key wasn't there. – John Gardner Jan 06 '14 at 19:25
  • @Marko I know that. But it looks like he's just trying to wrap a `HashMap` in an extra layer of hashing, so that it effectively becomes a kind of `HashSet`. If that is all he's using it for, then I wonder whether the extra complexity that this induces is worthwhile, just to make the lookup `O(1)`. – Dawood ibn Kareem Jan 06 '14 at 19:26
  • use a debugger? place a breakpoint in findProduct, I bet something is passing in a null? – John Gardner Jan 06 '14 at 19:27
  • @DavidWallace The difference is that OP's scheme avoids any hash collisions---but only if he doesn't allow overflow, as you note. – Marko Topolnik Jan 06 '14 at 19:27
  • @DavidWallace I'm trying to allow my keys to be unique numbers that map to only the values that can produce them. In a sense it is an extra set of hashing, but it allows me to have a map of the prime product of the numbers in the word list, so that when given any scrambled set of letters, I can take the prime product of those letters and quickly map from that product to a list of words that also have those letters (and are known to be words by the nature of the list I'm using.) – Bill L Jan 06 '14 at 19:28
  • Line 44 is product = product*prodFinder.get(val); – Bill L Jan 06 '14 at 19:29
  • Where is your `main` method. Can we see how you're calling these methods and what you're passing to them – Paul Samsotha Jan 06 '14 at 19:30
  • Bill, I kind of get that, but given that there WILL be collisions in the underlying `HashMap`, I don't think the extra hashing is buying you anything. You may as well just hash the word to get a key for the map, rather than hashing the Godel number of the word. – Dawood ibn Kareem Jan 06 '14 at 19:34
  • @peeskillet as it is now it is not clear, but before trying to debug everything was set to private except the descramble method. Basically I would just call that method on a ProdFinder object, and that would output what it says there, the main method would be a two line code of creating the object, then calling object.descramble("srambledStringHere"). – Bill L Jan 06 '14 at 19:34
  • @DavidWallace You are ignoring the essential difference: it is not the *sequence* of characters that is encoded by the number (as it was in Gödel's case), but the *bag* of characters (invariant under reordering). – Marko Topolnik Jan 06 '14 at 19:36
  • @DavidWallace could you be a little more specific on how that would work? For example, given the string omucntde, how would I go about figuring out that that matches with "document" (and any other word that has those letters too.) – Bill L Jan 06 '14 at 19:37
  • Frankly, all the guesswork going on in the answers is ridiculous. *Print out the character you are using on that line* and settle your problem. There was little point in going all the way to StackOveflow for a trifling NPE like this one. – Marko Topolnik Jan 06 '14 at 19:39
  • @MarkoTopolnik You're right. That had escaped me on first reading. For some reason, I got Godel numbers into my head, then thought that he was hashing the WORD, not the bag of letters. Apologies. – Dawood ibn Kareem Jan 06 '14 at 20:12

4 Answers4

7

The problem is ProdFinder.java:44 as the stack trace indicates which is

product = product*prodFinder.get(val);

The null pointer exception is the prodFinder does not have a product/value for val so it returns null which the program then attempts to multiply by product.

(anything * null) results in NullPointerException

The reason the prodFinder map does not have a value is probably because the word has some capital letter, special character or number when all that has been added to the map is lower case alphabetic characters

Java debugger is a great resource in problems like this. Use an IDE such as eclipse or netbeans and just place a breakpoint at line 44 to quickly find the culprit use case.

bdrx
  • 924
  • 13
  • 31
  • I'm passing in each value in my word list into the method, so val would be each character from the string that I passed in. It works by manually passing in the words, so I don't see why taking them from a file and passing them in that way would throw a NullPointerException – Bill L Jan 06 '14 at 19:32
  • Improve your answer by sugesting that the autoboxing of the value fails to convert a `null` Integer object from the map to an `int` primitive. – rolfl Jan 06 '14 at 19:33
  • @rolfl Truth be told, Aaron should explain *how come* `prodFinder.get(val)` returns `null`, since OP's logic obviously wants to have it for an invariant that each possible character is already there. – Marko Topolnik Jan 06 '14 at 19:34
  • @BillL Print out what it's trying to get (or use a debugger). Maybe your program is doing something you don't expect it to do elsewhere, and this is just a consequence of another bug. – MxLDevs Jan 06 '14 at 19:34
  • @BillL - add a line `System.out.println(val + " maps to " + prodFinder.get(val));` just before the `product*` line – rolfl Jan 06 '14 at 19:34
  • +1 tested OP's code with a word in a file with special chars, number - NPE – StoopidDonut Jan 06 '14 at 19:40
  • OK, that solved the problem, the algorithm is buggy, but there were some random spaces inserted and some special characters, all of which threw the exception. Now to work on speeding it up! – Bill L Jan 06 '14 at 19:52
2

You're currently unboxing null values from your Map. This is why you result in a NullPointerException.

A brief primer on autoboxing: There are a set of objects which represent primitives; these are called "wrapper classes" or "boxed classes". They include every primitive with an associated class.

This allows you to use a primitive value inside of a generic collection, as generics will not work without objects.

An autoboxing conversion occurs when Java encounters an operation that would require the primitive value - namely, some form of arithmetic or unary operator (+, -, *, /, ++, --, +, -).

Boxing and unboxing conversion happens according to the specification. I would encourage you to read up on that, since there's a lot there to list, and I'd be repeating what's already been said.

The caveat to autoboxing is null. Remember that primitives have a fixed, finite value, and objects can either exist (with infinitely many values depending on the number of fields), or not exist, or is null. If you attempt to unbox a value that is null, then a NullPointerException occurs.

Now, as to why you're pulling back null from your Map: char[] str = x.toCharArray(); turns whatever x is into a character array, and you then look in your mapping for all lower case characters, and map them to a given prime value.

What happens if a character in x is upper case? What if it's a number? What if it's a special character? A space? A tab? Something that isn't a lowercase a-z letter? You don't find a value in the mapping, and it would return null.

This also serves as a bit of a warning - when dealing with boxed primitives, one must take great care that, when relying on autoboxing conversions, that they are not dealing with any null values or instances. This will lead to confusing NPEs (like this one) which may be tougher to track.

Makoto
  • 104,088
  • 27
  • 192
  • 230
  • As it turns out the list we were given a list with only lowercase letters, it was only an intro class to data structures, so the point of most of these assignments was to gain familiarity with data structures. Could you explain more about the unboxing null values? As far as I can tell, the problem only arises when I pass values from the list, when I hardcode the string in it works fine. – Bill L Jan 06 '14 at 19:40
  • +1 tested OP's code with a word in a file with uppser case - NPE – StoopidDonut Jan 06 '14 at 19:41
  • @BillL: I've added a section on autoboxing into my answer. Furthermore, take care into what you're actually passing in as your character array - it will blow up on anything that isn't a lower case alphabetic character. This includes whitespace. You may need to trim some of it away. – Makoto Jan 06 '14 at 19:49
1

The only way I can imagine the following line is throwing an exception is that if val is not in prodFinder

product = product*prodFinder.get(val);

you might want to check the result of prodFinder.Get before you use it

Integer value = prodFinder.get(val);

if(val == null)
    //indicate that you have an error state?
else
    product *= val;
0

First of all you should learn how to use a debuger. If you cant you could help yourself with some System.out's

public int findProduct(String x) {
    int product = 1;
    char[] str = x.toCharArray();
    System.out.println("str " + str);
    for (Character val: str) {
        System.out.println("prodFinder " + prodFinder);
        System.out.println("val " + val);
        System.out.println("prodFinder.get(val) " + prodFinder.get(val));
        product = product*prodFinder.get(val);
    }
    return product;
}

For a File content like this:

Hello
World

The output is:

Cmd-$> java Descramble
str Hello
str [C@e80842
prodFinder {f=13, g=17, d=7, e=11, b=3, c=5, a=2, n=43, o=47, l=37 ...
val H
prodFinder.get(val) null
Exception in thread "main" java.lang.NullPointerException
        at ProdFinder.findProduct(Descramble.java:48)
        at ProdFinder.<init>(Descramble.java:23)
        at Descramble.main(Descramble.java:70)

as you can see, for the letter H the return value is null

There are no capital letters in your array letters, thus none in the Map!

The Exception happens on the line

product = product*prodFinder.get(val);

Here the jvm is trying to unbox the Integer returned by the map into a primitive int to perform the multiplication. But it is not possible because the map returns null

A4L
  • 17,353
  • 6
  • 49
  • 70