How to use java.util.Map in a more Perl like way.

R

robertjparks

Hi, I make extensive use of N-dimensional Maps in my code and would
like to find out if there is a way to manipulate them in a more
Perlish fashion. For example, say I have 2D map and I want to write
all the way through to the end. My code else up looking like this:

Map<String, Map<String,String>> map = new HashMap<String,
Map<String,String>>();
String key1= "key1";
String key2="key2";
String val="val";
// write to the structure building it up as you go
if(!map.containsKey(key1)) map.put(key1,new HashMap<String,String>());
if(!map.get(key1).containsKey(key2)) map.get(key1).put(key2,val);

In perl, you don't have to build up and walk through the structure in
order to write to it. For example, this would suffice:
my %map=();
my $key1= "key1";
my $key2="key2";
my $val="val";
# write to the structure in 1 shot
map{$key1}{$key2}=$val

To avoid having to "walk through and build up the structure" every
time I write to it, I wrote a static MapUtils to do the it. You can
say I am lazy here but the walking code blows out really fast when you
have an 5 level deep Map and I like to keep things short and neat.

public MapUtils{
public static put(Map<String,Map<String,String>> map, String key1,
String, key2,String, val){
if(!map.containsKey(key1)) map.put(key1,new
HashMap<String,String>());
if(!map.get(key1).containsKey(key2)) map.get(key1).put(key2,val);
}
}

Now I can just say:

MapUtils.put(map, key1, key2, val);

Which make my code much more readable.

Now here is where I need help!

How do I write MapUtils.put() so that it can take a Map<?,?> of any
number of dimensions and types and a list of N-keys and 1 value of any
type? I tried messing around with generics and wildcards but didn't
get too far. Maybe what I want to do is not possible. If this is the
case, I would like to hear why.

So far my best solution is to write a new version of put() every time
I need to write to a new type of Map and let function overloading pick
the correct one. This works ok, but I am hoping somone can offer a
better approach.

Thanks,
Rob
 
T

Thomas Hawtin

Hi, I make extensive use of N-dimensional Maps in my code and would
like to find out if there is a way to manipulate them in a more
Perlish fashion. For example, say I have 2D map and I want to write
all the way through to the end. My code else up looking like this:

Map<String, Map<String,String>> map = new HashMap<String,
Map<String,String>>();
String key1= "key1";
String key2="key2";
String val="val";
// write to the structure building it up as you go
if(!map.containsKey(key1)) map.put(key1,new HashMap<String,String>());
if(!map.get(key1).containsKey(key2)) map.get(key1).put(key2,val);

In perl, you don't have to build up and walk through the structure in
order to write to it. For example, this would suffice:
my %map=();
my $key1= "key1";
my $key2="key2";
my $val="val";
# write to the structure in 1 shot
map{$key1}{$key2}=$val

To avoid having to "walk through and build up the structure" every
time I write to it, I wrote a static MapUtils to do the it. You can
say I am lazy here but the walking code blows out really fast when you
have an 5 level deep Map and I like to keep things short and neat.

public MapUtils{
public static put(Map<String,Map<String,String>> map, String key1,
String, key2,String, val){
if(!map.containsKey(key1)) map.put(key1,new
HashMap<String,String>());
if(!map.get(key1).containsKey(key2)) map.get(key1).put(key2,val);
}
}

Now I can just say:

MapUtils.put(map, key1, key2, val);

Which make my code much more readable.

Now here is where I need help!

How do I write MapUtils.put() so that it can take a Map<?,?> of any
number of dimensions and types and a list of N-keys and 1 value of any
type? I tried messing around with generics and wildcards but didn't
get too far. Maybe what I want to do is not possible. If this is the
case, I would like to hear why.

Instead of your put, you could write a static get method that creates if
necessary.

import static collection.HashMaps.get;
....
Map<String,Map<String,String>> map2;
Map<String,Map<String,Map<String,String>>> map3;
...
get(map2, key1).put(key2, value);
get(get(map3, key1), key2).put(key3, value);
....

package collection;

public final class HashMaps {
private Maps() {
throw new Error();
}
public static <K, MK, MV> Map<MK, MV> get(
Map<K, Map<MK, MV>> map, K key
) {
Map<MK, MV> nested = map.get(key);
if (nested == null) {
nested = new java.util.HashMap<MK, MV>();
map.put(key, nested);
}
return nested;
}
}

Perhaps better would be to write your own Map-like types to create on
demand.

Another approach is to use a single map with composite key. That also
may be faster and more memory efficient.

Tom Hawtin
 
R

robertjparks

Instead of your put, you could write a static get method that creates if
necessary.

import static collection.HashMaps.get;
...
Map<String,Map<String,String>> map2;
Map<String,Map<String,Map<String,String>>> map3;
...
get(map2, key1).put(key2, value);
get(get(map3, key1), key2).put(key3, value);
...

package collection;

public final class HashMaps {
private Maps() {
throw new Error();
}
public static <K, MK, MV> Map<MK, MV> get(
Map<K, Map<MK, MV>> map, K key
) {
Map<MK, MV> nested = map.get(key);
if (nested == null) {
nested = new java.util.HashMap<MK, MV>();
map.put(key, nested);
}
return nested;
}

}

Perhaps better would be to write your own Map-like types to create on
demand.

Another approach is to use a single map with composite key. That also
may be faster and more memory efficient.

Tom Hawtin

Thanks for the feedback.

I agree that having get("missingKey") automatically build out the Map
will make it work more like perl and also will solve my put() issue.

The problem is that I shouldn't have said I wanted it to work EXACTLY
like perl. I like that in perl you can write through hash dimensions
and put them in existence, but I don't like that when you read through
a missing hash key that it adds it automatically. For example, I don't
like when I check

if(exists($map{"k1"}{"k2"})){ ... }

that it puts "k1" into existence. So although your suggestion is great
for emulating perl, it isn't what I was looking for.

The composite key is also a good idea, but it doesn't quite have the
same functionality. Although I always write through all the dimensions
of the Map, I still like that multi-dimensional maps allow you to see
all the values for a specific key.

So thanks for the work around suggestions, but I am still hoping to
solve my exact problem.

Thanks,
Rob
 
P

Patricia Shanahan

Thanks for the feedback.

I agree that having get("missingKey") automatically build out the Map
will make it work more like perl and also will solve my put() issue.

The problem is that I shouldn't have said I wanted it to work EXACTLY
like perl. I like that in perl you can write through hash dimensions
and put them in existence, but I don't like that when you read through
a missing hash key that it adds it automatically. For example, I don't
like when I check

You may still be able to use the HashMaps idea, but give it two distinct
get methods, a pure get and a creatingGet. creatingGet would be the get
shown above. The pure get would check for nesting, but return null
rather than creating a new mapping. In a put situation you would use the
creatingGet.

Patricia
 
M

Mark Space

Now I can just say:

MapUtils.put(map, key1, key2, val);

Whenever I see this type of construct:

static procedure( Object thing_to_operate_on, ... )

I think somebody missed an opportunity for inheritance

class MyMap extends Map
{
//...

void procedure( ... )
//..
}

But I guess that's kind of obvious too. I like the idea of extending
the methods, rather than over-riding them, which is a tad safer and less
complicated in general. A new get() and put() might not be to hard. I
like Patricia's suggestion to add a creatingGet() method, that would
work well. Var args could clean up the code to add variable numbers of
keys too...
 
T

Thomas Hawtin

Mark said:
I think somebody missed an opportunity for inheritance

I think you should prefer not to use inheritance. It's a big powerful
tool, but you probably don't want to throw it around too eagerly.
class MyMap extends Map

I guess that would be extends HashMap. Perhaps introduce an interface
that extends Map and a class that extends HashMap and implements the
interface.

That doesn't work so well if this is an isolated piece of code and the
map comes from elsewhere. So you might want to use a decorator. But a
decorator to do something like this feels a bit clumsy. It's almost as
bad as creating a new object only to call a single method on it.
work well. Var args could clean up the code to add variable numbers of
keys too...

But then you'd lose type safety and some performance.

If you still wanted that approach, there would be less code to simply
use java.util.Arrays.asList to create the keys. I don't recommend it.

Tom Hawtin
 
T

Twisted

I guess that would be extends HashMap. Perhaps introduce an interface
that extends Map and a class that extends HashMap and implements the
interface.

Ugh!!

Try

public class MyMap<K,V> implements Map<K,V>
private Map<K,V> delegate;
public MyMap () {
delegate = new HashMap<K,V>();
}
public MyMap (Map<K,V> delegate) {
this.delegate = delegate;
}
...
}

This uses a HashMap by default but lets people use the alt constructor
to dependency-inject and get a MyMap based on a TreeMap, etc. (and of
course they can specify a TreeMap comparator when they construct the
TreeMap prior to passing it to the MyMap constructor). The one iffy
thing is the aliasing that occurs if the TreeMap (or whatever)
reference is kept around and also gets used; changes to the MyMap and
the TreeMap are reflected in one another.

To OP: If the key types where you want multiple keys are always all
the same, just use Map<List<KeyType>, ValueType>; this supports
different key list lengths for different entries. If you don't want
that make a fixed-size immutable list class; e.g. use
Map<ThreeElementList<KeyType>, ValueType> and make ThreeElementList a
class that implements List and has a constructor that accepts a List
but throws if it's the wrong length, otherwise wrapping it and
implementing none of the "optional" (list-mutating) operations.
 
T

Thomas Hawtin

Twisted said:
Ugh!!

Try

public class MyMap<K,V> implements Map<K,V>

Yes, that's a decorator as mentioned in my previous post. It's a lot
more work (although the method forwarding can be factored out into an
abstract class).

Tom Hawtin
 

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,744
Messages
2,569,484
Members
44,904
Latest member
HealthyVisionsCBDPrice

Latest Threads

Top