структура данных, которая может выполнять поиск типа «выберите отдельный X, где W = w и Y = y и Z = z and ...» - PullRequest
1 голос
/ 25 июля 2010

У меня есть набор уникальных векторов (стоимостью 10 тыс.).И мне нужно для любого выбранного столбца извлечь набор значений, которые видны в этом столбце, в строках, где всем другим столбцам даны значения.

Я надеюсь, что решение будет сублинейным(по количеству предметов) во времени и самое большее линейное (по отношению к общему размеру всех предметов) в пространстве, предпочтительно сублинейное дополнительное пространство над простым хранением предметов.1005 *

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


редактировать: затраты на поиск иНе включает время на строительство конструкций.Все данные, которые когда-либо будут проиндексированы, доступны до того, как будет выполнен первый запрос.


Кажется, я плохо описываю то, что ищу, поэтому вот решениеэто близко:

class Index:
  dep __init__(self, stuff):  # don't care about this O() time
    self.all = set(stuff)
    self.index = {}
    for item in stuff:
      for i,v in item:
        self.index.getdefault(i,set()).add(v)

  def Get(self, col, have):  # this O() matters
    ret = []
    t = array(have)  # make a copy.
    for i in self.index[col]:
      t[col] = i
      if t in self.all:
        ret.append(i)
    return ret

Проблема в том, что это дает действительно плохой (O(n)) худший случай перф.

Ответы [ 5 ]

11 голосов
/ 25 июля 2010

Поскольку вы запрашиваете SQL-подобный запрос, как насчет использования реляционной базы данных?SQLite является частью стандартной библиотеки и может использоваться либо на диске, либо полностью в памяти.

5 голосов
/ 25 июля 2010

Если у вас есть набор Python (без упорядочивания), невозможно выбрать все релевантные элементы, по крайней мере, не глядя на все элементы, поэтому невозможно, чтобы какое-либо решение было «сублинейным» (по количеству элементов) как вам требуется.

Если у вас есть список, а не набор, его можно упорядочить, но этого нельзя достичь за линейное время в общем случае (O(N log N), несомненно, лучшее, что вы можете сделать для сортировки в общем случае) - и построение отсортированных индексов будет аналогичным - если только нет ограничений, которые позволят вам использовать подходы типа сортировки по сегментам). Вы можете распределить время, необходимое для сохранения индексов по всем вставкам в структуре данных, но это не сократит общее время, необходимое для построения таких индексов, просто, как я уже сказал, распределите их вокруг.

Индексы, основанные на хэше (не отсортированные), могут быть быстрее для вашего особого случая (когда вам нужно выбирать только по равенству, а не по «меньше чем» & c), но время для создания таких индексов линейно по числу элементов (очевидно, вы не можете создать такой индекс, не смотря хотя бы один раз на каждый элемент - сублинейное время требует некоторой магии, которая позволяет полностью игнорировать определенные элементы, и это не может произойти без поддержки «структуры» (такой как сортировка ) что, в свою очередь, требует времени для достижения (хотя это может быть достигнуто «постепенно» заранее, такой подход не сокращает общее требуемое время).

Итак, в буквальном смысле ваши требования кажутся чрезмерными: ни Python, ни какой-либо другой язык, ни какой-либо механизм базы данных и т. Д. Не могут их достичь - если интерпретировать их буквально в том виде, в каком вы их изложили. Если «инкрементная работа, выполненная раньше времени» не учитывается (как нарушение требований линейности и сублинейности), если вы выбираете поведение, ожидаемое / типичное, а не наихудшее (и ваши элементы имеют дружественные распределения вероятностей) и т. Д., То близко может удовлетворить ваши очень требовательные запросы.

Например, рассмотрите возможность сохранения для каждого из измерений векторов D словаря, отображающего значение, которое элемент имеет в этом измерении, на набор индексов таких элементов; затем, выбирая элементы, которые удовлетворяют требованиям равенства D-1 для каждого измерения, но i th, можно выполнить с помощью установленных пересечений. Соответствует ли это вашим требованиям? Не сводя последнее строго к букве, как я объяснил выше, но возможно, в зависимости от того, насколько каждое требование может быть принято в более «расслабленном» смысле.

Кстати, я не понимаю, что здесь подразумевается под «группой по», поскольку все векторы в каждой группе будут идентичны (поскольку вы сказали, что все измерения, кроме одного, определены равенством), так что, возможно, вы пропустил COUNT(*) в вашем SQL-эквиваленте - т. е. вам нужно подсчитать, сколько таких векторов имеют заданное значение в i-м измерении. В этом случае это было бы достижимо с помощью вышеуказанного подхода.

Редактировать : поскольку ФП несколько разъяснил в комментариях и редактировании своего Q, я могу предложить подход более подробно:

import collections

class Searchable(object):

  def __init__(self, toindex=None):
    self.toindex = toindex
    self.data = []
    self.indices = None

  def makeindices(self):
    if self.indices is not None:
      return
    self.indices = dict((i, collections.defaultdict(set))
                        for i in self.toindex)

  def add(self, record):
    if self.toindex is None:
      self.toindex = range(len(record))
    self.makeindices()
    where = len(self.data)
    self.data.append(record)
    for i in self.toindex:
      self.indices[i][record[i]].add(where)

  def get(self, indices_and_values, indices_to_get):
    ok = set(range(len(self.data)))
    for i, v in indices_and_values:
      ok.intersection_update(self.indices[i][v])
    result = set()
    for rec in (self.data[i] for i in ok):
      t = tuple(rec[i] for i in indices_to_get)
      result.add(t)
    return result

def main():
  c = Searchable()
  for r in ((1,2,3), (1,2,4), (1,5,4)):
    c.add(r)
  print c.get([(0,1),(1,2)], [2])

main()

Это печатает

set([(3,), (4,)])

и, конечно, может быть легко специализирован для возврата результатов в других форматах, принятия индексов (для индексации и / или для возврата) различными способами и т. Д. Я считаю, что это соответствует требованиям, которые были отредактированы / уточнены, поскольку дополнительное хранилище, для каждого индексированного измерения / значения - набор индексов, при которых упомянутое значение встречается в этом измерении, а время поиска - это одно пересечение наборов для каждого индексированного измерения плюс цикл по числу элементов, которые должны быть возвращены.

2 голосов
/ 25 июля 2010

Я предполагаю, что вы пробовали словарь, и вам нужно что-то более гибкое. По сути, вам нужно индексировать значения x, y и z

def build_index(vectors):
  index = {x: {}, y: {}, z: {}}

  for position, vector in enumerate(vectors):
    if vector.x in index['x']:
      index['x'][vector.x].append(position)
    else:
      index['x'][vector.x] = [position]

    if vector.y in index['y']:
      index['y'][vector.y].append(position)
    else:
      index['y'][vector.y] = [position]

    if vector.z in index['z']:
      index['z'][vector.z].append(position)
    else:
      index['z'][vector.z] = [position]

  return index

Что у вас есть в index таблице поиска. Вы можете сказать, например, select x,y,z from vectors where x=42, выполнив это:

def query_by(vectors, index, property, value):
  results = []
  for i in index[property][value]:
    results.append(vectors[i])

vecs_x_42 = query_by(index, 'x', 42)
# now vec_x_42 is a list of all vectors where x is 42

Теперь, чтобы сделать логическое соединение, скажем, select x,y,z from vectors where x=42 and y=3, вы можете использовать наборы Python для этого:

def query_by(vectors, index, criteria):
  sets = []
  for k, v in criteria.iteritems():
    if v not in index[k]:
      return []
    sets.append(index[k][v]) 

  results = []
  for i in set.intersection(*sets):
    results.append(vectors[i])

  return results

vecs_x_42_y_3 = query_by(index, {'x': 42, 'y': 3})

Операция пересечения на наборах создает значения, которые появляются только в обоих наборах, поэтому вы выполняете только те итерации, которые удовлетворяют обоим условиям.

Теперь перейдем к последней части вашего вопроса, сгруппировав по x:

def group_by(vectors, property):
  result = {}
  for v in vectors:
    value = getattr(v, property)
    if value in result:
      result[value].append(v)
    else:
      result[value] = [v]

  return result

Итак, давайте соберем все вместе:

vectors = [...] # your vectors, as objects such that v.x, v.y produces the x and y values
index = build_index(vectors)
my_vectors = group_by(query_by(vectors, index, {'y':42, 'z': 3}), 'x')

# now you have in my_vectors a dictionary of vectors grouped by x value, where y=42 and z=3

Обновление Я обновил код выше и исправил несколько очевидных ошибок. Это работает сейчас и делает то, что заявляет. На моем ноутбуке, 2 ГГц Core 2 Duo с 4 ГБ оперативной памяти, это занимает менее 1 с build_index. Поиск выполняется очень быстро, даже если в наборе данных имеется 100 000 векторов. Если у меня будет время, я сделаю некоторые формальные сравнения с MySQL.

Вы можете увидеть полный код на этой кодовой панели , если вы рассчитываете время или улучшаете его, дайте мне знать.

1 голос
/ 25 июля 2010

Предположим, у вас есть класс 'tuple' с полями x, y и z, и у вас есть куча таких кортежей, сохраненных в перечислимой переменной с именем myTuples. Тогда:

А) Предпопопуляция:

dct = {}
for tpl in myTuples:
    tmp = (tpl.y, tpl.z)
    if tmp in dct:
        dct[tmp].append(tpl.x)
    else: 
        dct[tmp] = [tpl.x]

B) Запрос:

def findAll(y,z):
    tmp = (y,z)
    if tmp not in dct: return ()
    return [(x,y,z) for x in dct[tmp]]

Я уверен, что есть способ оптимизировать код для удобства чтения, сохранить несколько циклов и т. Д. Но, по сути, вы хотите предварительно заполнить текст, используя 2-кортеж в качестве ключа. Если бы я не видел запрос на сублинейные функции, я бы этого не сделал:)

А) Предварительная популяция является линейной, извините. B) Запрос должен быть таким же медленным, как и количество возвращаемых элементов - большую часть времени сублинейным, за исключением странных крайних случаев.

0 голосов
/ 25 июля 2010

Итак, у вас есть 3 координаты и одно значение для начала и конца вектора (x, y, z)?

Как можно узнать семь известных значений? Много ли многократных координат многократно?

Вы, должно быть, делаете очень плотную петлю с функцией, которая должна быть сохранена во времени поиска, учитывая небольшой размер данных (10 КБ).

Не могли бы вы привести пример реального вклада для вашего класса, который вы опубликовали?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...