Encapsulating HashMap bulding

R

Roedy Green

Can anyone think of a way to write a method that takes an array of X,
and produces a HashMap<Key,X>

How would you specify the name of the key field/method?

Maybe you could do it by making X implement an interface that defines
the key.

Perhaps you could do it with reflection.
 
L

Lew

Can anyone think of a way to write a method that takes an array of X,
and produces a HashMap<Key,X>

How would you specify the name of the key field/method?

Maybe you could do it by making X implement an interface that defines
the key.

Perhaps you could do it with reflection.

Map <Key, X> map = new HashMap <Key, X>();
for ( X x : arrayOfX )
{
map.put( genKey( x ), x );
}

Example:

public class X
{
private final Key key; // getter and setter omitted
public X( Key k ) { key = k; }
public Key getKey() { return key; }
// other attributes omitted
}

other class#method:

...
for ( X x : arrayOfX )
{
map.put( x.getKey(), x );
}
 
A

Arne Vajhøj

Can anyone think of a way to write a method that takes an array of X,
and produces a HashMap<Key,X>

How would you specify the name of the key field/method?

Maybe you could do it by making X implement an interface that defines
the key.

Perhaps you could do it with reflection.

The two approaches seems to be what is available.

See below for some code.

Arne

=============================

import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;

public class A2HM {
public static <K,T extends KeyContainer<K>> Map<K,T> convert1(T[] a) {
Map<K,T> res = new HashMap<K,T>();
for(T e : a) {
res.put(e.getKey(), e);
}
return res;
}
@SuppressWarnings("unchecked")
public static <K,T> Map<K,T> convert2(T[] a, String prop, Class<K>
clz) throws IntrospectionException, IllegalArgumentException,
IllegalAccessException, InvocationTargetException {
PropertyDescriptor pd = new PropertyDescriptor(prop,
a.getClass().getComponentType());
Map<K,T> res = new HashMap<K,T>();
for(T e : a) {
res.put((K)pd.getReadMethod().invoke(e), e);
}
return res;
}
public static void main(String[] args) throws Exception {
Foobar[] a = new Foobar[3];
a[0] = new Foobar(1,1.2,"A");
a[1] = new Foobar(2,12.34,"BB");
a[2] = new Foobar(3,123.456,"CCC");
System.out.println(convert1(a));
System.out.println(convert2(a, "k", Integer.class));
}
}

interface KeyContainer<K> {
public K getKey();
}

class Foobar implements KeyContainer<Integer> {
private int k;
private double v1;
private String v2;
public Foobar(int k, double v1, String v2) {
this.k = k;
this.v1 = v1;
this.v2 = v2;
}
public int getK() {
return k;
}
public void setK(int k) {
this.k = k;
}
public double getV1() {
return v1;
}
public void setV1(double v1) {
this.v1 = v1;
}
public String getV2() {
return v2;
}
public void setV2(String v2) {
this.v2 = v2;
}
public Integer getKey() {
return k;
}
@Override
public String toString() {
return "(" + k + "," + v1 + "," + v2 + ")";
}
}
 
R

Robert Klemme

Can anyone think of a way to write a method that takes an array of X,
and produces a HashMap<Key,X>

How would you specify the name of the key field/method?

Maybe you could do it by making X implement an interface that defines
the key.

Perhaps you could do it with reflection.

I'd rather provide an interface that is responsible for the conversion
from X to Key - this is more modular.

package util;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

public class MapUtil {

public interface Transformer<A, B> {
B transform(A a);
}

public static <K, V> Map<K, V> createHash(V[] i, Transformer<V, K> t) {
return createHash(Arrays.asList(i), t);
}

public static <K, V> Map<K, V> createHash(Iterable<V> i,
Transformer<V, K> t) {
final Map<K, V> map = new HashMap<K, V>();
fill(map, i, t);
return map;
}

public static <K, V> void fill(Map<K, V> m, Iterable<V> i,
Transformer<V, K> t) {
for (V val : i) {
m.put(t.transform(val), val);
}
}
}

Kind regards

robert
 
K

Kevin McMurtrie

Roedy Green said:
Can anyone think of a way to write a method that takes an array of X,
and produces a HashMap<Key,X>

How would you specify the name of the key field/method?

Maybe you could do it by making X implement an interface that defines
the key.

Perhaps you could do it with reflection.

If the key is easily derived from the value, what you might have is a
set rather than a map. If that's not the case, Generics trickery will
make it a simple task.


interface Key
{
boolean equals(Object other);
int hashCode ();
// Other stuff
}

interface Keyed
{
Key getKey();
}

public <K extends Keyed> HashMap <Key, K> keyedMap (K[] in)
{
HashMap<Key, K> map= new HashMap<Key, K>(in.length);
for (K keyed : in)
map.put(keyed.getKey(), keyed);
return map;
}


You could even put generics on they Key to support different categories
of keys. It would be nearing the boundary of where Generics stops
making coding easier, though.
 
R

Robert Klemme

I'd rather provide an interface that is responsible for the conversion
from X to Key - this is more modular.

Here's the more generics savvy solution:

package util;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

public class MapUtil {

public interface Transformer<A, B> {
B transform(A a);
}

public static <K, V> Map<K, V> createHash(V[] i, Transformer<? super
V, ? extends K> t) {
return createHash(Arrays.asList(i), t);
}

public static <K, V> Map<K, V> createHash(Iterable<? extends V> i,
Transformer<? super V, ? extends K> t) {
final Map<K, V> map = new HashMap<K, V>();
fill(map, i, t);
return map;
}

public static <K, V> void fill(Map<K, V> m, Iterable<? extends V> i,
Transformer<? super V, ? extends K> t) {
for (V val : i) {
m.put(t.transform(val), val);
}
}
}


Example usage

public static void main(String[] args) {
System.out.println(Arrays.asList(args));
Map<Integer, String> m = createHash(args, new Transformer<String,
Integer>() {
@Override
public Integer transform(String a) {
return a == null ? 0 : a.length();
}
});

System.out.println(m);
}


Cheers

robert
 
R

Robert Klemme

If the key is easily derived from the value, what you might have is a
set rather than a map.  If that's not the case, Generics trickery will
make it a simple task.

Good point. Although I believe that could not be stated for the most
general case.
interface Key
{
  boolean equals(Object other);
  int hashCode ();
  // Other stuff

}

interface Keyed
{
  Key getKey();

}

public <K extends Keyed> HashMap <Key, K> keyedMap (K[] in)
{
  HashMap<Key, K> map= new HashMap<Key, K>(in.length);
  for (K keyed : in)
    map.put(keyed.getKey(), keyed);
  return map;

}

You could even put generics on they Key to support different categories
of keys.  It would be nearing the boundary of where Generics stops
making coding easier, though.

Roedy did not state how exactly the key is obtained for a particular
value although he seems to insinuate that it's a property of the
value. I prefer a more modular approach where the key finding
algorithm is completely decoupled from the value type. This is much
more modular and allows for better reuse. Here are some advantages:

- If you have multiple key candidates making the value type implement
a particular interface limits you to using exactly one of those
candidates. Or you have to wrap values in another type which
implements the key extraction interface which has all sorts of nasty
effects because object identity changes - and it might also be slower,
since more objects are needed.

- You can obtain keys for items which do not exhibit matching
properties at all.

- You can even create a completely new key object based on arbitrary
state which can be extracted from the value object or from somewhere
else (e.g. a counter).

Note also how I have separated Map creation from Map filling in order
to retain even greater modularity of the code. That way you can apply
the key extraction and map filling algorithm to even more Map types
than only HashMap. You can even fill a single map from multiple value
sources.

Kind regards

robert
 
T

Tom Anderson

I'd rather provide an interface that is responsible for the conversion from X
to Key - this is more modular.

That's what i'd do too. Many winters ago, i faced the same problem, and
ended up doing it the interface way - i required my elements to implement:

public interface Keyed {
public Object getKey(); // this was before generics!
}

And then wrote code to build a map using the keys derived from supplied
values. Having had years to digest that design, i now think a separate
key-derivation function is a better idea.

Incidentally, you could use Robert's approach to wrap the reflective
approach easily:

public class KeyExtractor<A, B> implements Transformer<A, B> {
private Method getter;
private KeyExtractor(Class<A> a, String methodName, Class<B> b) {
Method method = a.getMethod(methodName);
if (!b.isAssignableFrom(method.getReturnType())) throw new IllegalArgumentException("bad return type from getter");
this.getter = method;
}
@SuppressWarnings("unchecked")
public B transform (A obj) {
return (B)method.invoke(obj);
}
}

Now you can say:

Customer[] customers;
MapUtil.createHash(customers, new KeyExtractor(Customers.class, "getSalesman", Salesman.class));

Which is amazingly wordy, so maybe you wouldn't bother.

I initially thought Roedy wanted a method to create maps from explicit
lists of keys and values, and wrote this:

public static <K, V> Map<K, V> mapWith(K[] keys, V[] values) {
if (keys.length != values.length) throw new IllegalArgumentException("different number of keys and values");
Map<K, V> map = new LinkedHashMap<K, V>(keys.length);
for (int i = 0; i < keys.length; ++i) {
map.put(keys, values);
}
return map;
}

public static <K> K[] keys(K... keys) {
return keys;
}

public static <V> V[] values(V... values) {
return values;
}

example after suitable static imports: mapWith(keys(1, 2, 3), values("one", "two", "three"))

But that's not what he wanted.

tom
 
D

Daniel Pitts

Can anyone think of a way to write a method that takes an array of X,
and produces a HashMap<Key,X>

How would you specify the name of the key field/method?

Maybe you could do it by making X implement an interface that defines
the key.

public class Table<R> {
private final List<UniqueIndex<?, R>> indexes =
new ArrayList<UniqueIndex<?, R>>();

public <K> UniqueIndex<K, R> addIndex(KeyExtractor<K, R> extractor) {
// create index and add to indexes
}

public void add(R item) {
for(UniqueIndex<?, R> index: indexes) {
index.add(item);
}
}
}

public class UniqueIndex<K, R> {
private final KeyExtractor<K, R> extractor;
private final Map<K, R> map = new HashMap<K, R>();
public R add(R item) {
return map.put(extractor.extract(item), item);
}
public Map<K, R> getMap() { /* return copy */ }
}

public interface KeyExtractor<K, R> {
K extract(R);
}
> Perhaps you could do it with reflection.
Don't use reflection unless you have a really good reason. I would
prefer to use code generation before I use reflection in this situation.
 
S

Stefan Ram

Roedy Green said:
Can anyone think of a way to write a method that takes an
array of X, and produces a HashMap<Key,X>

HashMap< Key, X >m( final X[] x ){ return new HashMap< Key, X >(); }
 
L

Lew

Stefan said:
HashMap< Key, X>m( final X[] x ){ return new HashMap< Key, X>(); }

That is genius. This is my favorite answer so far.

Most of the other answers just made my head hurt. I wonder what the point is.
Rephrasing, what is the value?
 
R

Robert Klemme

Stefan said:
HashMap<  Key, X>m( final X[] x ){ return new HashMap<  Key, X>(); }

That is genius.  This is my favorite answer so far.

Most of the other answers just made my head hurt.  I wonder what the point is.
  Rephrasing, what is the value?

I am sorry your brain hurts. ;-)

Actually it did not occur to me that the code above is a valid
implementation of the requirements before. Rereading Roedy's original
posting I think it *is* a valid implementation although I must say
that I found his question a bit vague. So, Roedy, what is it that you
want?

[ ] an empty HashMap with correct types
[ ] a filled HashMap where keys are found based on the values in the
array
[ ] something else, namely
_________________________________
_________________________________

Kind regards

robert
 
A

Arne Vajhøj

Stefan said:
HashMap< Key, X>m( final X[] x ){ return new HashMap< Key, X>(); }

That is genius. This is my favorite answer so far.

I assume that it was a sarcastic comment on the level of precise
description in Roedy's question.
Most of the other answers just made my head hurt. I wonder what the
point is. Rephrasing, what is the value?

The point in Roedy's questions seems very clear to me. He want
to reuse some code for a conversion that he apparently does
frequently.

Arne
 
R

Roedy Green

Roedy did not state how exactly the key is obtained for a particular
value although he seems to insinuate that it's a property of the
value.

I presumed the key was either a string field in the value or a get
method on the value object for a string. For a somewhat more
difficult problem, allow the key type to be an arbitrary Object of
some generic type.
--
Roedy Green Canadian Mind Products
http://mindprod.com

Beauty is our business.
~ Edsger Wybe Dijkstra (born: 1930-05-11 died: 2002-08-06 at age: 72)

Referring to computer science.
 
R

Roedy Green

Can anyone think of a way to write a method that takes an
array of X, and produces a HashMap<Key,X>

HashMap< Key, X >m( final X[] x ){ return new HashMap< Key, X >(); }

A lawyer's answer that answers the letter of the spec but not the
spirit.
--
Roedy Green Canadian Mind Products
http://mindprod.com

Beauty is our business.
~ Edsger Wybe Dijkstra (born: 1930-05-11 died: 2002-08-06 at age: 72)

Referring to computer science.
 
L

lewis

Stefan Ram wrote, quoted or indirectly quoted someone who said :
HashMap< Key, X >m( final X[] x ){ return new HashMap< Key, X >(); }

Roedy said:
A lawyer's answer that answers the letter of the spec but not the
spirit.

That's exactly what makes is so brilliant and funny.

You could, should you have a sense of humor, also call it a comedian's
answer that answers the letter of the spec but not the spirit.

But yeah, there is that nasty precondition.

Shakespeare wrote, "First, we kill all the lawyers" as a recipe for
how to destabilize a society.
 

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,482
Members
44,901
Latest member
Noble71S45

Latest Threads

Top