Multidimensional Associative Arrays..

L

lordy

Any nice way to do the Java equivalent of the perl..

$myHash{val1}{val2}{val3} = val4

Eg. myHash.get(val1) is another Hashtable 'hash2'.
hash2.get(val2) is another Hashtable 'hash3' etc. etc.

At each step, if the intermediate hash doesn't exist it is created.


I've rolled my own method that takes an array of objects and creates the
required nested Maps as it steps through the array. Just wondered if I'm
re-inventing a wheel..

Cheers,
Lordy
 
S

Stefan Ram

lordy said:
Any nice way to do the Java equivalent of the perl..
$myHash{val1}{val2}{val3} = val4
At each step, if the intermediate hash doesn't exist it is created.

This is called »autovivification«.

Here is a simple example of autovivification in a special
case for Java:

class NumericMapUtils
{ public static <D> void addTo
( final java.util.Map<D,java.lang.Integer> map, final D d, final int i )
{ map.put( d, i +( map.containsKey( d )? map.get( d ): 0 )); }}

Now one can declare a map:

final java.util.Map<java.lang.Integer,java.lang.Integer> map =
new java.util.HashMap<java.lang.Integer,java.lang.Integer>();

And then add 7 to its entry 4:

NumericMapUtils.<java.lang.Integer>addTo( map, 4, 7 );

If the entry 4 does not exist yet, it will be created (hence,
"autovivification") with an initial value of 0 by »addTo«.

Your case from above could use an interface like (simplified):

setHash
( final java.util.Map map, final java.lang.Object newValue,
final java.lang.Object keys ... )

And for your example would be called like

setHash( myHash, val4, va11, val2, val3 );

In Perl, I dynamically build an expression like
»::root->{a}->{b}->...->{z}« and then use »eval« to build the
whole path including all hashes required by autovivification,
where the number of hashes required is only known at runtime.

Eventually »$source« is stored at the new location within
this map of maps of maps of ... maps. All it needs is:

my $buffer = '';
while( $psource =~ /:)|\/|[^:\/]+)/g ){ $buffer .= "->{'" . $1 . "'}"; }
my $expression = '\$::root' . $buffer;
my $place = eval( $expression ); ${$place}->{'#'}= $source;

Without »=~«, without »eval« and without autovivification, it
needs some more than four lines of Java to express the same
thing in Java.

dimitar said:
FWIW one of the idioms for doing the 'autovivification' thing in Java is:
synchronized(masterMap) {
if (!masterMap.contains(key)) {
masterMap.put(key, new HashMap()); (...)
masterMap.get(key).put(subkey, value);
it takes six lines, and it's thread-safe. Anything less and you might
loose data if more than one threads try to access the structure.

It is still not quite the same, because it contains the fixed
assumption "new HashMap()", while in Perl this also could be
"new List()" or "new Integer()" depending on the client code
used.

If that would be possible in Java, one would be able to write

mainMap.get( key ).put( subKey, value );

and get an autovivificated Map (as above), but also

mainMap.get( key ).add( value )

and get an autovivificated List or

mainMap.get( key )+= 12

and get an autovivificated Integer.

Here's the complete Perl script:

use Data::Dumper;
$names->{ "branch" }->{ "account" } = 123;
$names->{ "branch1" }->[ 3 ] = 123;
$names->{ "branch2" } += 12;
print Data::Dumper::Dumper( $names ), "\n";

and its output is:

$VAR1 = {
'branch' => {
'account' => 123
},
'branch1' => [
undef,
undef,
undef,
123
],
'branch2' => 12
};

Without autovivification, in Java, one needs some work of the
programmer, for example, to create a nested array with a
dimension given at runtime:

For example, the following code builds an array with three
dimensions of the extension 4, 5, and 6, respectively.
The vivification happens recursively in »build« using
»java.lang.reflect.Array.newInstance«. The number of arguments
of »build« gives the dimension.

A call to newInstance alone with the extensions will give
an array object with the correct type, but its entries will
still be empty and not other array objects. So, the recursion
of »build« is needed to fill all array entries with other
subarrays up to the lowest level.

public class Main
{
public static int[] cdr( final int[] list )
{ return java.util.Arrays.copyOfRange( list, 1, list.length ); }

public static java.lang.Object build( final int ... extensions )
{ final java.lang.Object array = java.lang.reflect.Array.newInstance
( java.lang.Integer.TYPE, extensions );
for( int i = 0; i < extensions[ 0 ]; ++i )if( extensions.length > 1 )
java.lang.reflect.Array.set( array, i, build( cdr( extensions )));
else java.lang.reflect.Array.setInt(( int[] )array, i, i );
return array; }

public static void print( final java.lang.Object array )
{ for( int i = 0; i < java.lang.reflect.Array.getLength( array ); ++i )
if( array.getClass().getName().startsWith( "[[" ))
print( java.lang.reflect.Array.get( array, i )); else
java.lang.System.out.print( java.lang.reflect.Array.getInt( array, i )); }

public static void main( final java.lang.String[] args )
{ print( build( 4, 5, 6 )); }}
 
I

Ingo R. Homann

Hi lordy,
Any nice way to do the Java equivalent of the perl..

$myHash{val1}{val2}{val3} = val4

Eg. myHash.get(val1) is another Hashtable 'hash2'.
hash2.get(val2) is another Hashtable 'hash3' etc. etc.

IMHO there are (at least) two ways to achieve that. The first one is...
At each step, if the intermediate hash doesn't exist it is created.

....Stefan showed a way to do this e.g. by a static helper-method (I did
not read hist posting til the end, because I (and many other people)
think that his code is unreadable - but thanks god there are some good
code-formatters :)

My way would be to implement a self-verifying Map by encapsulating (and
delegating to) a java.util.HashMap.

Another - totally different (and perhaps easier, but not so
flexible) - possibilty would be to combine the three values to a new key:

String val1=..., val2=..., val3=...;

// some char that does not occur in the Strings above:
String sepChar="|";

String key=val1+sepChar+val2+sepChar+val3;

Map<String,Something> map=...
map.get(key);


If you want to do it a bit 'cleaner', you can wrap the vals into a new
class:


class MyKey {
String val1, val2, val3;
int hashCode() {
return val1.hashCode()+val2.hashCode()+val3.hashCode();
}
boolean equals(Object o) {
MyKey k=(MyKey)o;
return val1.equals(k.val1)&&val2.equals(k.val2)&&val3.equals(k.val3);
}
}


Hth,
Ingo
 
P

Piotr Kobzda

lordy said:
Any nice way to do the Java equivalent of the perl..

$myHash{val1}{val2}{val3} = val4

Eg. myHash.get(val1) is another Hashtable 'hash2'.
hash2.get(val2) is another Hashtable 'hash3' etc. etc.

At each step, if the intermediate hash doesn't exist it is created.

Try the following:

<code>
import java.util.HashMap;
import java.util.Map;

public class AutoMapValue<K, V> {

private Map<K, AutoMapValue<K, V>> map;
private V value;

public Map<K, AutoMapValue<K, V>> getMap() {
if (map == null)
map = new HashMap<K, AutoMapValue<K, V>>();
return map;
}

public AutoMapValue<K, V> get(K key) {
Map<K, AutoMapValue<K, V>> map = getMap();
AutoMapValue<K, V> e = map.get(key);
if (e == null)
map.put(key, e = new AutoMapValue<K, V>());
return e;
}

public AutoMapValue<K, V> get(K... keys) {
AutoMapValue<K, V> e = this;
for(K k : keys)
e = e.get(k);
return e;
}

public V get() {
return value;
}

public void set(V value) {
this.value = value;
}

public String toString() {
return map + "|" + value;
}

}
</code>

Sample usage:

AutoMapValue<String, Integer> amv = new AutoMapValue<String, Integer>();

amv.get("a").set(1);
amv.get("a", "b", "c").set(3);

amv.get("a").get("b").get(); // null
amv.get("a").get("b").get("c").get(); // 3



piotr
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,755
Messages
2,569,535
Members
45,007
Latest member
obedient dusk

Latest Threads

Top