Ticket #18368: 18368-v2.patch

File 18368-v2.patch, 5.8 KB (added by GerdP, 6 years ago)

Improve performance of NodeGraph.buildSpanningPath(). Remaining sow case: When the graph is disconnected it might still search through all nodes, e.g. when you add an unconnected closed way with a few nodes to Brandenburg.osm search and try to connect all three ways. Will not take foreever but maybe a minute or so to find out no result. I think this case should better be detected in the calling method.

  • src/org/openstreetmap/josm/data/osm/NodeGraph.java

     
    44import java.util.ArrayList;
    55import java.util.Collection;
    66import java.util.Collections;
     7import java.util.Comparator;
     8import java.util.HashMap;
     9import java.util.HashSet;
    710import java.util.LinkedHashMap;
    811import java.util.LinkedHashSet;
    912import java.util.LinkedList;
    1013import java.util.List;
    1114import java.util.Map;
     15import java.util.Map.Entry;
    1216import java.util.Optional;
    1317import java.util.Set;
    1418import java.util.Stack;
     19import java.util.TreeMap;
    1520
    1621import org.openstreetmap.josm.tools.Pair;
    1722
     
    135140    private final Map<Node, List<NodePair>> predecessors = new LinkedHashMap<>();
    136141
    137142    protected void rememberSuccessor(NodePair pair) {
    138         if (successors.containsKey(pair.getA())) {
    139             if (!successors.get(pair.getA()).contains(pair)) {
    140                 successors.get(pair.getA()).add(pair);
    141             }
    142         } else {
    143             List<NodePair> l = new ArrayList<>();
     143        List<NodePair> l = successors.computeIfAbsent(pair.getA(), k -> new ArrayList<>());
     144        if (!l.contains(pair)) {
    144145            l.add(pair);
    145             successors.put(pair.getA(), l);
    146146        }
    147147    }
    148148
    149149    protected void rememberPredecessors(NodePair pair) {
    150         if (predecessors.containsKey(pair.getB())) {
    151             if (!predecessors.get(pair.getB()).contains(pair)) {
    152                 predecessors.get(pair.getB()).add(pair);
    153             }
    154         } else {
    155             List<NodePair> l = new ArrayList<>();
     150        List<NodePair> l = predecessors.computeIfAbsent(pair.getB(), k -> new ArrayList<>());
     151        if (!l.contains(pair)) {
    156152            l.add(pair);
    157             predecessors.put(pair.getB(), l);
    158153        }
    159154    }
    160155
     
    239234        return nodes;
    240235    }
    241236
    242     protected boolean isSpanningWay(Stack<NodePair> way) {
     237    protected boolean isSpanningWay(Collection<NodePair> way) {
    243238        return numUndirectedEges == way.size();
    244239    }
    245240
     
    262257     */
    263258    protected List<Node> buildSpanningPath(Node startNode) {
    264259        if (startNode != null) {
     260            // do not simply replace `Stack` by `ArrayDeque` because of different iteration behaviour, see #11957
    265261            Stack<NodePair> path = new Stack<>();
     262            Set<NodePair> dupCheck = new HashSet<>();
    266263            Stack<NodePair> nextPairs = new Stack<>();
    267264            nextPairs.addAll(getOutboundPairs(startNode));
    268265            while (!nextPairs.isEmpty()) {
    269266                NodePair cur = nextPairs.pop();
    270                 if (!path.contains(cur) && !path.contains(cur.swap())) {
     267                if (!dupCheck.contains(cur) && !dupCheck.contains(cur.swap())) {
    271268                    while (!path.isEmpty() && !path.peek().isPredecessorOf(cur)) {
    272                         path.pop();
     269                        dupCheck.remove(path.pop());
    273270                    }
    274271                    path.push(cur);
     272                    dupCheck.add(cur);
    275273                    if (isSpanningWay(path)) return buildPathFromNodePairs(path);
    276274                    nextPairs.addAll(getOutboundPairs(path.peek()));
    277275                }
     
    283281    /**
    284282     * Tries to find a path through the graph which visits each edge (i.e.
    285283     * the segment of a way) exactly once.
     284     * <p><b>Note that duplicated edges are removed first!</b>
    286285     *
    287286     * @return the path; null, if no path was found
    288287     */
     
    292291        // node which is connected by exactly one undirected edges (or
    293292        // two directed edges in opposite direction) to the graph. A
    294293        // graph built up from way segments is likely to include such
    295         // nodes, unless all ways are closed.
    296         // In the worst case this loops over all nodes which is very slow for large ways.
    297         //
     294        // nodes, unless the edges build one or more closed rings.
     295        // We order the nodes to start with the best candidates, but
     296        // it might take very long if there is no valid path as we iterate over all nodes
     297        // to find out. This will happen with a disconnected graph.
    298298        Set<Node> nodes = getTerminalNodes();
    299         nodes = nodes.isEmpty() ? getNodes() : nodes;
    300         for (Node n: nodes) {
     299        nodes = nodes.isEmpty() ? getMostFrequentVisitedNodesFirst() : nodes;
     300        for (Node n : nodes) {
    301301            List<Node> path = buildSpanningPath(n);
    302302            if (!path.isEmpty())
    303303                return path;
     
    304304        }
    305305        return null;
    306306    }
     307
     308    /**
     309     * Sort the nodes by number of appearances in the edges.
     310     * @return a linked set of all nodes ordered so that the nodes which appear in more edges appear first.
     311     */
     312    private Set<Node> getMostFrequentVisitedNodesFirst() {
     313        if (edges.isEmpty())
     314            return Collections.emptySet();
     315        // count appearance of nodes in edges
     316        Map<Node, Integer> counters = new HashMap<>();
     317        for (NodePair pair : edges) {
     318            Integer c = counters.get(pair.getA());
     319            counters.put(pair.getA(), c == null ? 1 : c + 1);
     320            c = counters.get(pair.getB());
     321            counters.put(pair.getB(), c == null ? 1 : c + 1);
     322        }
     323        // group by counters
     324        TreeMap<Integer, Set<Node>> sortedMap = new TreeMap<>(Comparator.reverseOrder());
     325        for (Entry<Node, Integer> e : counters.entrySet()) {
     326            sortedMap.computeIfAbsent(e.getValue(), LinkedHashSet::new).add(e.getKey());
     327        }
     328        LinkedHashSet<Node> result = new LinkedHashSet<>();
     329        for (Entry<Integer, Set<Node>> e : sortedMap.entrySet()) {
     330            result.addAll(e.getValue());
     331        }
     332        return result;
     333    }
     334
    307335}