views:

653

answers:

4

The put function works fine but the get function does not. Apparently I don't know the trick.

>> X = [ 1, 2, 3];
>> M = java.util.HashMap;
>> M.put(X,1);
>> M.get([1,2,3])

ans = []

I have searched and read many posts but could not find a solution to this problem. It would be great if someone can let me know the trick.

+1  A: 

I don't think you can use numeric vectors or matrices as keys in a Java hashmap. You would instead have to convert the vector or matrix into a single unique key, such as a unique character string representation of the values in the vector or matrix. There are a few ways to do this:

  • For integer arrays, you can use the CHAR function to convert the integers to their equivalent ASCII representations, thus creating a character string. This will only work effectively for integer values between 0 and 65535, since anything outside this range is likely to have undefined behavior. Here's an example:

    X = [1 2 3; 4 5 6];  % X is a 2-by-3 matrix
    keyValue = char(X(:)');  % Reshape X to a row vector and convert to ASCII
    

    For integer values too large to use CHAR, you can use INT2STR instead:

    keyValue = int2str(X(:)');
    
  • For floating-point arrays, you can use the NUM2STR function to create a formatted string representation of each of the array elements concatenated together. Here's an example:

    X = rand(2,3)*9999;  % X is a 2-by-3 matrix of random double values
    keyValue = num2str(X(:)','%10.5f');
    

    To ensure uniqueness of the key (by avoiding round-off of the floating-point value) you could instead convert the double values to their complete 64-bit binary representations using DEC2BIN. However, this will likely result in huge character keys:

    keyValue = reshape(dec2bin(X(:),64)',1,[]);
    

One drawback of these options is that your keys could potentially end up being rather long character strings. I'm not sure if there is an upper limit on the number of characters in the key or if there is a performance hit in using long character strings for keys.

gnovice
thanks. This works for integer inputs. Is there anyway to generate java objects out of Matlab matrices? (e.g 1:.1:3)The char function works on them too but I think it simply ignores the non integer parts.
Thanks again. It works pretty well, though it is very slow. The reason I ended up doing this is that I am trying to implement an efficient A* algorithm in matlab. Hence I am using java objects for fast access but it seems I am better off write a C++ code and mexify it. The catch is, I would like to have A* as flexible as possible. For example receiving all the search domain specs as input functions.
A: 

If you're using a more recent version of MATLAB (2008b or later, I think) then MATLAB has its own map class which works for certain kinds of keys. See the documentation: containers.Map

kwatford
Unfortunately Containers in Matlab dont support matrices.
+5  A: 

I think the problem is that Java primitive arrays don't provide the right equals() and hashCode() for you. They use the standard Object methods that compare by object identity instead of contained values. When using nonscalar arrays as keys in a HashMap, Matlab will convert them to double[], but they'll be distinct Java objecs, so they'll get this behavior.

If you wrapped your array values in a Java object that provided by-value behavior for equals() and hashCode() before using them as keys, this could work. Luckily, java.util.Arrays provides by-value implementations for the primitive arrays. We just need to slap them in a wrapper class that provides the interface that HashMap is expecting.

package test;
import java.util.Arrays;

/**
 * A double[] that with by-value semantics for equals() and hashCode() so you
 * can use it in HashMaps.
 * In a non-toy class, you'd probably use switch statements to support arrays
 * of any primitive type. In a language with real generics, you'd just template
 * this.
 */
public class EqualByValueDoubleArray {
    private double[] x;
    public EqualByValueDoubleArray(double[] x) { this.x = x; }
    public double[] getArray() { return x; };
    public boolean equals(Object obj) {
        if (obj instanceof EqualByValueDoubleArray) {
            return Arrays.equals(this.x, ((EqualByValueDoubleArray)obj).x);
        } else {
            return false;
        }
    }
    public int hashCode() { return Arrays.hashCode(x); }
}

Now you can wrap them and use them as keys from Matlab.

function scratch_array_keyed_hashmap
import test.EqualByValueDoubleArray;
map = java.util.HashMap;
a = [1 2 3 4 5]';

key = EqualByValueDoubleArray(a);
map.put(key, 'my value');
% Separate key so we know it's comparing by value, not Java object identity
key2 = EqualByValueDoubleArray(a);
gotBack = map.get(key2)

This works under R2008b for me.

>> scratch_array_keyed_hashmap
gotBack =
my value

For easier use, you could create a HashMap subclass that checked the type of its input keys, and automatically wrapped primitive arrays in this by-value wrapper.

Andrew Janke
Thanks a bunch. It works flawlessly!
A: 

Matlab structs provide very fast lookup from alphanumeric keys (well, [a-zA-Z][a-zA-Z_0-9]* matching); failing that, if you are trying to hash from numbers, I would suggest using sparse arrays with array doubling; let the arrayvalue point to the index into whatever you are trying to look up. hth

shabbychef