records and edgs

This commit is contained in:
2024-08-19 17:48:10 +03:00
parent 509d7c13f2
commit c97be8666e
46 changed files with 4790 additions and 1387 deletions
+186
View File
@@ -0,0 +1,186 @@
<?php
namespace Lucent\Lexorank;
use function array_filter;
use function chr;
use function in_array;
use function ord;
use function str_split;
use function strcmp;
use function strlen;
use function substr;
final class Lexorank
{
public const string MIN_CHAR = '0';
public const string MAX_CHAR = 'z';
/** Usually, database like MySQL order using only the first 1024 chars */
public const int MAX_RANK_LEN = 1024;
/**
* @var non-empty-string
* @psalm-readonly
*/
private string $rank;
/**
* @throws LexorankException
*/
private function __construct(string $rank)
{
if(empty($rank)){
$this->rank = "0";
return;
}
self::rankValidator($rank);
$this->rank = $rank;
}
/**
* @param non-empty-string $rank
*
* @psalm-pure
* @throws LexorankException
*/
private static function rankValidator(string $rank): void
{
if (strlen($rank) > self::MAX_RANK_LEN) {
throw new LexorankException($rank);
}
$invalidChars = array_filter(
str_split($rank),
static function ($char) {
return ord($char) < ord(self::MIN_CHAR) || ord($char) > ord(self::MAX_CHAR);
}
);
if ($invalidChars !== []) {
throw new LexorankException($rank);
}
$lastChar = substr($rank, -1);
if ($lastChar === self::MIN_CHAR) {
throw new LexorankException($rank);
}
}
/**
* @return non-empty-string
*/
public function get(): string
{
return $this->rank;
}
public static function fromString(string $rank): self
{
return new self($rank);
}
/**
* @psalm-pure
*/
public static function forEmptySequence(): self
{
return self::fromString(self::mid(self::MIN_CHAR, self::MAX_CHAR));
}
/**
* @psalm-pure
*/
public static function after(self $prevRank): self
{
$char = substr($prevRank->get(), -1);
if (ord($char) + 1 >= ord(self::MAX_CHAR)) {
return self::fromString(
$prevRank->get() . chr(ord(self::MIN_CHAR) + 1)
);
}
$return = substr($prevRank->get(), 0, -1) . chr(ord($char) + 1);
return self::fromString($return);
}
/**
* @psalm-pure
*/
public static function before(self $nextRank): self
{
$char = substr($nextRank->get(), -1);
if (ord($char) - 1 <= ord(self::MIN_CHAR)) {
$return = substr($nextRank->get(), 0, -1) . chr(ord($char) - 1) . chr(ord(self::MAX_CHAR) - 1);
return self::fromString($return);
}
$return = substr($nextRank->get(), 0, -1) . chr(ord($char) - 1);
return self::fromString($return);
}
/**
* @psalm-pure
*/
public static function betweenRanks(self $prevRank, self $nextRank): self
{
if (strcmp($prevRank->get(), $nextRank->get()) >= 0) {
throw new LexorankException("problem");
}
$rank = '';
$i = 0;
while ($i <= self::MAX_RANK_LEN) {
$prevChar = $prevRank->getChar($i, self::MIN_CHAR);
$nextChar = $nextRank->getChar($i, self::MAX_CHAR);
$i++;
$midChar = self::mid($prevChar, $nextChar);
if (in_array($midChar, [$prevChar, $nextChar])) {
$rank .= $prevChar;
continue;
}
$rank .= $midChar;
break;
}
return self::fromString($rank);
}
/**
* @param 0|positive-int $i
* @param non-empty-string $defaultChar
*
* @return non-empty-string
*/
private function getChar(int $i, string $defaultChar): string
{
return $this->rank[$i] ?? $defaultChar;
}
/**
* @param non-empty-string $prev
* @param non-empty-string $next
* @return non-empty-string
*
* @psalm-pure
*/
private static function mid(string $prev, string $next): string
{
if (ord($prev) >= ord($next)) {
return $prev;
}
return chr((int)((ord($prev) + ord($next)) / 2));
}
}
+15
View File
@@ -0,0 +1,15 @@
<?php
namespace Lucent\Lexorank;
use Exception;
final class LexorankException extends Exception
{
// Redefine the exception so message isn't optional
public function __construct(string $message, int $code = 0, Exception $previous = null)
{
// make sure everything is assigned properly
parent::__construct($message, $code, $previous);
}
}