Skip to content

Commit 8019f65

Browse files
ondrejmirtesclaude
andcommitted
Use hash map for O(1) key lookup in ConstantArrayType
`findKeyIndex` did O(k) linear scan per key, making `mergeWith` O(k²) and `isKeysSupersetOf` O(k²). Add a lazily-built `keyIndexMap` that maps key values to their indices for O(1) lookup. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 3f5f2de commit 8019f65

File tree

2 files changed

+56
-5
lines changed

2 files changed

+56
-5
lines changed

src/Type/Constant/ConstantArrayType.php

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,9 @@ class ConstantArrayType implements Type
9494

9595
private ?Type $iterableValueType = null;
9696

97+
/** @var array<int|string, int>|null */
98+
private ?array $keyIndexMap = null;
99+
97100
/**
98101
* @api
99102
* @param list<ConstantIntegerType|ConstantStringType> $keyTypes
@@ -1805,15 +1808,17 @@ public function isKeysSupersetOf(self $otherArray): bool
18051808

18061809
$failOnDifferentValueType = $keyTypesCount !== $otherKeyTypesCount || $keyTypesCount < 2;
18071810

1808-
$keyTypes = $this->keyTypes;
1811+
$keyIndexMap = $this->getKeyIndexMap();
1812+
$otherKeyValues = [];
18091813

18101814
foreach ($otherArray->keyTypes as $j => $keyType) {
1811-
$i = self::findKeyIndex($keyType, $keyTypes);
1815+
$keyValue = $keyType->getValue();
1816+
$i = $keyIndexMap[$keyValue] ?? null;
18121817
if ($i === null) {
18131818
return false;
18141819
}
18151820

1816-
unset($keyTypes[$i]);
1821+
$otherKeyValues[$keyValue] = true;
18171822

18181823
$valueType = $this->valueTypes[$i];
18191824
$otherValueType = $otherArray->valueTypes[$j];
@@ -1828,7 +1833,10 @@ public function isKeysSupersetOf(self $otherArray): bool
18281833
}
18291834

18301835
$requiredKeyCount = 0;
1831-
foreach (array_keys($keyTypes) as $i) {
1836+
foreach ($this->keyTypes as $i => $keyType) {
1837+
if (isset($otherKeyValues[$keyType->getValue()])) {
1838+
continue;
1839+
}
18321840
if ($this->isOptionalKey($i)) {
18331841
continue;
18341842
}
@@ -1868,12 +1876,29 @@ public function mergeWith(self $otherArray): self
18681876
return $this->recreate($this->keyTypes, $valueTypes, $nextAutoIndexes, $optionalKeys, $this->isList->and($otherArray->isList));
18691877
}
18701878

1879+
/**
1880+
* @return array<int|string, int>
1881+
*/
1882+
private function getKeyIndexMap(): array
1883+
{
1884+
if ($this->keyIndexMap !== null) {
1885+
return $this->keyIndexMap;
1886+
}
1887+
1888+
$map = [];
1889+
foreach ($this->keyTypes as $i => $keyType) {
1890+
$map[$keyType->getValue()] = $i;
1891+
}
1892+
1893+
return $this->keyIndexMap = $map;
1894+
}
1895+
18711896
/**
18721897
* @param ConstantIntegerType|ConstantStringType $otherKeyType
18731898
*/
18741899
private function getKeyIndex($otherKeyType): ?int
18751900
{
1876-
return self::findKeyIndex($otherKeyType, $this->keyTypes);
1901+
return $this->getKeyIndexMap()[$otherKeyType->getValue()] ?? null;
18771902
}
18781903

18791904
/**

0 commit comments

Comments
 (0)