federationlib/src/FederationLib/Classes/Utilities.php

214 lines
No EOL
6.7 KiB
PHP

<?php
/** @noinspection PhpMissingFieldTypeInspection */
namespace FederationLib\Classes;
use Exception;
use FederationLib\Enums\SerializationMethod;
use FederationLib\Interfaces\SerializableObjectInterface;
use InvalidArgumentException;
use LogLib\Log;
use Throwable;
class Utilities
{
/**
* @var string[]|null
*/
private static $wordlist;
/**
* Returns an array of words from the wordlist.
*
* @param bool $cache True to cache the wordlist in memory, false to not cache the wordlist
* @return string[]
*/
public static function getWordlist(bool $cache=true): array
{
if(self::$wordlist !== null)
{
return self::$wordlist;
}
$wordlist = file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . 'Data' . DIRECTORY_SEPARATOR . 'wordlist.txt');
$wordlist = array_filter(array_map('trim', explode("\n", $wordlist)));
if($cache)
{
self::$wordlist = $wordlist;
}
return $wordlist;
}
/**
* Generates a random name.
*
* @param int $word_count
* @param string $separator
* @param bool $capitalize
* @return string
*/
public static function generateName(int $word_count=3, string $separator=' ', bool $capitalize=true): string
{
$wordlist = self::getWordlist();
$name = '';
for($i = 0; $i < $word_count; $i++)
{
$name .= $wordlist[array_rand($wordlist)] . $separator;
}
$name = substr($name, 0, -1);
if($capitalize)
{
$name = ucwords($name);
}
return $name;
}
/**
* Parses a federated address into an array of parts.
* Example: entity:uid
*
* @param string $address
* @return array
*/
public static function parseFederatedAddress(string $address): array
{
if (preg_match($address, '/^(?P<entity>[a-zA-Z0-9_-]+):(?P<uid>[a-zA-Z0-9_-]+)$/', $matches, PREG_UNMATCHED_AS_NULL))
{
return [
'entity' => $matches['entity'],
'uid' => $matches['uid']
];
}
throw new InvalidArgumentException(sprintf('Invalid address provided: %s', $address));
}
/**
* Serializes an array into a string.
*
* @param array $data
* @param string $method
* @return string
*/
public static function serialize(array|SerializableObjectInterface $data, string $method): string
{
if($data instanceof SerializableObjectInterface)
{
$data = $data->toArray();
}
switch(strtolower($method))
{
case SerializationMethod::JSON:
return json_encode($data);
case SerializationMethod::MSGPACK:
return msgpack_pack($data);
default:
Log::warning('net.nosial.federationlib', sprintf('Unknown serialization method: %s, defaulting to msgpack', $method));
return msgpack_pack($data);
}
}
/**
* Recursively converts a Throwable into an array representation.
*
* @param Throwable $throwable
* @return array
*/
public static function throwableToArray(Throwable $throwable): array
{
$results = [
'message' => $throwable->getMessage(),
'code' => $throwable->getCode(),
'file' => $throwable->getFile(),
'line' => $throwable->getLine(),
'trace' => $throwable->getTrace(),
'previous' => $throwable->getPrevious()
];
if($results['previous'] instanceof Throwable)
{
$results['previous'] = self::throwableToArray($results['previous']);
}
return $results;
}
/**
* Uses the z-score method to detect anomalies in an array of numbers.
* The key of the returned array is the index of the number in the original array.
* The value of the returned array is the probability of the number being an anomaly.
* Negative values are anomalies, positive values are not.
* The higher the absolute value, the more likely it is to be an anomaly.
*
* @param array $data An array of numbers to check for anomalies
* @param int $threshold The threshold to use for detecting anomalies
* @param bool $filter True to only return anomalies, false to return all values
* @return array An array of probabilities
*/
public static function detectAnomalies(array $data, int $threshold = 2, bool $filter=true): array
{
$mean = array_sum($data) / count($data);
$squares = array_map(static function($x) use ($mean) { return ($x - $mean) ** 2; }, $data);
$standard_deviation = sqrt(array_sum($squares) / count($data));
$outliers = [];
foreach ($data as $key => $value)
{
$z_score = ($value - $mean) / $standard_deviation;
$probability = exp(-$z_score ** 2 / 2) / (sqrt(2 * M_PI) * $standard_deviation);
if($filter)
{
if ($z_score >= $threshold)
{
$outliers[$key] = -$probability;
}
}
else
{
$outliers[$key] = $probability;
}
}
return $outliers;
}
public static function weightedRandomPick( array $data): string
{
$totalWeight = array_sum($data);
if($totalWeight == 0)
{
throw new InvalidArgumentException('Total weight cannot be 0');
}
// Normalize weights to 0-1
foreach ($data as $item => $weight)
{
$data[$item] = $weight / $totalWeight;
}
// Generate a random number between 0 and 1
$rand = mt_rand() / getrandmax();
// Select an item
$cumulativeWeight = 0.0;
foreach ($data as $item => $weight)
{
$cumulativeWeight += $weight;
if ($rand < $cumulativeWeight)
{
return $item;
}
}
}
}