mirror of
https://github.com/Karaka-Management/phpOMS.git
synced 2026-01-10 17:28:40 +00:00
fix tests
This commit is contained in:
parent
efd5580a75
commit
af9a7e1f29
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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];
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user