5

I have a mind-block when trying to create data-structure that follows the pattern:

Map<String, T> is a main building block and T is either Map<String, T> or as terminal operator List<String>. Is it possible to build anything similar in Java, this idea comes from functional languages like F# or Haskell-like.

I searched SO but so far couldn't find anything that match my idea in Java.

Dmytro Chasovskyi
  • 3,209
  • 4
  • 40
  • 82

4 Answers4

4

Yes: you can do something like this:

public abstract class T {
...
}
public class NonTerminal extends T {
    private Map<String,T> map = new HashMap<>();
...
}
public class Terminal extends T {
    private List<String> list;
---
}
Maurice Perry
  • 9,261
  • 2
  • 12
  • 24
  • 3
    I'd go with an interface rather than an abstract class. There is no need to limit inheritance for this. – RealSkeptic Feb 18 '19 at 08:44
  • 1
    I'd add private constructor to the abstract class and put the subclasses as static nested classes to make it look like an algebraic data type. – Some Name Feb 18 '19 at 09:04
3

Recreating functional programming stuff in Java isn't really a good idea (at least not in Java 8, I don't know about Java 11).

You can do something like this:

class EitherMapOrList {
    private Map<String, EitherMapOrList> map;
    private List<String> list;

    public EitherMapOrList(Map<String, EitherMapOrList> map) {
        this.map = map;
    }

    public EitherMapOrList(List<String> list) {
        this.list = list;
    }
    // you can remove the optionals here and use null directly.
    public Optional<Map<String, EitherMapOrList>> getMap() {
        return Optional.ofNullable(map);
    }

    public Optional<List<String>> getList() {
        return Optional.ofNullable(list);
    }
}

And then create a Map<String, EitherMapOrList>.

But I would imagine it would be a pain to use this thing in Java.

Sweeper
  • 213,210
  • 22
  • 193
  • 313
3

You could use just a single Map<String, KeyOrValue> where the value could be a marker interface with two implementations

interface KeyOrValue {}

class Key implements KeyOrValue {
    private String key;
}

class Value implements KeyOrValue {
    private List<String> values;
}

You could then just create a lookup method which recursivly calls itself and then returns the value once it has reached the end:

private final Map<String, KeyOrValue> map = ...

public List<String> getValues(String key) {
    KeyOrValue keyOrValue = map.get(key);
    if(keyOrValue instanceof Key) {
        // is a key, so use recursion to get the value
        key = ((Key) keyOrValue).key;
        return getValues(key);
    } else if(keyOrValue instanceof Value) {
        // is a value, so just return the value it holds
        return ((Value) keyOrValue).values;
    } else {
        // no mapping was found for "key"
        return null;
    }
}

You could do the same without recursion too:

public List<String> getValues(String key) {
    KeyOrValue keyOrValue;
    List<String> values = null;
    do {
        keyOrValue = map.get(key);
        if(keyOrValue instanceof Key) {
            // is a key, so iterate further
            key = ((Key) keyOrValue).key;
        } else if(keyOrValue instanceof Value) {
            // is a value, so get the values out and set the key to null to break the loop
            values = ((Value) keyOrValue).values;
            key = null;
        }
    } while(key != null);

    // return the values, may be null due to nothing being found
    return values;
}

The marker interface is not really needed though, you can get the same outcome if you just use Map<String, Object> where the value could either be a String or a List<String> and then the instanceof checks would have to be adapted too, but I like the approach the way with an interface more

Lino
  • 19,604
  • 6
  • 47
  • 65
2

If you want to translate haskell

data Map a = Branch { key :: String, value :: a, left :: Map a, right :: Map a} | MapNul

to java you can go with:

class Map<T> {
    String key;
    T value;
    Map<T> left;
    Map<T> right;
} 

you do not need MapNul in java because you can use null instead of it.

talex
  • 17,973
  • 3
  • 29
  • 66