fix tests

This commit is contained in:
Dennis Eichhorn 2024-04-24 22:43:45 +00:00
parent efd5580a75
commit af9a7e1f29
5 changed files with 70 additions and 36 deletions

View File

@ -150,20 +150,8 @@ final class MemoryCF
} }
/** /**
* Find potential items/users which are a good match for a user/item. * Find similar users
* *
* The algorithm uses the ratings of a a user and tries to find other users who have similar rating behavior and then searches for high rated items that the user doesn't have yet.
*
* This can be used to find items for a specific user (aka might be interested in) or to find users who might be interested in this item
*
* option 1 - find items
* ranking[itemId] = itemRank (how much does specific user like item)
* rankings[userId][itemId] = itemRank
*
* option 2 - find user
* ranking[userId] = itemRank (how much does user like specific item)
* rankings[itemId][userId] = itemRank
* option 1 searches for items, option 2 searches for users
* *
* @param array $ranking Array of item ratings (e.g. products, movies, ...) * @param array $ranking Array of item ratings (e.g. products, movies, ...)
* *
@ -171,7 +159,46 @@ final class MemoryCF
* *
* @since 1.0.0 * @since 1.0.0
*/ */
public function bestMatch(array $ranking, int $size = 10) : array public function bestMatchUser(array $ranking, int $size = 10) : array
{
$ranking = $this->normalizeRanking([$ranking]);
$ranking = $ranking[0];
$euclidean = $this->euclideanDistance($ranking, $this->rankings);
// $cosine = $this->cosineDistance($ranking, $this->rankings);
\asort($euclidean);
// \asort($cosine);
$size = \min($size, \count($this->rankings));
$matches = [];
$distancePointer = \array_keys($euclidean);
// $anglePointer = \array_keys($cosine);
// Inspect items of the top n comparable users
for ($i = 0; $i < $size; ++$i) {
// $uId = $i % 2 === 0 ? $distancePointer[$i] : $anglePointer[$i];
$uId = $distancePointer[$i];
if (!\in_array($uId, $matches)) {
$matches[] = $uId;
}
}
return $matches;
}
/**
* Find potential users which are a good match for a user.
*
* @param array $ranking Array of item ratings (e.g. products, movies, ...)
*
* @return array
*
* @since 1.0.0
*/
public function bestMatchItem(array $ranking, int $size = 10) : array
{ {
$ranking = $this->normalizeRanking([$ranking]); $ranking = $this->normalizeRanking([$ranking]);
$ranking = $ranking[0]; $ranking = $ranking[0];
@ -194,13 +221,13 @@ final class MemoryCF
$distances = $i % 2 === 0 ? $euclidean : $cosine; $distances = $i % 2 === 0 ? $euclidean : $cosine;
foreach ($this->rankings[$uId] as $iId => $_) { foreach ($this->rankings[$uId] as $iId => $_) {
// Item is not already in dataset and not in historic dataset (we are only interested in new) // Item is already in dataset or in historic dataset (we are only interested in new)
if (isset($matches[$iId]) || isset($ranking[$iId])) { if (isset($matches[$iId]) || isset($ranking[$iId])) {
continue; continue;
} }
// Calculate the expected rating the user would give based on what the best comparable users did // Calculate the expected rating the user would give based on what the best comparable users did
$matches[$iId] = $this->weightedItemRank($iId, $distances, $this->rankings, $size); $matches[$iId] = $this->weightedItemRank((string) $iId, $distances, $this->rankings, $size);
} }
} }

View File

@ -74,16 +74,10 @@ final class MetricsND
* *
* @return float * @return float
* *
* @throws InvalidDimensionException
*
* @since 1.0.0 * @since 1.0.0
*/ */
public static function euclidean(array $a, array $b) : float public static function euclidean(array $a, array $b) : float
{ {
if (\count($a) !== \count($b)) {
throw new InvalidDimensionException(\count($a) . 'x' . \count($b));
}
$dist = 0.0; $dist = 0.0;
foreach ($a as $key => $e) { foreach ($a as $key => $e) {
$dist += \abs($e - $b[$key]) ** 2; $dist += \abs($e - $b[$key]) ** 2;
@ -100,16 +94,10 @@ final class MetricsND
* *
* @return float * @return float
* *
* @throws InvalidDimensionException
*
* @since 1.0.0 * @since 1.0.0
*/ */
public static function cosine(array $a, array $b) : float public static function cosine(array $a, array $b) : float
{ {
if (\count($a) !== \count($b)) {
throw new InvalidDimensionException(\count($a) . 'x' . \count($b));
}
$dotProduct = 0; $dotProduct = 0;
foreach ($a as $id => $_) { foreach ($a as $id => $_) {
$dotProduct += $a[$id] * $b[$id]; $dotProduct += $a[$id] * $b[$id];

View File

@ -129,21 +129,25 @@ final class SocketRouter implements RouterInterface
} }
foreach ($destination as $d) { foreach ($destination as $d) {
if (!($d['active'] ?? true)) {
continue;
}
if ((!isset($d['verb']) || $d['verb'] === RouteVerb::ANY) if ((!isset($d['verb']) || $d['verb'] === RouteVerb::ANY)
|| $verb === RouteVerb::ANY || $verb === RouteVerb::ANY
|| ($verb & $d['verb']) === $verb || ($verb & $d['verb']) === $verb
) { ) {
// if csrf is required but not set // if csrf is required but not set
if (isset($d['csrf']) && $d['csrf'] && $csrf === null) { if (($d['csrf'] ?? false) && $csrf === null) {
return ['dest' => RouteStatus::INVALID_CSRF]; return ['dest' => RouteStatus::INVALID_CSRF];
} }
// if permission check is invalid // if permission check is invalid
if (isset($d['permission']) && !empty($d['permission']) if (!empty($d['permission'] ?? null)
&& ($account === null || $account->id === 0) && ($account === null || $account->id === 0)
) { ) {
return ['dest' => RouteStatus::NOT_LOGGED_IN]; return ['dest' => RouteStatus::NOT_LOGGED_IN];
} elseif (isset($d['permission']) && !empty($d['permission']) } elseif (!empty($d['permission'] ?? null)
&& !($account?->hasPermission( && !($account?->hasPermission(
$d['permission']['type'] ?? 0, $d['permission']['type'] ?? 0,
$d['permission']['unit'] ?? $unitId, $d['permission']['unit'] ?? $unitId,

View File

@ -150,16 +150,16 @@ final class WebRouter implements RouterInterface
|| ($verb & $d['verb']) === $verb || ($verb & $d['verb']) === $verb
) { ) {
// if csrf is required but not set // if csrf is required but not set
if (isset($d['csrf']) && $d['csrf'] && $csrf === null) { if (($d['csrf'] ?? false) && $csrf === null) {
return ['dest' => RouteStatus::INVALID_CSRF]; return ['dest' => RouteStatus::INVALID_CSRF];
} }
// if permission check is invalid // if permission check is invalid
if (isset($d['permission']) && !empty($d['permission']) if (!empty($d['permission'] ?? null)
&& ($account === null || $account->id === 0) && ($account === null || $account->id === 0)
) { ) {
return ['dest' => RouteStatus::NOT_LOGGED_IN]; return ['dest' => RouteStatus::NOT_LOGGED_IN];
} elseif (isset($d['permission']) && !empty($d['permission']) } elseif (!empty($d['permission'] ?? null)
&& !($account?->hasPermission( && !($account?->hasPermission(
$d['permission']['type'] ?? 0, $d['permission']['type'] ?? 0,
$d['permission']['unit'] ?? $unitId, $d['permission']['unit'] ?? $unitId,

View File

@ -22,7 +22,7 @@ use phpOMS\Business\Recommendation\MemoryCF;
#[\PHPUnit\Framework\Attributes\TestDox('phpOMS\tests\Business\Recommendation\MemoryCFTest: Article affinity/correlation')] #[\PHPUnit\Framework\Attributes\TestDox('phpOMS\tests\Business\Recommendation\MemoryCFTest: Article affinity/correlation')]
final class MemoryCFTest extends \PHPUnit\Framework\TestCase final class MemoryCFTest extends \PHPUnit\Framework\TestCase
{ {
public function testBestMatch() : void public function testBestMatchUser() : void
{ {
$memory = new MemoryCF([ $memory = new MemoryCF([
'A' => [1.0, 2.0], 'A' => [1.0, 2.0],
@ -33,7 +33,22 @@ final class MemoryCFTest extends \PHPUnit\Framework\TestCase
self::assertEquals( self::assertEquals(
['B', 'C'], ['B', 'C'],
$memory->bestMatch([2.2, 4.1], 2) $memory->bestMatchUser([2.2, 4.1], 2)
);
}
public function testBestMatchItem() : void
{
$memory = new MemoryCF([
'A' => [1.0, 2.0],
'B' => [2.0, 4.0],
'C' => [2.5, 4.0],
'D' => [4.5, 5.0],
]);
self::assertEquals(
[1 => 0.0],
$memory->bestMatchItem([2.2], 2)
); );
} }
} }