Completed
Push — master ( 6be570...be77e1 )
by Benjamin
05:55
created

Dijkstra::findNextParent()   A

Complexity

Conditions 6
Paths 6

Size

Total Lines 18
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 6

Importance

Changes 0
Metric Value
cc 6
eloc 9
nc 6
nop 0
dl 0
loc 18
ccs 10
cts 10
cp 1
crap 6
rs 9.2222
c 0
b 0
f 0
1
<?php
2
3
namespace Bmichotte\Dijkstra;
4
5
class Dijkstra
6
{
7
    private $positions;
8
    private $from;
9
    private $to;
10
    private $weights;
11
    private $predecessors;
12
13 6
    public function __construct(array $positions, Point $from, Point $to)
14
    {
15 6
        $this->positions = $positions;
16 6
        $this->from = $from;
17 6
        $this->to = $to;
18 6
    }
19
20 6
    public function findShortestPath(): array
21
    {
22 6
        $this->weights = [];
23 6
        $this->predecessors = [];
24
25 6
        foreach ($this->positions as $position) {
26 6
            $weight = -1;
27 6
            $passed = false;
28
29 6
            if ($position->equals($this->from)) {
30 6
                $weight = 0;
31 6
                $passed = true;
32
            }
33
34
            // init weights array
35 6
            $this->weights[$position->ref] = [
36 6
                'position' => $position,
37 6
                'weight' => $weight,
38 6
                'passed' => $passed,
39
            ];
40
41
            // init predecessors array
42 6
            $this->predecessors[$position->ref] = [
43 6
                'position' => $position,
44
                'previous' => null,
45
            ];
46
        }
47
48 6
        return $this->run($this->from)->getPath();
49
    }
50
51 6
    protected function run(Point $parent): self
52
    {
53
        // we reached the final point !
54 6
        if ($parent->equals($this->to)) {
55 4
            return $this;
56
        }
57
58
        // we can set this node has been passed by
59 6
        $this->weights[$parent->ref]['passed'] = true;
60
61
        // search for weight between $parent and its children
62 6
        foreach ($parent->points as $child) {
63 4
            $this->calculateWeight($parent, $child);
64
        }
65
66 6
        return $this->findNextParent();
67
    }
68
69 6
    protected function findNextParent(): self
70
    {
71
        // search for the next parent (smallest weight)
72
        // we have to find the smallest weight for a node we didn't passed by atm
73 6
        $smallest = INF;
74 6
        $nextParent = null;
75 6
        foreach ($this->weights as $weight) {
76 6
            if ($weight['weight'] < $smallest && $weight['weight'] !== -1 && ! $weight['passed']) {
77 4
                $smallest = $weight['weight'];
78 4
                $nextParent = $weight['position'];
79
            }
80
        }
81
82 6
        if ($nextParent !== null) {
83 4
            return $this->run($nextParent);
84
        }
85
86 2
        return $this;
87
    }
88
89 6
    protected function getPath(): array
90
    {
91 6
        $path = [$this->to];
92
93 6
        $point = $this->to;
94 6
        while (true) {
95 6
            foreach ($this->predecessors as $predecessor) {
96 6
                if ($predecessor['position']->equals($point)) {
97 6
                    $point = $predecessor['previous'];
98 6
                    $path[] = $point;
99
100 6
                    break;
101
                }
102
            }
103
104
            // $point is null -> path impossible
105 6
            if (is_null($point)) {
106 2
                unset($path[count($path) - 1]);
107 2
                break;
108
            }
109
110 4
            if ($point->equals($this->from)) {
111 4
                break;
112
            }
113
        }
114
115 6
        $path = array_reverse($path);
116
117 6
        return $path;
118
    }
119
120 4
    protected function calculateWeight(Point $parent, Point $child): void
121
    {
122
        /*
123
         * Dijkstra algo says :
124
         *
125
         * IF (child-node is not traversed yet) AND
126
         *    (WEIGHT(parent-node) + WEIGHT(DISTANCE(parent-node, child-node) < WEIGHT(child-node) OR WEIGHT(child-node) = -1)
127
         * THEN
128
         *    WEIGHT(child-node) = WEIGHT(parent-node) + WEIGHT(DISTANCE(parent-node, child-node)
129
         *  PREDECESSOR(child-node) = parent-node
130
         * ENDIF
131
         */
132 4
        if (! $this->weights[$child->ref]['passed']
133 4
            && ($this->weights[$parent->ref]['weight'] + self::distance($parent, $child) < $this->weights[$child->ref]['weight']
134 4
                || $this->weights[$child->ref]['weight'] === -1)) {
135 4
            $this->weights[$child->ref]['weight'] = $this->weights[$parent->ref]['weight'] + self::distance($parent, $child);
136 4
            $this->predecessors[$child->ref]['previous'] = $parent;
137
        }
138 4
    }
139
140 4
    public static function distance(Point $p1, Point $p2): float
141
    {
142
        // distance = square root of ((x2 - x1)^2 + (y2 - y1)^2)
143 4
        return sqrt(bcpow($p2->x - $p1->x, 2) + bcpow($p2->y - $p1->y, 2));
144
    }
145
}
146