How to create recursive tree-like data structure in java using Map<String, T>?
Asked Answered
C

4

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.

Conjugate answered 18/2, 2019 at 8:35 Comment(0)
Z
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;
---
}
Zebada answered 18/2, 2019 at 8:43 Comment(2)
I'd go with an interface rather than an abstract class. There is no need to limit inheritance for this.Weatherford
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.Michaelmas
A
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.

Abbess answered 18/2, 2019 at 8:52 Comment(0)
M
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

Mascarenas answered 18/2, 2019 at 8:59 Comment(0)
L
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.

Liverpool answered 18/2, 2019 at 8:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.