Improve distance performance and reduce duplication in distance classes. (#348)

* Issue #347: Reduce duplicated code.

* Issue #347: Replace array_* with regular loops for better perfomance.
This commit is contained in:
Pol Dellaiera 2019-02-06 08:00:17 +01:00 committed by Arkadiusz Kondas
parent 6844cf407a
commit 4b837fae8e
5 changed files with 93 additions and 79 deletions

View File

@ -4,27 +4,16 @@ declare(strict_types=1);
namespace Phpml\Math\Distance;
use Phpml\Exception\InvalidArgumentException;
use Phpml\Math\Distance;
class Chebyshev implements Distance
/**
* Class Chebyshev
*/
class Chebyshev extends Distance
{
/**
* @throws InvalidArgumentException
* {@inheritdoc}
*/
public function distance(array $a, array $b): float
{
if (count($a) !== count($b)) {
throw new InvalidArgumentException('Size of given arrays does not match');
}
$differences = [];
$count = count($a);
for ($i = 0; $i < $count; ++$i) {
$differences[] = abs($a[$i] - $b[$i]);
}
return max($differences);
return max($this->deltas($a, $b));
}
}

View File

@ -0,0 +1,61 @@
<?php
declare(strict_types=1);
namespace Phpml\Math\Distance;
use Phpml\Exception\InvalidArgumentException;
use Phpml\Math\Distance as DistanceInterface;
/**
* Class Distance
*/
abstract class Distance implements DistanceInterface
{
/**
* @var float|int
*/
public $norm;
/**
* Distance constructor.
*/
public function __construct(float $norm = 3.0)
{
$this->norm = $norm;
}
/**
* @throws InvalidArgumentException
*/
public function distance(array $a, array $b): float
{
$distance = 0;
foreach ($this->deltas($a, $b) as $delta) {
$distance += $delta ** $this->norm;
}
return $distance ** (1 / $this->norm);
}
/**
* @throws InvalidArgumentException
*/
protected function deltas(array $a, array $b): array
{
$count = count($a);
if ($count !== count($b)) {
throw new InvalidArgumentException('Size of given arrays does not match');
}
$deltas = [];
for ($i = 0; $i < $count; $i++) {
$deltas[] = abs($a[$i] - $b[$i]);
}
return $deltas;
}
}

View File

@ -4,31 +4,25 @@ declare(strict_types=1);
namespace Phpml\Math\Distance;
use Phpml\Exception\InvalidArgumentException;
use Phpml\Math\Distance;
class Euclidean implements Distance
/**
* Class Euclidean
*
* L^2 Metric.
*/
class Euclidean extends Distance
{
/**
* @throws InvalidArgumentException
* Euclidean constructor.
*/
public function distance(array $a, array $b): float
public function __construct()
{
if (count($a) !== count($b)) {
throw new InvalidArgumentException('Size of given arrays does not match');
}
$distance = 0;
foreach ($a as $i => $val) {
$distance += ($val - $b[$i]) ** 2;
}
return sqrt((float) $distance);
parent::__construct(2.0);
}
/**
* Square of Euclidean distance
*
* @throws \Phpml\Exception\InvalidArgumentException
*/
public function sqDistance(array $a, array $b): float
{

View File

@ -4,22 +4,18 @@ declare(strict_types=1);
namespace Phpml\Math\Distance;
use Phpml\Exception\InvalidArgumentException;
use Phpml\Math\Distance;
class Manhattan implements Distance
/**
* Class Manhattan
*
* L^1 Metric.
*/
class Manhattan extends Distance
{
/**
* @throws InvalidArgumentException
* Manhattan constructor.
*/
public function distance(array $a, array $b): float
public function __construct()
{
if (count($a) !== count($b)) {
throw new InvalidArgumentException('Size of given arrays does not match');
}
return array_sum(array_map(function ($m, $n) {
return abs($m - $n);
}, $a, $b));
parent::__construct(1.0);
}
}

View File

@ -4,37 +4,11 @@ declare(strict_types=1);
namespace Phpml\Math\Distance;
use Phpml\Exception\InvalidArgumentException;
use Phpml\Math\Distance;
class Minkowski implements Distance
/**
* Class Minkowski
*
* L^n Metric.
*/
class Minkowski extends Distance
{
/**
* @var float
*/
private $lambda;
public function __construct(float $lambda = 3.0)
{
$this->lambda = $lambda;
}
/**
* @throws InvalidArgumentException
*/
public function distance(array $a, array $b): float
{
if (count($a) !== count($b)) {
throw new InvalidArgumentException('Size of given arrays does not match');
}
$distance = 0;
$count = count($a);
for ($i = 0; $i < $count; ++$i) {
$distance += pow(abs($a[$i] - $b[$i]), $this->lambda);
}
return (float) pow($distance, 1 / $this->lambda);
}
}