]> git.openstreetmap.org Git - nominatim.git/blob - lib/Phrase.php
Merge remote-tracking branch 'upstream/master'
[nominatim.git] / lib / Phrase.php
1 <?php
2
3 namespace Nominatim;
4
5 /**
6  * Segment of a query string.
7  *
8  * The parts of a query strings are usually separated by commas.
9  */
10 class Phrase
11 {
12     const MAX_WORDSET_LEN = 20;
13     const MAX_WORDSETS = 100;
14
15     // Complete phrase as a string.
16     private $sPhrase;
17     // Element type for structured searches.
18     private $sPhraseType;
19     // Space-separated words of the phrase.
20     private $aWords;
21     // Possible segmentations of the phrase.
22     private $aWordSets;
23
24     public static function cmpByArraylen($aA, $aB)
25     {
26         $iALen = count($aA);
27         $iBLen = count($aB);
28
29         if ($iALen == $iBLen) {
30             return 0;
31         }
32
33         return ($iALen < $iBLen) ? -1 : 1;
34     }
35
36
37     public function __construct($sPhrase, $sPhraseType)
38     {
39         $this->sPhrase = trim($sPhrase);
40         $this->sPhraseType = $sPhraseType;
41         $this->aWords = explode(' ', $this->sPhrase);
42     }
43
44     /**
45      * Return the element type of the phrase.
46      *
47      * @return string Pharse type if the phrase comes from a structured query
48      *                or empty string otherwise.
49      */
50     public function getPhraseType()
51     {
52         return $this->sPhraseType;
53     }
54
55     /**
56      * Return the array of possible segmentations of the phrase.
57      *
58      * @return string[][] Array of segmentations, each consisting of an
59      *                    array of terms.
60      */
61     public function getWordSets()
62     {
63         return $this->aWordSets;
64     }
65
66     /**
67      * Add the tokens from this phrase to the given list of tokens.
68      *
69      * @param string[] $aTokens List of tokens to append.
70      *
71      * @return void
72      */
73     public function addTokens(&$aTokens)
74     {
75         $iNumWords = count($this->aWords);
76
77         for ($i = 0; $i < $iNumWords; $i++) {
78             $sPhrase = $this->aWords[$i];
79             $aTokens[' '.$sPhrase] = ' '.$sPhrase;
80             $aTokens[$sPhrase] = $sPhrase;
81
82             for ($j = $i + 1; $j < $iNumWords; $j++) {
83                 $sPhrase .= ' '.$this->aWords[$j];
84                 $aTokens[' '.$sPhrase] = ' '.$sPhrase;
85                 $aTokens[$sPhrase] = $sPhrase;
86             }
87         }
88     }
89
90     /**
91      * Invert the set of possible segmentations.
92      *
93      * @return void
94      */
95     public function invertWordSets()
96     {
97         foreach ($this->aWordSets as $i => $aSet) {
98             $this->aWordSets[$i] = array_reverse($aSet);
99         }
100     }
101
102     public function computeWordSets($oTokens)
103     {
104         $iNumWords = count($this->aWords);
105         // Caches the word set for the partial phrase up to word i.
106         $aSetCache = array_fill(0, $iNumWords, array());
107
108         // Initialise first element of cache. There can only be the word.
109         if ($oTokens->containsAny($this->aWords[0])) {
110             $aSetCache[0][] = array($this->aWords[0]);
111         }
112
113         // Now do the next elements using what we already have.
114         for ($i = 1; $i < $iNumWords; $i++) {
115             for ($j = $i; $j > 0; $j--) {
116                 $sPartial = $j == $i ? $this->aWords[$j] : $this->aWords[$j].' '.$sPartial;
117                 if (!empty($aSetCache[$j - 1]) && $oTokens->containsAny($sPartial)) {
118                     $aPartial = array($sPartial);
119                     foreach ($aSetCache[$j - 1] as $aSet) {
120                         if (count($aSet) < Phrase::MAX_WORDSET_LEN) {
121                             $aSetCache[$i][] = array_merge($aSet, $aPartial);
122                         }
123                     }
124                     if (count($aSetCache[$i]) > 2 * Phrase::MAX_WORDSETS) {
125                         usort(
126                             $aSetCache[$i],
127                             array('\Nominatim\Phrase', 'cmpByArraylen')
128                         );
129                         $aSetCache[$i] = array_slice(
130                             $aSetCache[$i],
131                             0,
132                             Phrase::MAX_WORDSETS
133                         );
134                     }
135                 }
136             }
137
138             // finally the current full phrase
139             $sPartial = $this->aWords[0].' '.$sPartial;
140             if ($oTokens->containsAny($sPartial)) {
141                 $aSetCache[$i][] = array($sPartial);
142             }
143         }
144
145         $this->aWordSets = $aSetCache[$iNumWords - 1];
146         usort($this->aWordSets, array('\Nominatim\Phrase', 'cmpByArraylen'));
147         $this->aWordSets = array_slice($this->aWordSets, 0, Phrase::MAX_WORDSETS);
148     }
149
150
151     public function debugInfo()
152     {
153         return array(
154                 'Type' => $this->sPhraseType,
155                 'Phrase' => $this->sPhrase,
156                 'Words' => $this->aWords,
157                 'WordSets' => $this->aWordSets
158                );
159     }
160 }