-
Notifications
You must be signed in to change notification settings - Fork 14
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Performance optimizations #18
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,6 +17,12 @@ | |
use InvalidArgumentException; | ||
use RuntimeException; | ||
|
||
use function strlen; | ||
use function ord; | ||
use function in_array; | ||
use function count; | ||
use function array_key_exists; | ||
|
||
class Sqids implements SqidsInterface | ||
{ | ||
final public const DEFAULT_ALPHABET = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; | ||
|
@@ -606,16 +612,12 @@ public function __construct( | |
throw new InvalidArgumentException('Alphabet length must be at least 3'); | ||
} | ||
|
||
if (count(array_unique(str_split($alphabet))) !== strlen($alphabet)) { | ||
if (preg_match('/(.).*\1/', $alphabet)) { | ||
throw new InvalidArgumentException('Alphabet must contain unique characters'); | ||
} | ||
|
||
$minLengthLimit = 255; | ||
if ( | ||
!is_int($minLength) || | ||
$minLength < 0 || | ||
$minLength > $minLengthLimit | ||
) { | ||
if ($minLength < 0 || $minLength > $minLengthLimit) { | ||
throw new InvalidArgumentException( | ||
'Minimum length has to be between 0 and ' . $minLengthLimit, | ||
); | ||
|
@@ -635,7 +637,6 @@ public function __construct( | |
} | ||
|
||
$this->alphabet = $this->shuffle($alphabet); | ||
$this->minLength = $minLength; | ||
$this->blocklist = $filteredBlocklist; | ||
} | ||
|
||
|
@@ -655,11 +656,12 @@ public function encode(array $numbers): string | |
return ''; | ||
} | ||
|
||
$inRangeNumbers = array_filter($numbers, fn($n) => $n >= 0 && $n <= self::maxValue()); | ||
if (count($inRangeNumbers) != count($numbers)) { | ||
throw new InvalidArgumentException( | ||
'Encoding supports numbers between 0 and ' . self::maxValue(), | ||
); | ||
Comment on lines
-658
to
-662
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of creating a new array, the exception is thrown directly when there is an invalid number. |
||
foreach ($numbers as $n) { | ||
if ($n < 0 || $n > self::maxValue()) { | ||
throw new InvalidArgumentException( | ||
'Encoding supports numbers between 0 and ' . self::maxValue(), | ||
); | ||
} | ||
} | ||
|
||
return $this->encodeNumbers($numbers); | ||
|
@@ -688,26 +690,24 @@ protected function encodeNumbers(array $numbers, int $increment = 0): string | |
$alphabet = substr($this->alphabet, $offset) . substr($this->alphabet, 0, $offset); | ||
$prefix = $alphabet[0]; | ||
$alphabet = strrev($alphabet); | ||
$ret = [$prefix]; | ||
$id = $prefix; | ||
|
||
for ($i = 0; $i != count($numbers); $i++) { | ||
$num = $numbers[$i]; | ||
|
||
$ret[] = $this->toId($num, substr($alphabet, 1)); | ||
$id .= $this->toId($num, substr($alphabet, 1)); | ||
if ($i < count($numbers) - 1) { | ||
$ret[] = $alphabet[0]; | ||
$id .= $alphabet[0]; | ||
$alphabet = $this->shuffle($alphabet); | ||
} | ||
} | ||
|
||
$id = implode('', $ret); | ||
|
||
if ($this->minLength > strlen($id)) { | ||
$id .= $alphabet[0]; | ||
|
||
while ($this->minLength - strlen($id) > 0) { | ||
$alphabet = $this->shuffle($alphabet); | ||
$id .= substr($alphabet, 0, min($this->minLength - strlen($id), strlen($alphabet))); | ||
$id .= substr($alphabet, 0, min($this->minLength - strlen($id), strlen($this->alphabet))); | ||
} | ||
} | ||
|
||
|
@@ -736,11 +736,8 @@ public function decode(string $id): array | |
return $ret; | ||
} | ||
|
||
$alphabetChars = str_split($this->alphabet); | ||
foreach (str_split($id) as $c) { | ||
if (!in_array($c, $alphabetChars)) { | ||
return $ret; | ||
} | ||
Comment on lines
-739
to
-743
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This split operation is replaced by a more efficient regex. |
||
if (!preg_match('/^[' . preg_quote($this->alphabet, '/') . ']+$/', $id)) { | ||
return $ret; | ||
} | ||
|
||
$prefix = $id[0]; | ||
|
@@ -753,13 +750,13 @@ public function decode(string $id): array | |
$separator = $alphabet[0]; | ||
|
||
$chunks = explode($separator, $id, 2); | ||
if (!empty($chunks)) { | ||
if (array_key_exists(0, $chunks)) { | ||
if ($chunks[0] == '') { | ||
return $ret; | ||
} | ||
|
||
$ret[] = $this->toNumber($chunks[0], substr($alphabet, 1)); | ||
if (count($chunks) > 1) { | ||
if (array_key_exists(1, $chunks)) { | ||
$alphabet = $this->shuffle($alphabet); | ||
} | ||
} | ||
|
@@ -772,40 +769,36 @@ public function decode(string $id): array | |
|
||
protected function shuffle(string $alphabet): string | ||
{ | ||
$chars = str_split($alphabet); | ||
|
||
for ($i = 0, $j = count($chars) - 1; $j > 0; $i++, $j--) { | ||
$r = ($i * $j + ord($chars[$i]) + ord($chars[$j])) % count($chars); | ||
[$chars[$i], $chars[$r]] = [$chars[$r], $chars[$i]]; | ||
for ($i = 0, $j = strlen($alphabet) - 1; $j > 0; $i++, $j--) { | ||
$r = ($i * $j + ord($alphabet[$i]) + ord($alphabet[$j])) % strlen($alphabet); | ||
[$alphabet[$i], $alphabet[$r]] = [$alphabet[$r], $alphabet[$i]]; | ||
Comment on lines
+772
to
+774
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Manipulation of individual characters of the string, instead of using an array of chars. |
||
} | ||
|
||
return implode('', $chars); | ||
return $alphabet; | ||
} | ||
|
||
protected function toId(int $num, string $alphabet): string | ||
{ | ||
$id = []; | ||
$chars = str_split($alphabet); | ||
|
||
$result = $num; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reuse and modify the variable |
||
|
||
$id = ''; | ||
do { | ||
array_unshift($id, $chars[$this->math->intval($this->math->mod($result, count($chars)))]); | ||
$result = $this->math->divide($result, count($chars)); | ||
} while ($this->math->greaterThan($result, 0)); | ||
$id = $alphabet[$this->math->intval($this->math->mod($num, strlen($alphabet)))] . $id; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Appending to the end of the string is the same as using |
||
$num = $this->math->divide($num, strlen($alphabet)); | ||
} while ($this->math->greaterThan($num, 0)); | ||
|
||
return implode('', $id); | ||
return $id; | ||
} | ||
|
||
protected function toNumber(string $id, string $alphabet): int | ||
{ | ||
$chars = str_split($alphabet); | ||
return $this->math->intval(array_reduce(str_split($id), function ($a, $v) use ($chars) { | ||
$number = $this->math->multiply($a, count($chars)); | ||
$number = $this->math->add($number, array_search($v, $chars)); | ||
$number = 0; | ||
for ($i = 0; $i < strlen($id); $i++) { | ||
$number = $this->math->add( | ||
$this->math->multiply($number, strlen($alphabet)), | ||
strpos($alphabet, $id[$i]), | ||
); | ||
} | ||
|
||
return $number; | ||
}, 0)); | ||
return $this->math->intval($number); | ||
} | ||
|
||
protected function isBlockedId(string $id): bool | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Type is already validated in the arg type.