*/ abstract class DirectCollection extends AbstractCollection { /** @inheritdoc */ public function checkIntegrity(): void { $entityIndex = $this->getEntityIndex(); $tokenIndex = $this->getTokenIndex(); if ($entityIndex->exists() && $tokenIndex->exists()) { $ec = count($entityIndex); $tc = count($tokenIndex); if ($ec !== $tc) { throw new IndexIntegrityException( "Entity count ($ec) != token count ($tc)" ); } } } /** * Store a single token for the given entity * * Takes the first token from the list and writes it directly at the entity's position * in the token index. An empty list stores an empty string. * * @param string $entity The name of the entity * @param string[] $tokens The list of tokens (only the first is used) * @return static * @throws IndexLockException * @throws IndexAccessException * @throws IndexWriteException */ public function addEntity(string $entity, array $tokens): static { if (!$this->isWritable) { throw new IndexLockException('Indexes not locked. Forgot to call lock()?'); } $entityIndex = $this->getEntityIndex(); $entityId = $entityIndex->accessCachedValue($entity); $token = $tokens[0] ?? ''; $tokenIndex = $this->getTokenIndex(); $tokenIndex->changeRow($entityId, $token); $tokenIndex->save(); return $this; } /** * Get the token stored for the given entity * * @param string $entity The name of the entity * @return string The stored token, or empty string if none * @throws IndexAccessException * @throws IndexLockException * @throws IndexWriteException */ public function getToken(string $entity): string { $entityIndex = $this->getEntityIndex(); $entityId = $entityIndex->accessCachedValue($entity); $tokenIndex = $this->getTokenIndex(); return $tokenIndex->retrieveRow($entityId); } /** @inheritdoc */ public function resolveTokenFrequencies(int $group, array $tokenIds): array { // In a DirectCollection, token RID = entity RID, frequency is always 1 $result = []; foreach ($tokenIds as $tokenId) { $result[$tokenId] = [$tokenId => 1]; } return $result; } /** @inheritdoc */ public function getEntitiesWithData(): array { $entityIndex = $this->getEntityIndex(); $tokenIndex = $this->getTokenIndex(); $entityIds = []; foreach ($tokenIndex as $entityId => $token) { if ($token !== '') $entityIds[] = $entityId; } $names = $entityIndex->retrieveRows($entityIds); return array_values(array_filter($names, static fn($v) => $v !== '')); } /** * Frequency histogram of the tokens in this collection * * Each entity holds exactly one token, so this counts how many entities * share each token value. * * @param int $min minimum frequency a token must reach to be included * @param int $max maximum frequency to include, 0 for no upper limit * @param int $minlen minimum token length to include * @return array token => frequency, ordered by frequency descending * @throws IndexLockException */ public function histogram(int $min = 1, int $max = 0, int $minlen = 3): array { if ($min < 1) $min = 1; if ($max < $min) $max = 0; if ($minlen < 1) $minlen = 1; $tokenIndex = $this->getTokenIndex(); if (!$tokenIndex->exists()) return []; // an empty token is shorter than $minlen (>= 1) and so is filtered here $counts = []; foreach ($tokenIndex as $token) { if (strlen($token) < $minlen) continue; $counts[$token] = ($counts[$token] ?? 0) + 1; } $result = array_filter( $counts, static fn($freq) => $freq >= $min && (!$max || $freq <= $max) ); arsort($result); return $result; } /** * Not actually used, because we override addEntity() to directly write the token. * @inheritdoc */ protected function countTokens(array $tokens): array { $token = $tokens[0] ?? ''; return [$token => 1]; } }