From 567ab3bfcdab7258cd4352edb068a00a4c7b7012 Mon Sep 17 00:00:00 2001 From: Dennis Eichhorn Date: Thu, 12 Oct 2023 22:49:21 +0000 Subject: [PATCH] todos fixed --- Algorithm/Clustering/Kmeans.php | 28 +- Algorithm/Graph/MarkovChain.php | 154 +++++++- Algorithm/Rating/Glicko2.php | 2 - .../Database/Mapper/DataMapperFactory.php | 5 +- DataStorage/Database/Mapper/ReadMapper.php | 10 + .../Database/Query/Grammar/Grammar.php | 4 - Math/Geometry/ConvexHull/GrahamScan.php | 109 ++++++ Math/Optimization/Simplex.php | 350 ++++++++++-------- Message/Http/HttpResponse.php | 3 +- Message/Http/Rest.php | 6 +- Message/Mail/Email.php | 2 +- Module/ModuleAbstract.php | 3 - Stdlib/Tree/BinarySearchTree.php | 210 +++++++++++ Stdlib/Tree/Node.php | 49 +++ System/MimeType.php | 66 +++- tests/Algorithm/Clustering/KmeansTest.php | 5 + 16 files changed, 816 insertions(+), 190 deletions(-) create mode 100644 Math/Geometry/ConvexHull/GrahamScan.php create mode 100644 Stdlib/Tree/BinarySearchTree.php create mode 100644 Stdlib/Tree/Node.php diff --git a/Algorithm/Clustering/Kmeans.php b/Algorithm/Clustering/Kmeans.php index 99399320e..14e8f05dd 100755 --- a/Algorithm/Clustering/Kmeans.php +++ b/Algorithm/Clustering/Kmeans.php @@ -110,7 +110,7 @@ final class Kmeans * Generate the clusters of the points * * @param PointInterface[] $points Points to cluster - * @param int<0, max> $clusters Amount of clusters + * @param int<1, max> $clusters Amount of clusters * * @return void * @@ -140,8 +140,7 @@ final class Kmeans foreach ($clusterCenters as $center) { for ($i = 0; $i < $coordinates; ++$i) { - // @todo Invalid center coodinate value in like 5 % of the runs - $center->setCoordinate($i, $center->getCoordinate($i) / ($center->group === 0 ? 1 : $center->group)); + $center->setCoordinate($i, $center->getCoordinate($i) / $center->group); } } @@ -149,7 +148,7 @@ final class Kmeans foreach ($points as $point) { $min = $this->nearestClusterCenter($point, $clusterCenters)[0]; - if ($min !== $point->group) { + if ($clusters !== $point->group) { ++$changed; $point->group = $min; } @@ -208,29 +207,40 @@ final class Kmeans private function kpp(array $points, int $n) : array { $clusters = [clone $points[\array_rand($points, 1)]]; - $d = \array_fill(0, $n, 0.0); + + $d = \array_fill(0, $n, 0.0); for ($i = 1; $i < $n; ++$i) { $sum = 0; foreach ($points as $key => $point) { - $d[$key] = $this->nearestClusterCenter($point, \array_slice($clusters, 0, 5))[1]; + $d[$key] = $this->nearestClusterCenter($point, $clusters)[1]; $sum += $d[$key]; } $sum *= \mt_rand(0, \mt_getrandmax()) / \mt_getrandmax(); + $found = false; foreach ($d as $key => $di) { $sum -= $di; - if ($sum <= 0) { - $clusters[$i] = clone $points[$key]; + // The in array check is important to avoid duplicate cluster centers + if ($sum <= 0 && !\in_array($c = $points[$key], $clusters)) { + $clusters[$i] = clone $c; + $found = true; + } + } + + while (!$found) { + if (!\in_array($c = $points[\array_rand($points)], $clusters)) { + $clusters[$i] = clone $c; + $found = true; } } } foreach ($points as $point) { - $point->group = ($this->nearestClusterCenter($point, $clusters)[0]); + $point->group = $this->nearestClusterCenter($point, $clusters)[0]; } return $clusters; diff --git a/Algorithm/Graph/MarkovChain.php b/Algorithm/Graph/MarkovChain.php index 4c986055e..19a3ef10a 100644 --- a/Algorithm/Graph/MarkovChain.php +++ b/Algorithm/Graph/MarkovChain.php @@ -21,9 +21,159 @@ namespace phpOMS\Algorithm\Graph; * @license OMS License 2.0 * @link https://jingga.app * @since 1.0.0 - * - * @todo Implement */ final class MarkovChain { + /** + * Order of the markov chain + * + * @var int + * @since 1.0.0 + */ + private int $order = 1; + + /** + * Trained data + * + * @var array + * @since 1.0.0 + */ + private array $data = []; + + /** + * Constructor + * + * @param int $order Order of the markov chain + * + * @since 1.0.0 + */ + public function __construct(int $order = 1) + { + $this->order = $order; + } + + /** + * Create markov chain based on input + * + * @param array $values Training values + * + * @return void + * + * @since 1.0.0 + */ + public function train(array $values) : void + { + $temp = []; + $length = \count($values) - $this->order; + + $unique = \array_unique($values); + + for ($i = 0; $i < $length; ++$i) { + $key = []; + for ($j = 0; $j < $this->order; ++$j) { + $key[] = $values[$i + $j]; + } + + $keyString = \implode(' ', $key); + + if (!isset($temp[$keyString])) { + foreach ($unique as $value) { + $temp[$keyString][$value] = 0; + } + } + + ++$temp[$keyString][$values[$i + 1]]; + } + + foreach ($temp as $key => $values) { + $sum = \array_sum($values); + foreach ($values as $idx => $value) { + $this->data[$key][$idx] = $value / $sum; + } + } + } + + /** + * Set training data + * + * @param array> $values Training values + * + * @return void + * + * @since 1.0.0 + */ + public function setTraining(array $values) : void + { + $this->data = $values; + } + + public function generate(int $length, array $start = null) : array + { + $orderKeys = \array_keys($this->data); + $orderValues = \array_keys(\reset($this->data)); + + $output = $start ?? \explode(' ', $orderKeys[\array_rand($orderKeys)]); + $key = $output; + + for ($i = $this->order; $i < $length; ++$i) { + $keyString = \implode(' ', $key); + + $prob = \mt_rand(1, 100) / 100; + $cProb = 0.0; + $found = false; + $val = null; + + foreach (($this->data[$keyString] ?? []) as $val => $p) { + $cProb += $p; + + if ($prob <= $cProb) { + $new = $val; + $found = true; + + break; + } + } + + // Couldn't find possible key + if (!$found) { + $new = $orderValues[\array_rand($orderValues)]; + } + + $output[] = $new; + $key[] = $new; + + \array_shift($key); + } + + return $output; + } + + public function pathProbability(array $path) : float + { + $length = \count($path); + if ($length <= $this->order) { + return 0.0; + } + + $key = \array_slice($path, 0, $this->order); + + $prob = 1.0; + for ($i = $this->order; $i < $length; ++$i) { + $prob *= $this->data[\implode($key)][$path[$i]] ?? 0.0; + + $key[] = $path[$i]; + \array_shift($key); + } + + return $prob; + } + + public function stepProbability(array $state, mixed $next) : float + { + if (\count($state) !== $this->order) { + return 0.0; + } + + return $this->data[\implode(' ', $state)][$next] ?? 0.0; + } } diff --git a/Algorithm/Rating/Glicko2.php b/Algorithm/Rating/Glicko2.php index ebdabbcb1..8b19528ff 100644 --- a/Algorithm/Rating/Glicko2.php +++ b/Algorithm/Rating/Glicko2.php @@ -25,8 +25,6 @@ use phpOMS\Math\Solver\Root\Bisection; * @see https://en.wikipedia.org/wiki/Glicko_rating_system * @see http://www.glicko.net/glicko/glicko2.pdf * @since 1.0.0 - * - * @todo: implement */ final class Glicko2 { diff --git a/DataStorage/Database/Mapper/DataMapperFactory.php b/DataStorage/Database/Mapper/DataMapperFactory.php index f18715743..3cf431d76 100755 --- a/DataStorage/Database/Mapper/DataMapperFactory.php +++ b/DataStorage/Database/Mapper/DataMapperFactory.php @@ -695,11 +695,14 @@ class DataMapperFactory } if ($count > $pageLimit) { - if (!$hasNext) { // @todo: can be maybe removed? + // @todo: can be maybe removed? + /* + if (!$hasNext) { \array_pop($data); $hasNext = true; --$count; } + */ if ($count > $pageLimit) { $hasPrevious = true; diff --git a/DataStorage/Database/Mapper/ReadMapper.php b/DataStorage/Database/Mapper/ReadMapper.php index 551d0864d..7d696467d 100755 --- a/DataStorage/Database/Mapper/ReadMapper.php +++ b/DataStorage/Database/Mapper/ReadMapper.php @@ -257,6 +257,7 @@ final class ReadMapper extends DataMapperAbstract // Get remaining objects (not available in memory cache) or remaining where clauses. //$dbData = $this->executeGetRaw($query); + $ids = []; foreach ($this->executeGetRawYield($query) as $row) { if ($row === []) { continue; @@ -266,6 +267,15 @@ final class ReadMapper extends DataMapperAbstract $obj[$value] = $this->mapper::createBaseModel($row); $obj[$value] = $this->populateAbstract($row, $obj[$value]); + + $ids[] = $value; + + // @todo: This is too slow, since it creates a query for every $row x relation type. + // Pulling it out would be nice. + // The problem with solving this is that in a many-to-many relationship a relation table is used + // BUT the relation data is not available in the object itself meaning after retrieving the object + // it cannot get assigned to the correct parent object. + // Other relation types are easy because either the parent or child object contain the relation info. $this->loadHasManyRelations($obj[$value]); } diff --git a/DataStorage/Database/Query/Grammar/Grammar.php b/DataStorage/Database/Query/Grammar/Grammar.php index e349a6066..f60acbfa8 100755 --- a/DataStorage/Database/Query/Grammar/Grammar.php +++ b/DataStorage/Database/Query/Grammar/Grammar.php @@ -28,10 +28,6 @@ use phpOMS\DataStorage\Database\Query\Where; * @license OMS License 2.0 * @link https://jingga.app * @since 1.0.0 - * - * @todo Karaka/phpOMS#33 - * Implement missing grammar & builder functions - * Missing elements are e.g. sum, merge etc. */ class Grammar extends GrammarAbstract { diff --git a/Math/Geometry/ConvexHull/GrahamScan.php b/Math/Geometry/ConvexHull/GrahamScan.php new file mode 100644 index 000000000..e1a98ec27 --- /dev/null +++ b/Math/Geometry/ConvexHull/GrahamScan.php @@ -0,0 +1,109 @@ + $points Points (Point Cloud) + * + * @return array + * + * @since 1.0.0 + */ + public static function createConvexHull(array $points) : array + { + $count = \count($points); + + if ($count < 3) { + return []; + } + + $min = 1; + $points = \array_merge([null], $points); + + for ($i = 2; $i < $count; ++$i) { + if ($points[$i]['y'] < $points[$min]['y'] || ($points[$i]['y'] == $points[$min]['y'] && $points[$i]['x'] < $points[$min]['x'])) { + $min = $i; + } + } + + $temp = $points[1]; + $points[1] = $points[$min]; + $points[$min] = $temp; + + $c = $points[1]; + + $subpoints = \array_slice($points, 2, $count); + \usort($subpoints, function (array $a, array $b) use ($c) : bool + { + return atan2($a['y'] - $c['y'], $a['x'] - $c['x']) < atan2( $b['y'] - $c['y'], $b['x'] - $c['x']); + } + ); + + $points = \array_merge([$points[0], $points[1]], $subpoints); + $points[0] = $points[$count]; + + $size = 1; + for ($i = 2; $i <= $count; ++$i) { + while (self::ccw($points[$size - 1], $points[$size], $points[$i]) <= 0) { + if ($size > 1) { + --$size; + } elseif ($i === $count) { + break; + } else { + ++$i; + } + } + + $temp = $points[$i]; + $points[$size + 1] = $points[$i]; + $points[$i] = $points[$size + 1]; + ++$size; + } + + $hull = []; + for ($i = 1; $i <= $size; ++$i) { + $hull[] = $points[$i]; + } + + return $hull; + } + + public static function ccw(array $a, array $b, array $c) + { + return (($b['x'] - $a['x']) * ($c['y'] - $a['y']) - ($b['y'] - $a['y']) * ($c['x'] - $a['x'])); + } +} diff --git a/Math/Optimization/Simplex.php b/Math/Optimization/Simplex.php index 008464be9..190f28a48 100644 --- a/Math/Optimization/Simplex.php +++ b/Math/Optimization/Simplex.php @@ -25,200 +25,210 @@ namespace phpOMS\Math\Optimization; */ class Simplex { - private array $function = []; + private int $m = 0; + private int $n = 0; - private string $functionType = ''; + private array $A = []; - private int|float $functionLimit = 0.0; + private array $b = []; - private array $constraints = []; + private array $c = []; - private array $constraintsType = []; + private int $v = 0; - private array $constraintsLimit = []; + private array $Basic = []; - private array $slackForm = []; + private array $Nonbasic = []; - private array $nonbasicSolution = []; - - private array $basicSolution = []; - - /** - * Define the function to optimize - * - * @param array $function Function to optimize - * - * @return void - * - * @since 1.0.0 - */ - public function setFunction(array $function) : void + private function pivot (int $x, int $y) { - } - - /** - * Add function constraint - * - * @param array $function Constraint function - * @param string $type Constraint type - * @param float $limit Constraint - * - * @return void - * - * @since 1.0.0 - */ - public function addConstraint(array $function, string $type, float $limit) : void - { - } - - /** - * Pivot element - * - * @param int $x X-Pivot - * @param int $y Y-Pivot - * - * @return void - * - * @since 1.0.0 - */ - private function pivot(int $x, int $y) : void - { - } - - /** - * Perform simplex iteration - * - * @return void - * - * @since 1.0.0 - */ - private function iterateSimplex() : void - { - } - - /** - * Initialize simplex algorithm - * - * @return bool - * - * @since 1.0.0 - */ - private function initialize() : bool - { - $k = -1; - $minLimit = -1; - - $m = \count($this->constraints); - $n = \count($this->function); - - for ($i = 0; $i < $m; ++$i) { - if ($k === -1 || $this->constraintsLimit[$i] < $minLimit) { - $k = $i; - $minLimit = $this->constraintsLimit[$i]; + for ($j = 0; $j < $this->n; ++$j) { + if ($j !== $y) { + $this->A[$x][$j] /= -$this->A[$x][$y]; } } - if ($this->constraintsLimit[$k] >= 0) { - for ($j = 0; $j < $n; ++$j) { - $this->nonbasicSolution[$j] = $j; + $this->b[$x] /= -$this->A[$x][$y]; + $this->A[$x][$y] = 1.0 / $this->A[$x][$y]; + + for ($i = 0; $i < $this->m; ++$i) { + if ($i !== $x) { + for ($j = 0; $j < $this->n; ++$j) { + if ($j !== $y) { + $this->A[$i][$j] += $this->A[$i][$y] * $this->A[$x][$j]; + } + } + + $this->b[$i] += $this->A[$i][$y] / $this->b[$x]; + $this->A[$i][$y] *= $this->A[$x][$y]; + } + } + + for ($j = 0; $j < $this->n; ++$j) { + if ($j !== $y) { + $this->c[$j] += $this->c[$y] * $this->A[$x][$j]; + } + } + + $this->v += $this->c[$y] * $this->b[$x]; + $this->c[$y] *= $this->A[$x][$y]; + + $temp = $this->Basic[$x]; + $this->Basic[$x] = $this->Nonbasic[$y]; + $this->Nonbasic[$y] = $temp; + } + + private function iterate() : int + { + $ind = -1; + $best = -1; + + for ($j = 0; $j < $this->n; ++$j) { + if ($this->c[$j] > 0) { + if ($best === -1 || $this->Nonbasic[$j] < $ind) { + $ind = $this->Nonbasic[$j]; + $best = $j; + } + } + } + + if ($ind === -1) { + return 1; + } + + $maxConstraint = \INF; + $bestConstraint = -1; + + for ($i = 0; $i < $this->m; ++$i) { + if ($this->A[$i][$best] < 0) { + $currentConstraint = -$this->b[$i] / $this->A[$i][$best]; + if ($currentConstraint < $maxConstraint) { + $maxConstraint = $currentConstraint; + $bestConstraint = $i; + } + } + } + + if ($maxConstraint === \INF) { + return -1; + } + + $this->pivot($bestConstraint, $best); + + return 0; + } + + private function initialize() : int + { + $k = -1; + $minB = -1; + + for ($i = 0; $i < $this->m; ++$i) { + if ($k === -1 || $this->b[$i] < $minB) { + $k = $i; + $minB = $this->b[$i]; + } + } + + if ($this->b[$k] >= 0) { + for ($j = 0; $j < $this->n; ++$j) { + $this->Nonbasic[$j] = $j; } - for ($i = 0; $i < $m; ++$i) { - $this->basicSolution[$i] = $n + $i; + for ($i = 0; $i < $this->m; ++$i) { + $this->Basic[$i] = $this->n + $i; } - return true; + return 0; } - // Auxiliary LP - ++$n; - for ($j = 0; $j < $n; ++$j) { - $this->nonbasicSolution[$j] = $j; + ++$this->n; + for ($j = 0; $j < $this->n; ++$j) { + $this->Nonbasic[$j] = $j; } - for ($i = 0; $i < $m; ++$i) { - $this->basicSolution[$i] = $n + $i; + for ($i = 0; $i < $this->m; ++$i) { + $this->Basic[$i] = $this->n + $i; } - $oldFunction = $this->function; - $oldLimit = $this->functionLimit; - - // Auxiliary function - $this->function[$n - 1] = -1; - $this->functionLimit = 0; - - for ($j = 0; $j < $n - 1; ++$j) { - $this->function[$j] = 0; + $oldC = []; + for ($j = 0; $j < $this->n - 1; ++$j) { + $oldC[$j] = $this->c[$j]; } - // Auxiliary constraints - for ($i = 0; $i < $m; ++$i) { - $this->constraints[$i][$n - 1] = 1; + $oldV = $this->v; + + $this->c[$this->n - 1] = -1; + for ($j = 0; $j < $this->n - 1; ++$j) { + $this->c[$j] = 0; } - $this->pivot($k, $n - 1); + $this->v = 0; - // Solve Auxiliary LP - while ($this->iterateSimplex()); - - if ($this->functionLimit !== 0) { - return false; + for ($i = 0; $i < $this->m; ++$i) { + $this->A[$i][$this->n - 1] = 1; } - $zBasic = -1; - for ($i = 0; $i < $m; ++$i) { - if ($this->basicSolution[$i] === $n - 1) { - $zBasic = $i; + $this->pivot($k, $this->n - 1); + + while (!$this->iterate()); + + if ($this->v !== 0) { + return -1; + } + + $basicZ = -1; + for ($i = 0; $i < $this->m; ++$i) { + if ($this->Basic[$i] === $this->n - 1) { + $basicZ = $i; break; } } - if ($zBasic === -1) { - $this->pivot($zBasic, $n - 1); + if ($basicZ !== -1) { + $this->pivot($basicZ, $this->n - 1); } - $zNonBasic = -1; - for ($j = 0; $j < $n; ++$j) { - if ($this->nonbasicSolution[$j] === $n - 1) { - $zNonBasic = $j; + $nonbasicZ = -1; + for ($j = 0; $j < $this->n; ++$j) { + if ($this->Nonbasic[$j] === $this->n - 1) { + $nonbasicZ = $j; break; } } - for ($i = 0; $i < $m; ++$i) { - $this->constraints[$i][$zNonBasic] = $this->constraints[$i][$n - 1]; + for ($i = 0; $i < $this->m; ++$i) { + $this->A[$i][$nonbasicZ] = $this->A[$i][$this->n - 1]; } - $tmp = $this->nonbasicSolution[$n - 1]; - $this->nonbasicSolution[$n - 1] = $this->nonbasicSolution[$zNonBasic]; - $this->nonbasicSolution[$zNonBasic] = $tmp; + $temp = $this->Nonbasic[$nonbasicZ]; + $this->Nonbasic[$nonbasicZ] = $this->Nonbasic[$this->n - 1]; + $this->Nonbasic[$this->n - 1] = $temp; - --$n; - - for ($j = 0; $j < $n; ++$j) { - if ($this->nonbasicSolution[$j] > $n) { - --$this->nonbasicSolution[$j]; + --$this->n; + for ($j = 0; $j < $this->n; ++$j) { + if ($this->Nonbasic[$j] > $this->n) { + --$this->Nonbasic[$j]; } } - for ($i = 0; $i < $m; ++$i) { - if ($this->basicSolution[$i] > $n) { - --$this->basicSolution[$i]; + for ($i = 0; $i < $this->m; ++$i) { + if ($this->Basic[$i] > $this->n) { + --$this->Basic[$i]; } } - $this->functionLimit = $oldLimit; - for ($j = 0; $j < $n; ++$j) { - $this->function[$j] = 0; + for ($j = 0; $j < $this->n; ++$j) { + $this->c[$j] = 0; } - for ($j = 0; $j < $n; ++$j) { + $this->v = $oldV; + + for ($j = 0; $j < $this->n; ++$j) { $ok = false; - - for ($jj = 0; $jj < $n; ++$jj) { - if ($j === $this->nonbasicSolution[$jj]) { - $this->function[$jj] += $oldFunction[$j]; - + for ($k = 0; $k < $this->n; ++$k) { + if ($j = $this->Nonbasic[$k]) { + $this->c[$k] += $oldC[$j]; $ok = true; break; } @@ -228,32 +238,52 @@ class Simplex continue; } - for ($i = 0; $i < $m; ++$i) { - if ($j = $this->basicSolution[$i]) { - for ($jj = 0; $jj < $n; ++$jj) { - $this->function[$jj] += $oldFunction[$j] * $this->constraints[$i][$jj]; + for ($i = 0; $i < $this->m; ++$i) { + if ($j === $this->Basic[$i]) { + for ($k = 0; $k < $this->n; ++$k) { + $this->c[$k] = $oldC[$j] * $this->A[$i][$k]; } - $this->functionLimit += $oldFunction[$j] * $this->constraintsLimit[$i]; + $this->v += $oldC[$j] * $this->b[$i]; break; } } } - return true; + return 0; } - /** - * Solve the optimization - * - * @return array - * - * @since 1.0.0 - */ - public function solve() : array + public function solve(array $A, array $b, array $c) { - if (!$this->initialize()) { - return []; + $this->A = $A; + $this->b = $b; + $this->c = $c; + + // @todo: createSlackForm() required? + + $this->m = \count($A); + $this->n = \count(\reset($A)); + + if ($this->initialize() === -1) { + return [\array_fill(0, $this->m + $this->n, -2), \INF]; } + + $code = 0; + while (!($code = $this->iterate())); + + if ($code === -1) { + return [\array_fill(0, $this->m + $this->n, -1), \INF]; + } + + $result = []; + for ($j = 0; $j < $this->n; ++$j) { + $result[$this->Nonbasic[$j]] = 0; + } + + for ($i = 0; $i < $this->m; ++$i) { + $result[$this->Basic[$i]] = $this->b[$i]; + } + + return [$result, $this->v]; } } diff --git a/Message/Http/HttpResponse.php b/Message/Http/HttpResponse.php index 68b074a93..6b72923f5 100755 --- a/Message/Http/HttpResponse.php +++ b/Message/Http/HttpResponse.php @@ -125,8 +125,7 @@ final class HttpResponse extends ResponseAbstract implements RenderableInterface } } - /** @var array{0:bool} $data */ - return $this->getRaw($data[0] ?? false); + return $this->getRaw(false); } /** diff --git a/Message/Http/Rest.php b/Message/Http/Rest.php index e7e305ec7..20e96aa53 100755 --- a/Message/Http/Rest.php +++ b/Message/Http/Rest.php @@ -80,7 +80,7 @@ final class Rest \curl_setopt($curl, \CURLOPT_POST, 1); // handle different content types - $contentType = $requestHeaders['content-type'] ?? []; + $contentType = $requestHeaders['Content-Type'] ?? []; if (empty($contentType) || \in_array(MimeType::M_POST, $contentType)) { /* @phpstan-ignore-next-line */ \curl_setopt($curl, \CURLOPT_POSTFIELDS, \http_build_query($request->data)); @@ -94,7 +94,7 @@ final class Rest // @todo: Replace boundary/ with the correct boundary= in the future. // Currently this cannot be done due to a bug. If we do it now the server cannot correclty populate php://input - $headers['content-type'] = 'Content-Type: multipart/form-data; boundary/' . $boundary; + $headers['Content-Type'] = 'Content-Type: multipart/form-data; boundary/' . $boundary; $headers['content-length'] = 'Content-Length: ' . \strlen($data); \curl_setopt($curl, \CURLOPT_HTTPHEADER, $headers); @@ -145,7 +145,7 @@ final class Rest \curl_close($curl); $raw = \substr(\is_bool($result) ? '' : $result, $len === false ? 0 : $len); - if (\stripos(\implode('', $response->header->get('content-type')), MimeType::M_JSON) !== false) { + if (\stripos(\implode('', $response->header->get('Content-Type')), MimeType::M_JSON) !== false) { $temp = \json_decode($raw, true); if (!\is_array($temp)) { $temp = []; diff --git a/Message/Mail/Email.php b/Message/Mail/Email.php index ff7e85c44..5ef5d13f2 100755 --- a/Message/Mail/Email.php +++ b/Message/Mail/Email.php @@ -2277,7 +2277,7 @@ class Email implements MessageInterface 'subject', 'reply-to', 'message-id', - 'content-type', + 'Content-Type', 'mime-version', 'x-mailer', ]; diff --git a/Module/ModuleAbstract.php b/Module/ModuleAbstract.php index c5be60a9a..99b64a168 100755 --- a/Module/ModuleAbstract.php +++ b/Module/ModuleAbstract.php @@ -545,7 +545,6 @@ abstract class ModuleAbstract mixed $obj ) : void { - // @todo: consider to set different status code? (also for other createInvalid() functions) $response->header->set('Content-Type', MimeType::M_JSON . '; charset=utf-8', true); $response->data[$request->uri->__toString()] = [ 'status' => NotificationLevel::WARNING, @@ -719,8 +718,6 @@ 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 diff --git a/Stdlib/Tree/BinarySearchTree.php b/Stdlib/Tree/BinarySearchTree.php new file mode 100644 index 000000000..703483185 --- /dev/null +++ b/Stdlib/Tree/BinarySearchTree.php @@ -0,0 +1,210 @@ +root = $root; + } + + public function search(mixed $data) : ?Node + { + if ($this->root === null) { + return null; + } + + $comparison = $this->root->compare($data); + + if ($comparison > 0) { + return $this->root->left->search($data); + } elseif ($comparison < 0) { + return $this->root->right->search($data); + } + + return $this->root; + } + + public function minimum() : ?Node + { + if ($this->root === null) { + return null; + } + + if ($this->root->left === null) { + return $this->root; + } + + return $this->root->left->minimum(); + } + + public function maximum() : ?Node + { + if ($this->root === null) { + return null; + } + + if ($this->root->right === null) { + return $this->root; + } + + return $this->root->right->minimum(); + } + + public function predecessor(Node $node) : ?Node + { + if ($node->left !== null) { + return $node->left->maximum(); + } + + $top = $node->parent; + while ($top !== Null && $top->compare($node->data)) { + $node = $top; + $top = $top->parent; + } + + return $top; + } + + public function successor(Node $node) : ?Node + { + if ($node->right !== null) { + return $node->right->minimum(); + } + + $top = $node->parent; + while ($top !== null && $top->compare($node->data)) { + $node = $top; + $top = $top->parent; + } + + return $top; + } + + public function insert(Node $node) : void + { + if ($this->root === null) { + $new = new Node($node->key, $node->data); + $new->parent = null; + $new->tree = $this; + + $this->root = $new; + + return; + } + + $current = $this->root; + while (true) { + $comparison = $node->compare($current->data); + + if ($comparison < 0) { + if ($current->left === null) { + $BST = new BinarySearchTree(); + $new = new Node($node->key, $node->data); + $new->parent = $current; + $new->tree = $BST; + + $BST->root = $new; + $current->left = $BST; + } else { + $current = $current->left->root; + } + } elseif ($comparison > 0) { + if ($current->right === null) { + $BST = new BinarySearchTree(); + $new = new Node($node->key, $node->data); + $new->parent = $current; + $new->tree = $BST; + + $BST->root = $new; + $current->right = $BST; + } else { + $current = $current->right->root; + } + } + + return; + } + } + + public function delete(Node &$node) : void + { + if ($node->left === null && $node->right === null) { + if ($node->parent !== null) { + if ($node->parent->left !== null && $node->parent->left->root->compare($node->data) === 0) { + $node->parent->left = null; + } elseif ($node->parent->right !== null && $node->parent->right->root->compare($node) === 0) { + $node->parent->right = null; + } + } + + $node = null; + + return; + } + + $temp = null; + if ($node->left === null) { + $temp = $node->right->root; + if ($node->parent !== null) { + if ($node->parent->left !== null && $node->parent->left->root->compare($node->data) === 0) { + $node->parent->left = $temp->tree; + } elseif ($node->parent->right !== null && $node->parent->right->root->compare($node->data) === 0) { + $node->parent->right = $temp->tree; + } + } + + $temp->parent = $node->parent; + + $node = null; + + return; + } + + if ($node->right === null) { + $temp = $node->left->root; + if ($node->parent !== null) { + if ($node->parent->left !== null && $node->parent->left->root->compare($node->data) === 0) { + $node->parent->left = $temp->tree; + } elseif ($node->parent->right !== null && $node->parent->right->root->compare($node->data) === 0) { + $node->parent->right = $temp->tree; + } + } + + $temp->parent = $node->parent; + + $node = null; + + return; + } else { + $temp = $this->successor($node); + $node->key = $temp->key; + $node->data = $temp->data; + + $this->delete($temp); + } + } +} \ No newline at end of file diff --git a/Stdlib/Tree/Node.php b/Stdlib/Tree/Node.php new file mode 100644 index 000000000..9d2620d91 --- /dev/null +++ b/Stdlib/Tree/Node.php @@ -0,0 +1,49 @@ +key = $key; + $this->data = $data; + } + + public function compare(mixed $data) : int + { + return $this->data <=> $data; + } +} \ No newline at end of file diff --git a/System/MimeType.php b/System/MimeType.php index 27b9a49e6..54fde282e 100755 --- a/System/MimeType.php +++ b/System/MimeType.php @@ -458,8 +458,6 @@ abstract class MimeType extends Enum public const M_EVY = 'application/x-envoy'; - public const M_EXE = 'application/x-msdownload'; - public const M_EXI = 'application/exi'; public const M_EXT = 'application/vnd.novadigm.ext'; @@ -2010,6 +2008,20 @@ abstract class MimeType extends Enum public const M_123 = 'application/vnd.lotus-1-2-3'; + public const M_PEXE = 'vnd.microsoft.portable-executable'; + + public const M_EXE = 'application/exe'; + + public const M_DEXE = 'application/dos-exe'; + + public const M_XEXE = 'application/x-winexe'; + + public const M_MDEXE = 'application/msdos-windows'; + + public const M_MSP = 'application/x-msdos-program'; + + public const M_XMDEXE = 'application/x-msdownload'; + /** * Get mime from file extension * @@ -2036,7 +2048,6 @@ abstract class MimeType extends Enum * @return null|string * * @since 1.0.0 - * @todo continue implementation */ public static function mimeToExtension(string $mime) : ?string { @@ -2046,6 +2057,10 @@ abstract class MimeType extends Enum case self::M_JPEG: case self::M_JPG: return 'jpg'; + case self::M_PNG: + return 'png'; + case self::M_SVG: + return 'svg'; case self::M_BMP: return 'bmp'; case self::M_GIF: @@ -2053,6 +2068,51 @@ abstract class MimeType extends Enum case self::M_HTML: case self::M_HTM: return 'htm'; + case self::M_DOCX: + return 'docx'; + case self::M_DOC: + return 'doc'; + case self::M_ODT: + return 'odt'; + case self::M_XLSX: + return 'xlsx'; + case self::M_XLA: + case self::M_XLS: + return 'xls'; + case self::M_ODS: + return 'ods'; + case self::M_PPTX: + return 'pptx'; + case self::M_PPT: + return 'ppt'; + case self::M_ODP: + return 'odp'; + case self::M_CSV: + return 'csv'; + case self::M_XML: + return 'xml'; + case self::M_JSON: + return 'json'; + case self::M_ZIP: + return 'zip'; + case self::M_7Z: + return '7z'; + case self::M_RAR: + return 'rar'; + case self::M_TAR: + return 'tar'; + case self::M_MP3: + return 'mp3'; + case self::M_MP4: + return 'mp4'; + case self::M_PEXE: + case self::M_EXE: + case self::M_DEXE: + case self::M_XEXE: + case self::M_MDEXE: + case self::M_MSP: + case self::M_XMDEXE: + return 'exe'; default: return null; } diff --git a/tests/Algorithm/Clustering/KmeansTest.php b/tests/Algorithm/Clustering/KmeansTest.php index 5a2a30a40..861f745ab 100755 --- a/tests/Algorithm/Clustering/KmeansTest.php +++ b/tests/Algorithm/Clustering/KmeansTest.php @@ -17,6 +17,8 @@ namespace phpOMS\tests\Algorithm\Clustering; use phpOMS\Algorithm\Clustering\Kmeans; use phpOMS\Algorithm\Clustering\Point; +include __DIR__ . '/../../Autoloader.php'; + /** * @testdox phpOMS\tests\Algorithm\Clustering\KmeansTest: Clustering points/elements with the K-means algorithm * @@ -34,6 +36,9 @@ final class KmeansTest extends \PHPUnit\Framework\TestCase $seed = \mt_rand(\PHP_INT_MIN, \PHP_INT_MAX); \mt_srand($seed); + // The following seed + putting the loop to 1 would fail the test + //\mt_srand(1788576141); + $result = false; // due to the random nature this can be false sometimes?!