1 # SPDX-License-Identifier: GPL-3.0-or-later
 
   3 # This file is part of Nominatim. (https://nominatim.org)
 
   5 # Copyright (C) 2025 by the Nominatim developer community.
 
   6 # For a full list of authors see the git log.
 
   8 Simple dict-based implementation of a trie structure.
 
  10 from typing import TypeVar, Generic, Tuple, Optional, List, Dict
 
  11 from collections import defaultdict
 
  16 class SimpleTrie(Generic[T]):
 
  17     """ A simple read-only trie structure.
 
  18         This structure supports examply one lookup operation,
 
  19         which is longest-prefix lookup.
 
  22     def __init__(self, data: Optional[List[Tuple[str, T]]] = None) -> None:
 
  23         self._tree: Dict[str, 'SimpleTrie[T]'] = defaultdict(SimpleTrie[T])
 
  24         self._value: Optional[T] = None
 
  28             for key, value in data:
 
  29                 self._add(key, 0, value)
 
  33     def _add(self, word: str, pos: int, value: T) -> None:
 
  34         """ (Internal) Add a sub-word to the trie.
 
  35             The word is added from index 'pos'. If the sub-word to add
 
  36             is empty, then the trie saves the given value.
 
  39             self._tree[word[pos]]._add(word, pos + 1, value)
 
  43     def _make_compact(self) -> None:
 
  44         """ (Internal) Compress tree where there is exactly one subtree
 
  47             Compression works recursively starting at the leaf.
 
  49         for t in self._tree.values():
 
  52         if len(self._tree) == 1 and self._value is None:
 
  53             assert not self._prefix
 
  54             for k, v in self._tree.items():
 
  55                 self._prefix = k + v._prefix
 
  57                 self._value = v._value
 
  59     def longest_prefix(self, word: str, start: int = 0) -> Tuple[Optional[T], int]:
 
  60         """ Return the longest prefix match for the given word starting at
 
  63             The function returns a tuple with the value for the longest match and
 
  64             the position of the word after the match. If no match was found at
 
  65             all, the function returns (None, start).
 
  69         result: Tuple[Optional[T], int] = None, start
 
  73                 if not word.startswith(cur._prefix, pos):
 
  75                 pos += len(cur._prefix)
 
  78                 result = cur._value, pos
 
  80             if pos >= len(word) or word[pos] not in cur._tree:
 
  83             cur = cur._tree[word[pos]]