DijkstraClosestFirstIterator   A
last analyzed

Complexity

Total Complexity 17

Size/Duplication

Total Lines 166
Duplicated Lines 0 %

Test Coverage

Coverage 96.23%

Importance

Changes 0
Metric Value
eloc 53
dl 0
loc 166
ccs 51
cts 53
cp 0.9623
rs 10
c 0
b 0
f 0
wmc 17

6 Methods

Rating   Name   Duplication   Size   Complexity  
A next() 0 23 4
A updateDistance() 0 11 3
A hasNext() 0 12 3
A getDistanceAndPredecessorMap() 0 14 3
A __construct() 0 15 3
A getPaths() 0 3 1
1
<?php
2
3
namespace Graphp\Alg\Shortestpath;
4
5
use Exception;
6
use InvalidArgumentException;
7
use Heap\AddressableHeapInterface;
8
use Heap\Tree\PairingHeap;
9
use Graphp\GraphInterface;
10
use Graphp\GraphUtils;
11
use Graphp\Edge\EdgeInterface;
12
use Graphp\Vertex\VertexInterface;
13
use Graphp\Util\Supplier;
14
use Graphp\Alg\Interfaces\SingleSourcePathsInterface;
15
16
/**
17
 * Class DijkstraClosestFirstIterator
18
 *
19
 * @package Graphp\Alg\Shortestpath;
20
 */
21
class DijkstraClosestFirstIterator
22
{
23
    /**
24
     * The graph
25
     *
26
     * @var GraphInterface
27
     */
28
    private $graph;
29
30
    /**
31
     * The source vertex
32
     *
33
     * @var VertexInterface
34
     */
35
    private $source;
36
37
    /**
38
     * The radius
39
     *
40
     * @var float
41
     */
42
    private $radius;
43
44
    /**
45
     * Already seen vertices
46
     *
47
     * @var array
48
     */
49
    private $seen = [];
50
51
    /**
52
     * The underlying heap implementation
53
     *
54
     * @var AddressableHeapInterface
55
     */
56
    private $heap;
57
58
    /**
59
     * Create a new iterator for the specified graph.
60
     * If radius is specified, iteration will start at the source vertex and
61
     * will be limited to the subset of paths of weighted length less than or equal to the radius
62
     *
63
     * @param GraphInterface $graph - the graph
64
     * @param VertexInterface $source - the source vertex
65
     * @param float $radius - the limit on weighted path length
66
     * @param Supplier $heapSupplier - supplier of the preferable heap implementation
67
     *
68
     * @throws InvalidArgumentException
69
     */
70 6
    public function __construct(
71
        GraphInterface $graph,
72
        VertexInterface $source,
73
        ?float $radius = null,
74
        ?Supplier $heapSupplier = null
75
    ) {
76 6
        $this->graph = $graph;
77 6
        $this->source = $source;
78 6
        $this->radius = $radius ?? INF;
79 6
        if ($this->radius < 0.0) {
80
            throw new InvalidArgumentException("Radius must be non-negative");
81
        }
82 6
        $this->seen = [];
83 6
        $this->heap = is_null($heapSupplier) ? new PairingHeap() : $heapSupplier->get();
84 6
        $this->updateDistance($this->source, null, 0.0);
85 6
    }
86
87
    /**
88
     * Checks if iterator has next element to look up
89
     *
90
     * @return bool
91
     */
92 6
    public function hasNext(): bool
93
    {
94 6
        if ($this->heap->isEmpty()) {
95 3
            return false;
96
        }
97 6
        $vNode = $this->heap->findMin();
98 6
        $vDistance = $vNode->getKey();
99 6
        if ($this->radius < $vDistance) {
100 2
            $this->heap->clear();
101 2
            return false;
102
        }
103 6
        return true;
104
    }
105
106
    /**
107
     * Get the next vertex
108
     *
109
     * @return VertexInterface
110
     */
111 6
    public function next(): VertexInterface
112
    {
113 6
        if (!$this->hasNext()) {
114
            throw new Exception("No such element!");
115
        }
116
117
        // settle next node
118 6
        $vNode = $this->heap->deleteMin();
119 6
        $v = $vNode->getValue()[0];
120 6
        $vDistance = $vNode->getKey();
121
        
122
        // relax edges
123 6
        $edges = $this->graph->outgoingEdgesOf($v);
124 6
        foreach ($edges as $edge) {
125 6
            $u = GraphUtils::getOppositeVertex($this->graph, $edge, $v);
126 6
            $eWeight = $this->graph->getEdgeWeight($edge);
127 6
            if ($eWeight < 0.0) {
128 1
                throw new InvalidArgumentException("Negative edge weight not allowed");
129
            }
130 5
            $this->updateDistance($u, $edge, $vDistance + $eWeight);
131
        }
132
133 5
        return $v;
134
    }
135
136
    /**
137
     * Get the paths computed by the iterator
138
     *
139
     * @return SingleSourcePathsInterface
140
     */
141 5
    public function getPaths(): SingleSourcePathsInterface
142
    {
143 5
        return new TreeSingleSourcePaths($this->graph, $this->source, $this->getDistanceAndPredecessorMap());
144
    }
145
146
    /**
147
     * Get all paths using the traditional representation of the shortest path tree, which stores
148
     * for each vertex (a) the distance of the path from the source vertex and (b) the last edge
149
     * used to reach the vertex from the source vertex
150
     *
151
     * @return array
152
     */
153 5
    public function getDistanceAndPredecessorMap(): array
154
    {
155 5
        $distanceAndPredecessorMap = [];
156
157 5
        foreach ($this->seen as $vertexHash => $vNode) {
158 5
            $vDistance = $vNode->getKey();
159 5
            if ($this->radius < $vDistance) {
160 2
                continue;
161
            }
162 5
            $v = $vNode->getValue()[0];
0 ignored issues
show
Unused Code introduced by
The assignment to $v is dead and can be removed.
Loading history...
163 5
            $distanceAndPredecessorMap[$vertexHash] = [$vDistance, $vNode->getValue()[1]];
164
        }
165
166 5
        return $distanceAndPredecessorMap;
167
    }
168
169
    /**
170
     * Update the distance to the vertex
171
     *
172
     * @param VertexInterface $v - the vertex
173
     * @param EdgeInterface $e - the predecessor edge
174
     * @param float $distance - the new distance
175
     */
176 6
    private function updateDistance(VertexInterface $v, ?EdgeInterface $e = null, float $distance): void
177
    {
178 6
        $id = $v->getHash();
179 6
        if (!array_key_exists($id, $this->seen)) {
180 6
            $node = $this->heap->insert($distance, [$v, $e]);
181 6
            $this->seen[$id] = $node;
182
        } else {
183 5
            $node = $this->seen[$id];
184 5
            if ($distance < $node->getKey()) {
185 5
                $node->decreaseKey($distance);
186 5
                $node->setValue([$node->getValue()[0], $e]);
187
            }
188
        }
189 6
    }
190
}
191