Passed
Push — master ( 979da0...21dcf7 )
by Jesse
01:38
created

BellmanFordPathfinder::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 2
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
1
<?php declare(strict_types=1);
2
3
namespace Stratadox\Pathfinder;
4
5
use function count;
6
use const INF;
7
use Stratadox\Pathfinder\Reconstruction\PathRetracer;
8
9
final class BellmanFordPathfinder implements MultiPathfinder
10
{
11
    private $network;
12
    private $path;
13
14
    private function __construct(Network $network)
15
    {
16
        $this->network = $network;
17
        $this->path = new PathRetracer();
18
    }
19
20
    public static function operatingIn(Network $graph): MultiPathfinder
21
    {
22
        return new self($graph);
23
    }
24
25
    public function from(string $start): array
26
    {
27
        return $this->shortestPaths($this->startingAt($start), $start);
28
    }
29
30
    private function shortestPaths(array $lastSteps, string $start): array
31
    {
32
        $paths = [];
33
        foreach ($lastSteps as $goal => $lastStep) {
34
            if ($goal !== $start) {
35
                $paths[$goal] = $this->path->retrace($start, $goal, $lastSteps);
36
            }
37
        }
38
        return $paths;
39
    }
40
41
    /** @throws NoPathAvailable */
42
    private function startingAt(string $start): array
43
    {
44
        if (!$this->network->has($start)) {
45
            throw NonExistingStart::tried($start);
46
        }
47
48
        $distanceTo = [$start => 0];
49
        $lastStepBefore = [];
50
51
        /** @var string[] $nodes */
52
        $nodes = $this->network->all()->items();
53
        for ($i = count($nodes); $i > 0; --$i) {
54
            foreach ($nodes as $from) {
55
                foreach ($this->network->neighboursOf($from) as $to) {
56
                    $proposal = ($distanceTo[$from] ?? INF) +
57
                        $this->network->movementCostBetween($from, $to);
58
                    if ($proposal < ($distanceTo[$to] ?? INF)) {
59
                        $distanceTo[$to] = $proposal;
60
                        $lastStepBefore[$to] = $from;
61
                    }
62
                }
63
            }
64
        }
65
        foreach ($nodes as $from) {
66
            foreach ($this->network->neighboursOf($from) as $to) {
67
                $proposal = ($distanceTo[$from] ?? INF) +
68
                    $this->network->movementCostBetween($from, $to);
69
                if ($proposal < ($distanceTo[$to] ?? INF)) {
70
                    throw NegativeCycleDetected::amongTheNodes($from, $to);
71
                }
72
            }
73
        }
74
        return $lastStepBefore;
75
    }
76
}
77