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; namespace Phpml\Math\Distance;
use Phpml\Exception\InvalidArgumentException; /**
use Phpml\Math\Distance; * Class Chebyshev
*/
class Chebyshev implements Distance class Chebyshev extends Distance
{ {
/** /**
* @throws InvalidArgumentException * {@inheritdoc}
*/ */
public function distance(array $a, array $b): float public function distance(array $a, array $b): float
{ {
if (count($a) !== count($b)) { return max($this->deltas($a, $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);
} }
} }

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; namespace Phpml\Math\Distance;
use Phpml\Exception\InvalidArgumentException; /**
use Phpml\Math\Distance; * Class Euclidean
*
class Euclidean implements Distance * 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)) { parent::__construct(2.0);
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);
} }
/** /**
* Square of Euclidean distance * Square of Euclidean distance
*
* @throws \Phpml\Exception\InvalidArgumentException
*/ */
public function sqDistance(array $a, array $b): float public function sqDistance(array $a, array $b): float
{ {

View File

@ -4,22 +4,18 @@ declare(strict_types=1);
namespace Phpml\Math\Distance; namespace Phpml\Math\Distance;
use Phpml\Exception\InvalidArgumentException; /**
use Phpml\Math\Distance; * Class Manhattan
*
class Manhattan implements Distance * 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)) { parent::__construct(1.0);
throw new InvalidArgumentException('Size of given arrays does not match');
}
return array_sum(array_map(function ($m, $n) {
return abs($m - $n);
}, $a, $b));
} }
} }

View File

@ -4,37 +4,11 @@ declare(strict_types=1);
namespace Phpml\Math\Distance; namespace Phpml\Math\Distance;
use Phpml\Exception\InvalidArgumentException; /**
use Phpml\Math\Distance; * Class Minkowski
*
class Minkowski implements Distance * 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);
}
} }