PluginProbe
404 Solution / trunk
404 Solution vtrunk
4.3.5 4.3.4 4.3.3 4.3.2 4.3.1 4.3.0 4.2.0 4.1.19 4.1.18 4.1.17 4.1.16 4.1.15 4.1.13 4.1.12 4.1.11 4.1.10 4.1.9 4.1.8 4.1.7 4.1.6 4.1.5 4.1.4 4.1.3 trunk 2.30.0 All 109 releases
404-solution / includes / ngram / NGramExtractor.php

NGramExtractor.php in 404 Solution trunk, at includes/ngram/NGramExtractor.php

81 lines 2.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 if (!defined('ABSPATH')) {
4 exit;
5 }
6
7 /**
8 * Extracts N-grams (bigrams + trigrams) from a URL string.
9 *
10 * Pure computation. No I/O, no DB, no transients. Both production callers
11 * (the live 404 candidate-selection pipeline via NGramFilter, and the
12 * background term-cache rebuild loops in DatabaseUpgradeNGram) call into
13 * this for the same purpose: take a URL, produce a deduplicated set of
14 * character N-grams suitable for Dice-coefficient similarity. The only
15 * collaborator needed is the polymorphic mbstring adapter (Functions),
16 * because URLs may contain UTF-8 (i18n permalink slugs).
17 */
18 class ABJ_404_Solution_NGramExtractor {
19
20 /** Cap on URL length before extraction; above this the URL is truncated.
21 * Most real URLs are < 200 chars; 500 prevents memory blowup on 2083-char
22 * legacy URLs (4000+ N-grams per URL) without losing matching value. */
23 const MAX_URL_LENGTH = 500;
24
25 /** @var ABJ_404_Solution_Functions */
26 private $f;
27
28 /** @var ABJ_404_Solution_Logging */
29 private $logger;
30
31 /**
32 * @param ABJ_404_Solution_Functions|null $functions
33 * @param ABJ_404_Solution_Logging|null $logging
34 */
35 public function __construct($functions = null, $logging = null) {
36 $this->f = $functions !== null ? $functions : abj_service('functions');
37 $this->logger = $logging !== null ? $logging : abj_service('logging');
38 }
39
40 /**
41 * Extract N-grams from a URL string.
42 *
43 * Generates both bigrams (n=2) and trigrams (n=3) for optimal accuracy.
44 * Research shows using both provides better typo detection than either alone.
45 *
46 * @param string $url
47 * @param array<int, int> $ngramSizes default [2, 3]
48 * @return array{bi: array<int, string>, tri: array<int, string>}
49 */
50 public function extractNGrams($url, $ngramSizes = [2, 3]) {
51 if (empty($url)) {
52 return ['bi' => [], 'tri' => []];
53 }
54
55 $url = $this->f->strtolower($url);
56
57 $originalLength = $this->f->strlen($url);
58 if ($originalLength > self::MAX_URL_LENGTH) {
59 $this->logger->infoMessage("WARNING: URL too long for N-gram extraction: {$originalLength} chars, truncating to " . self::MAX_URL_LENGTH . ". URL: " . $this->f->substr($url, 0, 100) . "...");
60 $url = $this->f->substr($url, 0, self::MAX_URL_LENGTH);
61 }
62
63 $result = [];
64 $length = $this->f->strlen($url);
65
66 foreach ($ngramSizes as $n) {
67 $ngrams = [];
68 for ($i = 0; $i <= $length - $n; $i++) {
69 $ngram = $this->f->substr($url, $i, $n);
70 $ngrams[$ngram] = true;
71 }
72 $key = ($n == 2) ? 'bi' : 'tri';
73 // Convert keys to strings to prevent PHP from converting numeric strings to integers
74 $result[$key] = array_map('strval', array_keys($ngrams));
75 }
76
77 /** @var array{bi: array<int, string>, tri: array<int, string>} $result */
78 return $result;
79 }
80 }
81