Completed
Push — develop ( f7b91b...01a249 )
by Arkadiusz
02:37
created

DBSCAN::expandCluster()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 18
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 18
rs 9.2
cc 4
eloc 10
nc 4
nop 2
1
<?php
2
3
declare (strict_types = 1);
4
5
namespace Phpml\Clustering;
6
7
use Phpml\Math\Distance;
8
use Phpml\Math\Distance\Euclidean;
9
10
class DBSCAN implements Clusterer
11
{
12
    /**
13
     * @var float
14
     */
15
    private $epsilon;
16
17
    /**
18
     * @var int
19
     */
20
    private $minSamples;
21
22
    /**
23
     * @var Distance
24
     */
25
    private $distanceMetric;
26
27
    /**
28
     * @param float    $epsilon
29
     * @param int      $minSamples
30
     * @param Distance $distanceMetric
31
     */
32
    public function __construct($epsilon = 0.5, $minSamples = 3, Distance $distanceMetric = null)
33
    {
34
        if (null === $distanceMetric) {
35
            $distanceMetric = new Euclidean();
36
        }
37
38
        $this->epsilon = $epsilon;
39
        $this->minSamples = $minSamples;
40
        $this->distanceMetric = $distanceMetric;
41
    }
42
43
    /**
44
     * @param array $samples
45
     *
46
     * @return array
47
     */
48
    public function cluster(array $samples)
49
    {
50
        $clusters = [];
51
        $visited = [];
52
53
        foreach ($samples as $index => $sample) {
54
            if (isset($visited[$index])) {
55
                continue;
56
            }
57
            $visited[$index] = true;
58
59
            $regionSamples = $this->getSamplesInRegion($sample, $samples);
60
            if (count($regionSamples) >= $this->minSamples) {
61
                $clusters[] = $this->expandCluster($regionSamples, $visited);
62
            }
63
        }
64
65
        return $clusters;
66
    }
67
68
    /**
69
     * @param array $localSample
70
     * @param array $samples
71
     *
72
     * @return array
73
     */
74
    private function getSamplesInRegion($localSample, $samples)
75
    {
76
        $region = [];
77
78
        foreach ($samples as $index => $sample) {
79
            if ($this->distanceMetric->distance($localSample, $sample) < $this->epsilon) {
80
                $region[$index] = $sample;
81
            }
82
        }
83
84
        return $region;
85
    }
86
87
    /**
88
     * @param array $samples
89
     * @param array $visited
90
     *
91
     * @return array
92
     */
93
    private function expandCluster($samples, &$visited)
94
    {
95
        $cluster = [];
96
97
        foreach ($samples as $index => $sample) {
98
            if (!isset($visited[$index])) {
99
                $visited[$index] = true;
100
                $regionSamples = $this->getSamplesInRegion($sample, $samples);
101
                if (count($regionSamples) > $this->minSamples) {
102
                    $cluster = array_merge($regionSamples, $cluster);
103
                }
104
            }
105
106
            $cluster[] = $sample;
107
        }
108
109
        return $cluster;
110
    }
111
}
112