Есть ли ближайшая карта данных?

У меня есть ситуация, когда мне нужно найти значение с ключом, ближайшим к тому, который я запрашиваю. Это похоже на ближайшую карту, которая определяет расстояние между клавишами.

Например, если у меня есть ключи {A, C, M, Z} на карте, запрос D будет возвращать значение C.

Любая идея?

Ответы

Ответ 1

В большинстве структур данных дерева используется некоторый алгоритм сортировки для хранения и поиска ключей. Многие реализации таких могут найти ключ закрытия ключа, с которым вы зондируете (обычно это либо ближайший ниже, либо ближайший выше). Например, Java TreeMap реализует такую ​​структуру данных, и вы можете сказать ей, чтобы получить ближайший ключ под вашим ключом поиска или ближайший ключ над вашим ключом поиска (higherKey и lowerKey).

Если вы можете рассчитать расстояния (не всегда легко - интерфейс Java требует, чтобы вы знали, находится ли какой-либо данный ключ "ниже" или "выше" любого другого заданного ключа), тогда вы можете запросить как ближайшее, так и самое близкое ниже и затем подсчитайте для себя, какой из них ближе.

Ответ 2

Какова размерность ваших данных? Если это всего лишь одно измерение, сортированный массив сделает это - бинарный поиск найдет точное совпадение и/или покажет, между какими двумя ключами находится ваш ключ поиска, и простой тест скажет вам, что ближе.

Если вам нужно найти не только ближайший ключ, но и связанное с ним значение, поддерживайте идентично отсортированный массив значений - индекс возвращаемого ключа в массиве ключей будет тогда индексом значения в массиве значений.

Конечно, существует много альтернативных подходов, которые можно использовать в зависимости от многих других факторов, таких как потребление памяти, нужно ли вводить значения, если вы контролируете порядок вставки, удаления, проблемы с потоками и т.д..

Ответ 3

BK-деревья делают именно то, что вы хотите. Здесь хорошая статья об их реализации.

И вот реализация Scala:

class BKTree[T](computeDistance: (T, T) => Int, node: T) {
  val subnodes = scala.collection.mutable.HashMap.empty[Int,BKTree[T]]

  def query(what: T, distance: Int): List[T] = {
    val currentDistance = computeDistance(node, what)
    val minDistance = currentDistance - distance
    val maxDistance = currentDistance + distance
    val elegibleNodes = (
      subnodes.keys.toList 
      filter (key => minDistance to maxDistance contains key) 
      map subnodes
    )
    val partialResult = elegibleNodes flatMap (_.query(what, distance))
    if (currentDistance <= distance) node :: partialResult else partialResult
  }

  def insert(what: T): Boolean = if (node == what) false else (
    subnodes.get(computeDistance(node, what)) 
    map (_.insert(what)) 
    getOrElse {
      subnodes(computeDistance(node, what)) = new BKTree(computeDistance, what)
      true
    }
  )

  override def toString = node.toString+"("+subnodes.toString+")"
}

object Test {
  def main(args: Array[String]) {
    val root = new BKTree(distance, 'A')
    root.insert('C')
    root.insert('M')
    root.insert('Z')
    println(findClosest(root, 'D'))
  }
  def charDistance(a: Char, b: Char) = a - b abs
  def findClosest[T](root: BKTree[T], what: T): List[T] = {
    var distance = 0
    var closest = root.query(what, distance)
    while(closest.isEmpty) {
      distance += 1
      closest = root.query(what, distance)
    }
    closest
  }
}

Я соглашусь на определенную грязь и уродство об этом, и быть слишком умным с алгоритмом вставки. Кроме того, он будет работать только на небольшом расстоянии, иначе вы будете многократно искать дерево. Здесь альтернативная реализация, которая лучше справляется с этим:

class BKTree[T](computeDistance: (T, T) => Int, node: T) {
  val subnodes = scala.collection.mutable.HashMap.empty[Int,BKTree[T]]

  def query(what: T, distance: Int): List[T] = {
    val currentDistance = computeDistance(node, what)
    val minDistance = currentDistance - distance
    val maxDistance = currentDistance + distance
    val elegibleNodes = (
      subnodes.keys.toList 
      filter (key => minDistance to maxDistance contains key) 
      map subnodes
    )
    val partialResult = elegibleNodes flatMap (_.query(what, distance))
    if (currentDistance <= distance) node :: partialResult else partialResult
  }

  private def find(what: T, bestDistance: Int): (Int,List[T]) = {
    val currentDistance = computeDistance(node, what)
    val presentSolution = if (currentDistance <= bestDistance) List(node) else Nil
    val best = currentDistance min bestDistance
    subnodes.keys.foldLeft((best, presentSolution))(
      (acc, key) => {
        val (currentBest, currentSolution) = acc
        val (possibleBest, possibleSolution) = 
          if (key <= currentDistance + currentBest)
            subnodes(key).find(what, currentBest)
          else
            (0, Nil)
        (possibleBest, possibleSolution) match {
          case (_, Nil) => acc
          case (better, solution) if better < currentBest => (better, solution)
          case (_, solution) => (currentBest, currentSolution ::: solution)
        }
      }
    )
  }

  def findClosest(what: T): List[T] = find(what, computeDistance(node, what))._2

  def insert(what: T): Boolean = if (node == what) false else (
    subnodes.get(computeDistance(node, what)) 
    map (_.insert(what)) 
    getOrElse {
      subnodes(computeDistance(node, what)) = new BKTree(computeDistance, what)
      true
    }
  )

  override def toString = node.toString+"("+subnodes.toString+")"
}

object Test {
  def main(args: Array[String]) {
    val root = new BKTree(distance, 'A')
    root.insert('C')
    root.insert('E')
    root.insert('M')
    root.insert('Z')
    println(root.findClosest('D'))
  }
  def charDistance(a: Char, b: Char) = a - b abs
}

Ответ 4

В контейнерах С++ и STL (std::map) вы можете использовать следующую функцию шаблона:

#include <iostream>
#include <map>

//!This function returns nearest by metric specified in "operator -" of type T
//!If two items in map are equidistant from item_to_find, the earlier occured by key will be returned

template <class T,class U> typename std::map<T,U>::iterator find_nearest(std::map<T,U> map_for_search,const T& item_to_find)
{
  typename std::map<T,U>::iterator itlow,itprev;
  itlow=map_for_search.lower_bound(item_to_find);
  itprev=itlow;
  itprev--;
//for cases when we have "item_to_find" element in our map
//or "item_to_find" occures before the first element of map
  if ((itlow->first==item_to_find) || (itprev==map_for_search.begin()))
    return itlow;
//if "item"to_find" is besides the last element of map
  if (itlow==map_for_search.end())
    return itprev;

  return (itlow->first-item_to_find < item_to_find-itprev->first)?itlow:itprev; // C will be returned
//note that "operator -" is used here as a function for distance metric
}

int main ()
{
  std::map<char,int> mymap;
  std::map<char,int>::iterator nearest;
  //fill map with some information
  mymap['B']=20;
  mymap['C']=40;
  mymap['M']=60;
  mymap['Z']=80;
  char ch='D'; //C should be returned
  nearest=find_nearest<char,int>(mymap,ch);
  std::cout << nearest->first << " => " << nearest->second << '\n';
  ch='Z'; //Z should be returned
  nearest=find_nearest<char,int>(mymap,ch);
  std::cout << nearest->first << " => " << nearest->second << '\n';
  ch='A'; //B should be returned
  nearest=find_nearest<char,int>(mymap,ch);
  std::cout << nearest->first << " => " << nearest->second << '\n';
  ch='H'; // equidistant to C and M -> C is returned
  nearest=find_nearest<char,int>(mymap,ch);
  std::cout << nearest->first << " => " << nearest->second << '\n';
  return 0;
}

Вывод:

C => 40
Z => 80
B => 20
C => 40

Предполагается, что a operator - используется как функция для оценки расстояния. Вы должны реализовать этот оператор, если class T - ваш собственный класс, объекты которого служат в качестве ключей на карте. Вы также можете изменить код, чтобы использовать специальную class T статическую функцию-член (например, distance), а не operator -, вместо этого:

return (T::distance(itlow->first,item_to_find) < T::distance(item_to_find,itprev->first))?itlow:itprev;

где distance должно быть немного. как

static distance_type some_type::distance()(const some_type& first, const some_type& second){//...}

и distance_type должны поддерживать сравнение operator <

Ответ 5

Вы можете реализовать что-то подобное в виде дерева. Простым подходом является назначение каждого node в дереве битовой строки. Каждый уровень дерева хранится как бит. Вся родительская информация кодируется в битовой строке node. Затем вы можете легко найти произвольные узлы и найти родителей и детей. Например, "Заказ Morton" работает. У этого есть дополнительное преимущество, что вы можете рассчитать расстояния между узлами простым двоичным вычитанием.

Если у вас есть несколько связей между значениями данных, ваша структура данных представляет собой график, а не дерево. В этом случае вам потребуется немного более сложная система индексирования. Распределенные хэш-таблицы делают такие вещи. Обычно они имеют способ вычисления расстояния между любыми двумя узлами в индексном пространстве. Например, алгоритм Kademlia (используемый Bittorrent) использует расстояния XOR, применяемые к идентификаторам битстрима. Это позволяет клиентам Bittorrent искать идентификаторы в цепочке, сходящиеся в неизвестном целевом местоположении. Вы можете использовать аналогичный подход, чтобы найти node (s), ближайший к вашей цели node.

Ответ 6

Если ваши клавиши являются строками, а ваша функция подобия расстояние Levenshtein, то вы можете использовать конечные машины:

Ваша карта представляет собой trie, созданный как конечный автомат (путем объединения всех пар ключ/значение и детерминации). Затем составьте свой входной запрос с помощью простого преобразователя с конечным состоянием, который кодирует расстояние Левенштейна и составит его с помощью тви. Затем используйте алгоритм Витерби, чтобы извлечь кратчайший путь.

Вы можете реализовать все это только с помощью нескольких вызовов функций, используя конечный набор инструментов.

Ответ 7

в scala это метод, который я использую, чтобы найти ближайший Int <= к ключу, который вы ищете

val sMap = SortedMap(1 -> "A", 2 -> "B", 3 -> "C")
sMap.to(4).lastOption.get // Returns 3
sMap.to(-1) // Returns an empty Map