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:
umper;
$names->{ "branch" }->{ "account" } = 123;
$names->{ "branch1" }->[ 3 ] = 123;
$names->{ "branch2" } += 12;
print Data:
umper:
umper( $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 )); }}