1

I'm trying to port this Java tutorial to Xojo. I'm struggling to unpack the Set function below because, whilst short and elegant, it crams a lot of conversions into a small space and I'm not sure if I'm understanding it correctly. It's difficult as Java is not my primary language and Xojo lacks support for generics:

public interface GraphNode {
    String getId();
}


public class Graph<T extends GraphNode> {
    private final Set<T> nodes;
    private final Map<String, Set<String>> connections;

    public T getNode(String id) {
        return nodes.stream()
            .filter(node -> node.getId().equals(id))
            .findFirst()
            .orElseThrow(() -> new IllegalArgumentException("No node found with ID"));
    }

    public Set<T> getConnections(T node) {
        return connections.get(node.getId()).stream()
            .map(this::getNode)
            .collect(Collectors.toSet());
    }
}

I basically can only figure what is happening up to the .stream() method call:

  1. Get the Id of the passed node GraphNode
  2. Get the Set<String> from the connections Map whose key matches the retrieved Id

What I don't understand is what is happening here:

.map(this::getNode).collect(Collectors.toSet())

Can someone provide pseudocode to explain this?

Garry Pettet
  • 8,096
  • 22
  • 65
  • 103
  • Does this answer your question? [:: (double colon) operator in Java 8](https://stackoverflow.com/questions/20001427/double-colon-operator-in-java-8) – jonrsharpe Mar 14 '20 at 18:29
  • 1
    Since Xojo has no Set class, you could simply use a Dictionary with a dummy value (e.g. set to nil or true) instead, and storing the items as the Dictionary's keys. For getNode, loop over all Keys and check if any of them matches the id - and if it matches, return that first match. For getConnections, look over the connections Dictionary and get each their node, and place them into a new set (i.e. Dictionary or array). – Thomas Tempelmann Mar 14 '20 at 18:31

2 Answers2

4

it means map the id to a Node and put (collect) it into a set

this::getNode translates to: from this class, use getNode on the id which is just syntactic sugar for .map(id -> getNode(id)).collect(Collectors.toSet())

public T getNode(String id) {
        return nodes.stream()
            .filter(node -> node.getId().equals(id))
            .findFirst()
            .orElseThrow(() -> new IllegalArgumentException("No node found with ID"));
    }

This code returns the first Node with id in nodes Set, nodes.stream() .filter(node -> node.getId().equals(id)) will return a set where each node has the id passed as an argument, findFirst() will return the first node in the set

public Set<T> getConnections(T node) {
        return connections.get(node.getId()).stream()
            .map(this::getNode)
            .collect(Collectors.toSet());
    }

since connections is a map, connections.get(node.getId()) will return the value with the key node.getId(), then map(this::getNode) maps it from id to Node using getNode(String id), and finally put it into a set

Fibi
  • 427
  • 3
  • 9
2

Streams are basically glorified for-each loops. Generally, when you see XXX.stream() or a method that returns a Stream<XXX>, it means "for each thing in XXX" and "For each XXX..." respectively.

So, here it says "for each string in the Set<String>..."

map means "turn each thing into something else", in other words, a transformation. With a for loop, it's like this in pseudocode:

For Each x In set
    f(x)
Next x

f is the function that you pass in. In this case, it's getNode.

Now, getNode returns T, so every element in our original set has been transformed into a T. The last call is a collect, which means to put all these elements back into some collection, or some other object. In this case, we put all these transformed Ts back into a new Set.

The whole code in pseudocode would be:

originalSet = connections.get(node.getId())
newSet = []
For Each x In originalSet
    newSet.add(x)
Next x
Sweeper
  • 213,210
  • 22
  • 193
  • 313