mirror of
https://github.com/osm-search/Nominatim.git
synced 2024-11-27 00:49:55 +03:00
145 lines
4.4 KiB
PHP
145 lines
4.4 KiB
PHP
<?php
|
|
/**
|
|
* SPDX-License-Identifier: GPL-2.0-only
|
|
*
|
|
* This file is part of Nominatim. (https://nominatim.org)
|
|
*
|
|
* Copyright (C) 2022 by the Nominatim developer community.
|
|
* For a full list of authors see the git log.
|
|
*/
|
|
|
|
namespace Nominatim;
|
|
|
|
/**
|
|
* A word list creator based on simple splitting by space.
|
|
*
|
|
* Creates possible permutations of split phrases by finding all combination
|
|
* of splitting the phrase on space boundaries.
|
|
*/
|
|
class SimpleWordList
|
|
{
|
|
const MAX_WORDSET_LEN = 20;
|
|
const MAX_WORDSETS = 100;
|
|
|
|
// The phrase as a list of simple terms (without spaces).
|
|
private $aWords;
|
|
|
|
/**
|
|
* Create a new word list
|
|
*
|
|
* @param string sPhrase Phrase to create the word list from. The phrase is
|
|
* expected to be normalised, so that there are no
|
|
* subsequent spaces.
|
|
*/
|
|
public function __construct($sPhrase)
|
|
{
|
|
if (strlen($sPhrase) > 0) {
|
|
$this->aWords = explode(' ', $sPhrase);
|
|
} else {
|
|
$this->aWords = array();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get all possible tokens that are present in this word list.
|
|
*
|
|
* @return array The list of string tokens in the word list.
|
|
*/
|
|
public function getTokens()
|
|
{
|
|
$aTokens = array();
|
|
$iNumWords = count($this->aWords);
|
|
|
|
for ($i = 0; $i < $iNumWords; $i++) {
|
|
$sPhrase = $this->aWords[$i];
|
|
$aTokens[$sPhrase] = $sPhrase;
|
|
|
|
for ($j = $i + 1; $j < $iNumWords; $j++) {
|
|
$sPhrase .= ' '.$this->aWords[$j];
|
|
$aTokens[$sPhrase] = $sPhrase;
|
|
}
|
|
}
|
|
|
|
return $aTokens;
|
|
}
|
|
|
|
/**
|
|
* Compute all possible permutations of phrase splits that result in
|
|
* words which are in the token list.
|
|
*/
|
|
public function getWordSets($oTokens)
|
|
{
|
|
$iNumWords = count($this->aWords);
|
|
|
|
if ($iNumWords == 0) {
|
|
return null;
|
|
}
|
|
|
|
// Caches the word set for the partial phrase up to word i.
|
|
$aSetCache = array_fill(0, $iNumWords, array());
|
|
|
|
// Initialise first element of cache. There can only be the word.
|
|
if ($oTokens->containsAny($this->aWords[0])) {
|
|
$aSetCache[0][] = array($this->aWords[0]);
|
|
}
|
|
|
|
// Now do the next elements using what we already have.
|
|
for ($i = 1; $i < $iNumWords; $i++) {
|
|
for ($j = $i; $j > 0; $j--) {
|
|
$sPartial = $j == $i ? $this->aWords[$j] : $this->aWords[$j].' '.$sPartial;
|
|
if (!empty($aSetCache[$j - 1]) && $oTokens->containsAny($sPartial)) {
|
|
$aPartial = array($sPartial);
|
|
foreach ($aSetCache[$j - 1] as $aSet) {
|
|
if (count($aSet) < SimpleWordList::MAX_WORDSET_LEN) {
|
|
$aSetCache[$i][] = array_merge($aSet, $aPartial);
|
|
}
|
|
}
|
|
if (count($aSetCache[$i]) > 2 * SimpleWordList::MAX_WORDSETS) {
|
|
usort(
|
|
$aSetCache[$i],
|
|
array('\Nominatim\SimpleWordList', 'cmpByArraylen')
|
|
);
|
|
$aSetCache[$i] = array_slice(
|
|
$aSetCache[$i],
|
|
0,
|
|
SimpleWordList::MAX_WORDSETS
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
// finally the current full phrase
|
|
$sPartial = $this->aWords[0].' '.$sPartial;
|
|
if ($oTokens->containsAny($sPartial)) {
|
|
$aSetCache[$i][] = array($sPartial);
|
|
}
|
|
}
|
|
|
|
$aWordSets = $aSetCache[$iNumWords - 1];
|
|
usort($aWordSets, array('\Nominatim\SimpleWordList', 'cmpByArraylen'));
|
|
return array_slice($aWordSets, 0, SimpleWordList::MAX_WORDSETS);
|
|
}
|
|
|
|
/**
|
|
* Custom search routine which takes two arrays. The array with the fewest
|
|
* items wins. If same number of items then the one with the longest first
|
|
* element wins.
|
|
*/
|
|
public static function cmpByArraylen($aA, $aB)
|
|
{
|
|
$iALen = count($aA);
|
|
$iBLen = count($aB);
|
|
|
|
if ($iALen == $iBLen) {
|
|
return strlen($aB[0]) <=> strlen($aA[0]);
|
|
}
|
|
|
|
return ($iALen < $iBLen) ? -1 : 1;
|
|
}
|
|
|
|
public function debugInfo()
|
|
{
|
|
return $this->aWords;
|
|
}
|
|
}
|