This commit is contained in:
Dennis Eichhorn 2023-09-10 18:58:35 +00:00
parent 4469789af4
commit 018b9ce981
23 changed files with 388 additions and 133 deletions

View File

@ -166,8 +166,8 @@ final class DBSCAN
/**
* Find neighbors of a point
*
* @param PointInterface $point Base point for potential neighbors
* @param float $epsion Max distance to neighbor
* @param PointInterface $point Base point for potential neighbors
* @param float $epsilon Max distance to neighbor
*
* @return array
*
@ -210,6 +210,7 @@ final class DBSCAN
}
}
/** @var float[] $distances */
return $distances;
}
@ -228,10 +229,7 @@ final class DBSCAN
foreach ($this->clusters as $c => $cluster) {
$points = [];
foreach ($cluster as $p) {
$points[] = [
'x' => \reset($p->coordinates),
'y' => \end($p->coordinates),
];
$points[] = $p->coordinates;
}
$this->convexHulls[$c] = MonotoneChain::createConvexHull($points);
@ -239,14 +237,7 @@ final class DBSCAN
}
foreach ($this->convexHulls as $c => $hull) {
if (Polygon::isPointInPolygon(
[
'x' => \reset($point->coordinates),
'y' => \end($point->coordinates),
],
$hull
) <= 0
) {
if (Polygon::isPointInPolygon($point->coordinates, $hull) <= 0) {
return $c;
}
}

View File

@ -239,8 +239,8 @@ final class MeanShift
/**
* Find the closest cluster/group of a point
*
* @param PointInterface $point Point to find the cluster for
* @param PointInterface[] $group Clusters
* @param PointInterface $point Point to find the cluster for
* @param array<PointInterface[]> $groups Clusters
*
* @return int
*

View File

@ -18,8 +18,9 @@ namespace phpOMS\Algorithm\Clustering;
/**
* Point interface.
*
* @property int $group Group
* @property string $name Name
* @property int $group Group
* @property string $name Name
* @property array $coordinates Coordinates
*
* @package phpOMS\Algorithm\Clustering;
* @license OMS License 2.0

View File

@ -72,7 +72,7 @@ final class Apriori
*
* @param array<array> $sets Sets of a set (e.g. [[1,2,3,4], [1,2], [1]])
*
* @return array<array>
* @return array
*
* @since 1.0.0
*/

View File

@ -0,0 +1,29 @@
<?php
/**
* Jingga
*
* PHP Version 8.1
*
* @package phpOMS\Algorithm\Graph
* @copyright Dennis Eichhorn
* @license OMS License 2.0
* @version 1.0.0
* @link https://jingga.app
*/
declare(strict_types=1);
namespace phpOMS\Algorithm\Graph;
/**
* Markov chain
*
* @package phpOMS\Algorithm\Graph
* @license OMS License 2.0
* @link https://jingga.app
* @since 1.0.0
*
* @todo Implement
*/
final class MarkovChain
{
}

View File

@ -143,8 +143,8 @@ class GeneticOptimization
$fitnesses = [];
foreach ($population as $parameters) {
$fitnesses[$population] = ($fitness)($parameters);
foreach ($population as $key => $parameters) {
$fitnesses[$key] = ($fitness)($parameters);
}
\asort($fitnesses);

View File

@ -81,7 +81,7 @@ class TabuSearch
for ($i = 0; $i < $iterations; ++$i) {
$neighbors = [];
for ($i = 0; $i < $tabuListSize; ++$i) {
for ($j = 0; $j < $tabuListSize; ++$j) {
$neighbor = ($neighbor)($currentSolution);
$neighbors[] = $neighbor;
}

View File

@ -20,8 +20,8 @@ namespace phpOMS\Algorithm\Rating;
* @package phpOMS\Algorithm\Rating
* @license OMS License 2.0
* @link https://jingga.app
* @since 1.0.0
* @see https://en.wikipedia.org/wiki/Bradley%E2%80%93Terry_model
* @since 1.0.0
*/
final class BradleyTerry
{

View File

@ -61,7 +61,7 @@ final class Glicko1
*
* @see calculateC();
*
* @var int
* @var float
* @since 1.0.0
*/
public float $DEFAULT_C = 34.6;
@ -127,7 +127,6 @@ final class Glicko1
) : array
{
// Step 1:
$s = [];
$E = [];
$gRD = [];
@ -145,8 +144,8 @@ final class Glicko1
// Step 2:
foreach ($oElo as $id => $e) {
$gRD_t = 1 / (\sqrt(1 + 3 * self::Q * self::Q * $oRd[$id] * $oRd[$id] / (\M_PI * \M_PI)));
$gRD[] = $gRD_t;
$E[] = 1 / (1 + \pow(10, $gRD_t * ($elo - $e) / -400));
$gRD[$id] = $gRD_t;
$E[$id] = 1 / (1 + \pow(10, $gRD_t * ($elo - $e) / -400));
}
$d = 0;

View File

@ -123,13 +123,13 @@ final class Glicko2
// Step 1:
$g = [];
foreach ($oRd as $rd) {
$g[] = 1 / \sqrt(1 + 3 * $rd * $rd / (\M_PI * \M_PI));
foreach ($oRd as $idx => $rd) {
$g[$idx] = 1 / \sqrt(1 + 3 * $rd * $rd / (\M_PI * \M_PI));
}
$E = [];
foreach ($oElo as $idx => $elo) {
$E[] = 1 / (1 + \exp(-$g[$idx] * ($elo - $elo)));
$E[$idx] = 1 / (1 + \exp(-$g[$idx] * ($elo - $elo)));
}
$v = 0;

View File

@ -51,7 +51,7 @@ final class CreditSafe implements CreditRatingInterface
$response = Rest::request($request);
return $response->header->status === 200
? ($response->get('token') ?? '')
? ($response->getDataString('token') ?? '')
: '';
}
@ -212,7 +212,7 @@ final class CreditSafe implements CreditRatingInterface
$response = Rest::request($request);
return $response->get('orderID') ?? '';
return $response->getDataString('orderID') ?? '';
}
/**

View File

@ -20,8 +20,8 @@ namespace phpOMS\Business\Recommendation;
* @package phpOMS\Business\Recommendation
* @license OMS License 2.0
* @link https://jingga.app
* @since 1.0.0
* @see https://arxiv.org/ftp/arxiv/papers/1205/1205.2618.pdf
* @since 1.0.0
*
* @todo Implement, current implementation probably wrong
*/
@ -40,13 +40,15 @@ final class BayesianPersonalizedRanking
// num_factors determines the dimensionality of the latent factor space.
// learning_rate controls the step size for updating the latent factors during optimization.
// regularization prevents overfitting by adding a penalty for large parameter values.
public function __construct(int $numFactors, float $learningRate, float $regularization) {
public function __construct(int $numFactors, float $learningRate, float $regularization)
{
$this->numFactors = $numFactors;
$this->learningRate = $learningRate;
$this->regularization = $regularization;
}
private function generateRandomFactors() {
private function generateRandomFactors()
{
$factors = [];
for ($i = 0; $i < $this->numFactors; ++$i) {
$factors[$i] = \mt_rand() / \mt_getrandmax();
@ -67,7 +69,8 @@ final class BayesianPersonalizedRanking
return $score;
}
public function updateFactors($userId, $posItemId, $negItemId) : void {
public function updateFactors($userId, $posItemId, $negItemId) : void
{
if (!isset($this->userFactors[$userId])) {
$this->userFactors[$userId] = $this->generateRandomFactors();
}

View File

@ -35,8 +35,8 @@ final class JWT
/**
* Create JWT signature part
*
* @param string $secret Secret (at least 256 bit)
* @param array{alg:string, typ:string} $header Header
* @param string $secret Secret (at least 256 bit)
* @param array{alg:string, typ:string} $header Header
* @param array{sub:string, ?uid:string, ?name:string, iat:string} $payload Payload
*
* @return string hmac(Header64 . Payload64, secret)
@ -60,15 +60,15 @@ final class JWT
/**
* Create JWT token
*
* @param string $secret Secret (at least 256 bit)
* @param array{alg:string, typ:string} $header Header
* @param string $secret Secret (at least 256 bit)
* @param array{alg:string, typ:string} $header Header
* @param array{sub:string, ?uid:string, ?name:string, iat:string} $payload Payload
*
* @return string Header64 . Payload64 . hmac(Header64 . Payload64, secret)
*
* @since 1.0.0
*/
public static function createJWT(string $secret, array $header = [], array $payload = []) : string
public static function createJWT(string $secret, array $header, array $payload) : string
{
$header64 = Base64Url::encode(\json_encode($header));
$payload64 = Base64Url::encode(\json_encode($payload));
@ -94,11 +94,7 @@ final class JWT
return [];
}
try {
return \json_decode(Base64Url::decode($explode[0]), true);
} catch (\Throwable $_) {
return [];
}
return \json_decode(Base64Url::decode($explode[0]), true);
}
/**
@ -118,11 +114,7 @@ final class JWT
return [];
}
try {
return \json_decode(Base64Url::decode($explode[1]), true);
} catch (\Throwable $_) {
return [];
}
return \json_decode(Base64Url::decode($explode[1]), true);
}
/**

View File

@ -171,11 +171,15 @@ final class EventManager implements \Countable
*/
public function triggerSimilar(string $group, string $id = '', mixed $data = null) : bool
{
if (empty($this->callbacks)) {
return false;
}
$groupIsRegex = \stripos($group, '/') === 0;
$idIsRegex = \stripos($id, '/') === 0;
$groups = [];
foreach ($this->groups as $groupName => $value) {
foreach ($this->groups as $groupName => $_) {
$groupNameIsRegex = \stripos($groupName, '/') === 0;
if ($groupIsRegex) {
@ -189,8 +193,8 @@ final class EventManager implements \Countable
}
}
foreach ($groups as $groupName => $groupValues) {
foreach ($this->groups[$groupName] as $idName => $value) {
foreach ($groups as $groupName => $_) {
foreach ($this->groups[$groupName] as $idName => $_2) {
$idNameIsRegex = \stripos($idName, '/') === 0;
if ($idIsRegex) {

View File

@ -35,6 +35,7 @@ trait ISO3166Trait
*/
public static function getBy2Code(string $code) : mixed
{
/** @var string $code3 */
$code3 = ISO3166TwoEnum::getName($code);
return self::getByName($code3);

View File

@ -99,7 +99,7 @@ final class Vector extends Matrix
*/
public function cosine(self $v) : float
{
$dotProduct = 0;
$dotProduct = 0.0;
for ($i = 0; $i < $this->m; ++$i) {
$dotProduct += $this->matrix[$i][0] * $v[$i];
}
@ -110,7 +110,7 @@ final class Vector extends Matrix
}
$magnitude1 = \sqrt($sumOfSquares);
$sumOfSquares = 0;
$sumOfSquares = 0.0;
foreach ($v->matrix as $value) {
$sumOfSquares += $value[0] * $value[0];
}

View File

@ -107,12 +107,12 @@ final class MetricsND
*/
public static function cosine(array $a, array $b) : float
{
if (\count($a) !== \count($b)) {
if (($length = \count($a)) !== \count($b)) {
throw new InvalidDimensionException(\count($a) . 'x' . \count($b));
}
$dotProduct = 0;
for ($i = 0; $i < \count($a); ++$i) {
for ($i = 0; $i < $length; ++$i) {
$dotProduct += $a[$i] * $b[$i];
}
@ -128,7 +128,7 @@ final class MetricsND
}
$magnitude2 = \sqrt($sumOfSquares);
if ($magnitude1 === 0 || $magnitude2 === 0) {
if ($magnitude1 == 0 || $magnitude2 == 0) {
return \PHP_FLOAT_MAX;
}

View File

@ -45,15 +45,232 @@ abstract class ResponseAbstract implements \JsonSerializable, MessageInterface
/**
* Get response by ID.
*
* @param mixed $id Response ID
* @param mixed $key Response ID
*
* @return mixed
*
* @since 1.0.0
*/
public function get(mixed $id) : mixed
public function get(mixed $key, string $type = null) : mixed
{
return $this->data[$id] ?? null;
if ($key === null) {
return $this->data;
}
$key = \is_string($key) ? \mb_strtolower($key) : $key;
if (!isset($this->data[$key])) {
return null;
}
switch ($type) {
case null:
return $this->data[$key];
case 'int':
return (int) $this->data[$key];
case 'string':
return (string) $this->data[$key];
case 'float':
return (float) $this->data[$key];
case 'bool':
return (bool) $this->data[$key];
case 'DateTime':
return new \DateTime((string) $this->data[$key]);
default:
return $this->data[$key];
}
}
/**
* Get data.
*
* @param string $key Data key
*
* @return null|string
*
* @since 1.0.0
*/
public function getDataString(string $key) : ?string
{
$key = \mb_strtolower($key);
if (($this->data[$key] ?? '') === '') {
return null;
}
return (string) $this->data[$key];
}
/**
* Get data.
*
* @param string $key Data key
*
* @return null|int
*
* @since 1.0.0
*/
public function getDataInt(string $key) : ?int
{
$key = \mb_strtolower($key);
if (($this->data[$key] ?? '') === '') {
return null;
}
return (int) $this->data[$key];
}
/**
* Get data.
*
* @param string $key Data key
*
* @return null|float
*
* @since 1.0.0
*/
public function getDataFloat(string $key) : ?float
{
$key = \mb_strtolower($key);
if (($this->data[$key] ?? '') === '') {
return null;
}
return (float) $this->data[$key];
}
/**
* Get data.
*
* @param string $key Data key
*
* @return null|bool
*
* @since 1.0.0
*/
public function getDataBool(string $key) : ?bool
{
$key = \mb_strtolower($key);
if (($this->data[$key] ?? '') === '') {
return null;
}
return (bool) $this->data[$key];
}
/**
* Get data.
*
* @param string $key Data key
*
* @return null|\DateTime
*
* @since 1.0.0
*/
public function getDataDateTime(string $key) : ?\DateTime
{
$key = \mb_strtolower($key);
return empty($this->data[$key] ?? null)
? null
: new \DateTime((string) $this->data[$key]);
}
/**
* Get data.
*
* @param string $key Data key
*
* @return array
*
* @since 1.0.0
*/
public function getDataJson(string $key) : array
{
$key = \mb_strtolower($key);
if (($this->data[$key] ?? '') === '') {
return [];
}
$json = \json_decode($this->data[$key], true); /** @phpstan-ignore-line */
return \is_array($json) ? $json : [];
}
/**
* Get data.
*
* @param string $key Data key
* @param string $delim Data delimiter
*
* @return array
*
* @since 1.0.0
*/
public function getDataList(string $key, string $delim = ',') : array
{
$key = \mb_strtolower($key);
if (($this->data[$key] ?? '') === '') {
return [];
}
/* @phpstan-ignore-next-line */
$list = \explode($delim, $this->data[$key]);
if ($list === false) {
return []; // @codeCoverageIgnore
}
foreach ($list as $i => $e) {
$list[$i] = \trim($e);
}
return $list;
}
/**
* Get data based on wildcard.
*
* @param string $regex Regex data key
*
* @return array
*
* @since 1.0.0
*/
public function getLike(string $regex) : array
{
$data = [];
foreach ($this->data as $key => $value) {
if (\preg_match('/' . $regex . '/', (string) $key) === 1) {
$data[$key] = $value;
}
}
return $data;
}
/**
* Check if has data.
*
* The following empty values are considered as not set (null, '', 0)
*
* @param string $key Data key
*
* @return bool
*
* @since 1.0.0
*/
public function hasData(string $key) : bool
{
$key = \mb_strtolower($key);
return isset($this->data[$key])
&& $this->data[$key] !== ''
&& $this->data[$key] !== null;
}
/**

View File

@ -697,6 +697,8 @@ abstract class ModuleAbstract
*
* @return void
*
* @todo find a way to offload this to the cli in a different process (same for other similar functions)
*
* @since 1.0.0
*/
protected function createModel(int $account, mixed $obj, string | \Closure $mapper, string $trigger, string $ip) : void
@ -712,17 +714,18 @@ abstract class ModuleAbstract
$mapper();
}
$this->app->eventManager->triggerSimilar('POST:Module:' . $trigger, '',
[
$account,
null, $obj,
StringUtils::intHash(\is_string($mapper) ? $mapper : \get_class($mapper)), $trigger,
static::NAME,
(string) $id,
null,
$ip,
]
);
$data = [
$account,
null, $obj,
StringUtils::intHash(\is_string($mapper) ? $mapper : \get_class($mapper)), $trigger,
static::NAME,
(string) $id,
null,
$ip,
];
$this->app->moduleManager->get('Auditor', 'Api')->eventLogCreate(...$data);
$this->app->eventManager->triggerSimilar('POST:Module:' . $trigger, '', $data);
}
/**
@ -756,17 +759,18 @@ abstract class ModuleAbstract
$mapper();
}
$this->app->eventManager->triggerSimilar('POST:Module:' . $trigger, '',
[
$account,
null, $obj,
StringUtils::intHash(\is_string($mapper) ? $mapper : \get_class($mapper)), $trigger,
static::NAME,
(string) $id,
null,
$ip,
]
);
$data = [
$account,
null, $obj,
StringUtils::intHash(\is_string($mapper) ? $mapper : \get_class($mapper)), $trigger,
static::NAME,
(string) $id,
null,
$ip,
];
$this->app->moduleManager->get('Auditor', 'Api')->eventLogCreate(...$data);
$this->app->eventManager->triggerSimilar('POST:Module:' . $trigger, '', $data);
}
}
@ -801,17 +805,18 @@ abstract class ModuleAbstract
$mapper();
}
$this->app->eventManager->triggerSimilar('POST:Module:' . $trigger, '',
[
$account,
$old, $new,
StringUtils::intHash(\is_string($mapper) ? $mapper : \get_class($mapper)), $trigger,
static::NAME,
(string) $id,
null,
$ip,
]
);
$data = [
$account,
$old, $new,
StringUtils::intHash(\is_string($mapper) ? $mapper : \get_class($mapper)), $trigger,
static::NAME,
(string) $id,
null,
$ip,
];
$this->app->moduleManager->get('Auditor', 'Api')->eventLogUpdate(...$data);
$this->app->eventManager->triggerSimilar('POST:Module:' . $trigger, '', $data);
}
/**
@ -844,17 +849,18 @@ abstract class ModuleAbstract
$mapper();
}
$this->app->eventManager->triggerSimilar('POST:Module:' . $trigger, '',
[
$account,
$obj, null,
StringUtils::intHash(\is_string($mapper) ? $mapper : \get_class($mapper)), $trigger,
static::NAME,
(string) $id,
null,
$ip,
]
);
$data = [
$account,
$obj, null,
StringUtils::intHash(\is_string($mapper) ? $mapper : \get_class($mapper)), $trigger,
static::NAME,
(string) $id,
null,
$ip,
];
$this->app->moduleManager->get('Auditor', 'Api')->eventLogDelete(...$data);
$this->app->eventManager->triggerSimilar('POST:Module:' . $trigger, '', $data);
}
/**
@ -890,17 +896,19 @@ abstract class ModuleAbstract
$this->app->eventManager->triggerSimilar('PRE:Module:' . $trigger, '', $rel1);
$mapper::writer()->createRelationTable($field, \is_array($rel2) ? $rel2 : [$rel2], $rel1);
$this->app->eventManager->triggerSimilar('POST:Module:' . $trigger, '',
[
$account,
'', [$rel1 => $rel2],
StringUtils::intHash($mapper), $trigger,
static::NAME,
null,
null,
$ip,
]
);
$data = [
$account,
'', [$rel1 => $rel2],
StringUtils::intHash($mapper), $trigger,
static::NAME,
null,
null,
$ip,
];
$this->app->moduleManager->get('Auditor', 'Api')->eventLogRelationCreate(...$data);
$this->app->eventManager->triggerSimilar('POST:Module:' . $trigger, '', $data);
}
/**
@ -928,16 +936,18 @@ abstract class ModuleAbstract
$this->app->eventManager->triggerSimilar('PRE:Module:' . $trigger, '', $rel1);
$mapper::remover()->deleteRelationTable($field, \is_array($rel2) ? $rel2 : [$rel2], $rel1);
$this->app->eventManager->triggerSimilar('POST:Module:' . $trigger, '',
[
$account,
[$rel1 => $rel2], '',
StringUtils::intHash($mapper), $trigger,
static::NAME,
null,
null,
$ip,
]
);
$data = [
$account,
[$rel1 => $rel2], '',
StringUtils::intHash($mapper), $trigger,
static::NAME,
null,
null,
$ip,
];
$this->app->moduleManager->get('Auditor', 'Api')->eventLogRelationDelete(...$data);
$this->app->eventManager->triggerSimilar('POST:Module:' . $trigger, '', $data);
}
}

View File

@ -293,8 +293,8 @@ final class FileUtils
public static function makeSafeFileName(string $name) : string
{
$name = \preg_replace("/[^A-Za-z0-9\-_.]/", '_', $name);
$name = \preg_replace("/_+/", '_', $name);
$name = \trim($name, '_');
$name = \preg_replace("/_+/", '_', $name ?? '');
$name = \trim($name ?? '', '_');
$name = \strtolower($name);
return $name;

View File

@ -319,7 +319,9 @@ final class ArrayUtils
return null;
}
return \trim($args[(int) $key + 1], '" ');
$value = $args[(int) $key + 1];
return \is_string($value) ? \trim($value, '" ') : $value;
}
/**

View File

@ -42,6 +42,8 @@ class HtmlFormatter
$dom->preserveWhiteSpace = false;
$dom->formatOutput = true;
return $dom->saveXML($dom->documentElement);
$formatted = $dom->saveXML($dom->documentElement);
return $formatted === false ? '' : $formatted;
}
}

View File

@ -312,13 +312,17 @@ final class ImageUtils
$color1Avg = self::getAverageColor($src1, $i, $j, $imageDim2[0], $imageDim2[1], $diffArea);
$color2Avg = self::getAverageColor($src2, $i, $j, $newDim[0], $newDim[1], $diffArea);
$color1 = \imagecolorat($src1, $i, $j);
//$color1 = \imagecolorat($src1, $i, $j);
$color2 = \imagecolorat($src2, $i, $j);
if (\abs($color1Avg - $color2Avg) / $color1Avg > 0.05 && $color1Avg > 0 && $color2Avg > 0) {
++$difference;
if ($diff === 0) {
if ($color2 === false) {
continue;
}
/** @var \GdImage $dst */
\imagesetpixel($dst, $i, $j, $color2);
} elseif ($diff === 1) {