Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
100.00% |
23 / 23 |
|
100.00% |
2 / 2 |
CRAP | |
100.00% |
1 / 1 |
PageRank | |
100.00% |
23 / 23 |
|
100.00% |
2 / 2 |
14 | |
100.00% |
1 / 1 |
__construct | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
8 | |||
calculateRanks | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
6 |
1 | <?php |
2 | /** |
3 | * Jingga |
4 | * |
5 | * PHP Version 8.1 |
6 | * |
7 | * @package phpOMS\Business\Marketing |
8 | * @copyright Dennis Eichhorn |
9 | * @license OMS License 2.0 |
10 | * @version 1.0.0 |
11 | * @link https://jingga.app |
12 | */ |
13 | declare(strict_types=1); |
14 | |
15 | namespace phpOMS\Business\Marketing; |
16 | |
17 | /** |
18 | * PageRank algorithm |
19 | * |
20 | * @package phpOMS\Business\Marketing |
21 | * @license OMS License 2.0 |
22 | * @link https://jingga.app |
23 | * @since 1.0.0 |
24 | */ |
25 | final class PageRank |
26 | { |
27 | /** |
28 | * Damping value |
29 | * |
30 | * @var float |
31 | * @since 1.0.0 |
32 | */ |
33 | private float $damping = 0.85; |
34 | |
35 | /** |
36 | * Page rank |
37 | * |
38 | * @var array<mixed, float> |
39 | * @since 1.0.0 |
40 | */ |
41 | private array $pageRanks = []; |
42 | |
43 | /** |
44 | * Relation array |
45 | * |
46 | * Array of elements where every element has an array of incoming links/relations |
47 | * |
48 | * @var array[] |
49 | * @since 1.0.0 |
50 | */ |
51 | private array $relations = []; |
52 | |
53 | /** |
54 | * Amount of outgoing links from an element |
55 | * |
56 | * @var int[] |
57 | * @since 1.0.0 |
58 | */ |
59 | private array $outgoing = []; |
60 | |
61 | /** |
62 | * Constructor. |
63 | * |
64 | * @param array[] $relations Relations between elements (keys => link from, array => link to) |
65 | * @param bool $isUnique Only consider unique relations |
66 | * @param float $damping Damping value |
67 | * |
68 | * @since 1.0.0 |
69 | */ |
70 | public function __construct(array $relations, bool $isUnique = true, float $damping = 0.85) |
71 | { |
72 | $this->damping = $damping; |
73 | |
74 | foreach ($relations as $key => $relation) { |
75 | $this->outgoing[$key] = \count($relation); |
76 | |
77 | if (!isset($this->relations[$key])) { |
78 | $this->relations[$key] = []; |
79 | } |
80 | |
81 | foreach ($relation as $linkTo) { |
82 | if (!isset($this->relations[$linkTo])) { |
83 | $this->relations[$linkTo] = []; |
84 | } |
85 | |
86 | if (!isset($this->outgoing[$linkTo])) { |
87 | $this->outgoing[$linkTo] = 0; |
88 | } |
89 | |
90 | if (!$isUnique || !\in_array($key, $this->relations[$linkTo])) { |
91 | $this->relations[$linkTo][] = $key; |
92 | } |
93 | } |
94 | } |
95 | } |
96 | |
97 | /** |
98 | * Calcualte the rank based on a start rank for the different elements |
99 | * |
100 | * A different start rank for different elements might make sense if the elements are not uniform from the very beginning |
101 | * |
102 | * @param int $iterations Algorithm iterations |
103 | * @param null|array<mixed, float> $startRank Start rank for an element |
104 | * |
105 | * @return array |
106 | * |
107 | * @since 1.0.0 |
108 | */ |
109 | public function calculateRanks(int $iterations = 20, array $startRank = null) : array |
110 | { |
111 | if ($startRank !== null) { |
112 | $this->pageRanks = $startRank; |
113 | } else { |
114 | foreach ($this->relations as $key => $relation) { |
115 | $this->pageRanks[$key] = 0.0; |
116 | } |
117 | } |
118 | |
119 | for ($i = 0; $i < $iterations; ++$i) { |
120 | foreach ($this->relations as $key => $relation) { |
121 | $PR = 0.0; |
122 | |
123 | foreach ($relation as $linkFrom) { |
124 | $PR += $this->pageRanks[$linkFrom] / $this->outgoing[$linkFrom]; |
125 | } |
126 | |
127 | $this->pageRanks[$key] = 1 - $this->damping + $this->damping * $PR; |
128 | } |
129 | } |
130 | |
131 | return $this->pageRanks; |
132 | } |
133 | } |