214 lines
No EOL
6.7 KiB
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;
|
|
}
|
|
}
|
|
}
|
|
} |