1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
declare(strict_types=1); |
4
|
|
|
|
5
|
|
|
namespace Stadly\PasswordPolice\CharTree; |
6
|
|
|
|
7
|
|
|
use InvalidArgumentException; |
8
|
|
|
use Stadly\PasswordPolice\CharTree; |
9
|
|
|
|
10
|
|
|
final class Cutter |
11
|
|
|
{ |
12
|
|
|
/** |
13
|
|
|
* @var array<array<array<string|CharTree>>> Memoization of cut character trees. |
14
|
|
|
*/ |
15
|
|
|
private static $memoization = []; |
16
|
|
|
|
17
|
|
|
/** |
18
|
|
|
* @param CharTree $charTree Character tree to cut. |
19
|
|
|
* @param int $position Number of characters before cut. |
20
|
|
|
* @return array<array<string|CharTree>> Cut variants of the character tree. |
21
|
|
|
*/ |
22
|
14 |
|
public function cut(CharTree $charTree, int $position): array |
23
|
|
|
{ |
24
|
14 |
|
if ($position < 0) { |
25
|
4 |
|
throw new InvalidArgumentException('Position must be non-negative.'); |
26
|
|
|
} |
27
|
|
|
|
28
|
|
|
// When PHP 7.1 is no longer supported, change to using spl_object_id. |
29
|
10 |
|
$hash = spl_object_hash($charTree) . ';' . $position; |
30
|
|
|
|
31
|
10 |
|
if (!isset(self::$memoization[$hash])) { |
32
|
10 |
|
self::$memoization[$hash] = $this->cutInternal($charTree, $position); |
33
|
|
|
} |
34
|
|
|
|
35
|
10 |
|
return self::$memoization[$hash]; |
36
|
|
|
} |
37
|
|
|
|
38
|
|
|
/** |
39
|
|
|
* @param CharTree $charTree Character tree to cut. |
40
|
|
|
* @param int $position Number of characters before cut. |
41
|
|
|
* @return array<array<string|CharTree>> Cut variants of the character tree. Memoization is not used. |
42
|
|
|
*/ |
43
|
10 |
|
private function cutInternal(CharTree $charTree, int $position): array |
44
|
|
|
{ |
45
|
10 |
|
$root = $charTree->getRoot(); |
46
|
|
|
|
47
|
10 |
|
if ($root === null) { |
48
|
3 |
|
return []; |
49
|
|
|
} |
50
|
|
|
|
51
|
8 |
|
$rootLength = mb_strlen($root); |
52
|
8 |
|
$branchPosition = $position - $rootLength; |
53
|
|
|
|
54
|
8 |
|
if ($branchPosition < 0) { |
55
|
1 |
|
return [['', $charTree]]; |
56
|
|
|
} |
57
|
|
|
|
58
|
7 |
|
if ($branchPosition === 0) { |
59
|
5 |
|
return [[$root, CharTree::fromArray($charTree->getBranches())]]; |
60
|
|
|
} |
61
|
|
|
|
62
|
5 |
|
$cutCharTrees = []; |
63
|
5 |
|
foreach ($charTree->getBranches() as $branch) { |
64
|
4 |
|
foreach ($this->cut($branch, $branchPosition) as [$branchRoot, $branchTree]) { |
65
|
3 |
|
assert(is_string($branchRoot)); |
66
|
3 |
|
assert(is_object($branchTree)); |
67
|
|
|
|
68
|
4 |
|
$cutCharTrees[] = [$root . $branchRoot, $branchTree]; |
69
|
|
|
} |
70
|
|
|
} |
71
|
|
|
|
72
|
5 |
|
return $cutCharTrees; |
73
|
|
|
} |
74
|
|
|
} |
75
|
|
|
|