views:

146

answers:

3

We know that Map interface models function abstraction in math. How should I model multi-variable function? For example, to model f(x, y, z), I have two options:

Map<List<Integer>, Integer> f1;

or

Map<Integer, Map<Integer, Map<Integer, Integer>>> f2;

Which one do you think is better?

Thanks,

+3  A: 

A third option, which I would prefer, is to define a simple class with three Integer fields x, y and z (call it Triple, for example) and use Map<Triple, Integer>.

If you need this kind of thing often you could make Triple itself generic, and use a Map<Triple<Integer>, Integer> (or if some of your "functions" need to have arguments of different types it could even be a Map<Triple<Integer, Integer, Integer>, Integer> but this is starting to lose readability IMHO).

Alex Martelli
+4  A: 

Neither.

The teqnique you're looking for is called uncurrying. We know that a function represents a logical implication:

∀ a b. a -> b

We also know that if A and B imply C, then A implies that B implies C:

∀ a b c. ((a, b) => c) <=> (a => b => c)

Here's proof. Look at a truth table for logical implication and you'll see that these are equivalent. So how do you represent A and B at the type level? You use a product type. The product of two types is a pair, i.e. a type whose values have both of two types:

interface P2<A, B> { public A _1(); public B _2(); }

You can do the same with triples, or tuples of any arity. Beware though, since this line of reasoning leads you to Heterogeneous Lists, which aren't pretty in Java.

Also, your Map representation is only appropriate for partial functions. What's more, the JDK's Map interface is designed to be mutable, and whoever heard of a mutable function? Here's a better representation:

interface F<A, B> { public B apply(A a); }
Apocalisp
I think a mutable partial function is needed when the function is being built from ground up (by mapping each (x,y,z) to the value of f). The function has to be partial because it's being built and has to be mutable in order to be built.
gineer
It's true that if you want to implement your function as a hash table, then your best choice is to build it with a mutable HashMap, but once constructed, you don't want to mutate it again, and you probably want to hide the fact that it's implemented that way.
Apocalisp
A: 

Your first choice is usually better, since it requires less memory and requires less code to access:

Map<List<Integer>, Integer> f1 = ... 
f1.put(Arrays.asList(1, 2, 3), 7);
Integer v = f1.get(Arrays.asList(1, 2, 3));

Use a map of maps only when the access patterns require it, such as

Map<Integer, Map<Integer, Map<Integer, Integer>>> f2 = ...
Set<Integer> keys = f2.keySet();
Jared Levy