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
>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 <dimitar@example.com> writes:
>FWIW one of the idioms for doing the 'autovivification' thing in Java is:
>synchronized(masterMap) {
[quoted text clipped - 3 lines]
>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 )); }}
lordy - 19 Jul 2006 00:57 GMT
>>Any nice way to do the Java equivalent of the perl..
>>$myHash{val1}{val2}{val3} = val4
[quoted text clipped - 4 lines]
> Here is a simple example of autovivification in a special
> case for Java:
Thanks a lot. Plenty to chew on ..
Lordy
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
> Any nice way to do the Java equivalent of the perl..
>
[quoted text clipped - 4 lines]
>
> 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