【问题标题】:Best algorithm for multi dimensional searching in pythonpython中多维搜索的最佳算法
【发布时间】:2012-12-06 11:50:44
【问题描述】:

我一直在试验一个应用程序,但我遇到了这个问题。我有一个规则列表,如下所示。这是实验数据,真实数据有更多字段(30+)。每条记录都可以包含一些值和一些空值。这是一个列表列表,但我也可以将它保存在 defaultdict 中(如果有帮助的话)。大约 100 万条记录。

Age  Gender  City    Religion  Propensity
23   *       Delhi   *         0.33
*    M       Mumbai  *         0.78
*    *       *       Hindu     0.23
34   F       Chennai *         0.33
...
...
...

现在我有一个包含所有值的数据集 - (23, M, Delhi, Hindu)。

我需要从上表中选择与该记录匹配的所有记录,即使一个维度以最快的速度按维度数降序排列。所以在这种情况下,第 3 行和第 1 行匹配。所以空值最少的记录会在底部。

我需要一种复杂的方法来实现这一点,它可以在 python 中大规模工作。无法使用任何其他软件。

【问题讨论】:

  • 将工作卸载到 SQL 是一种选择吗?如果没有,您是否对数据有列索引(按列值快速查找)(或者 python 是否原生提供)?
  • 使用数据库并创建索引。
  • 100 万条记录,没有数据库?如果你问我,那你有一个严重的问题。
  • 你已经有一个数据库,你将重新发明查询方法,如果你真的“不能使用数据库”,最好使用docs.python.org/2/library/sqlite3.html#sqlite3.connect和“:memory”存储方法。跨度>
  • 你会同时做很多这样的查询还是很少?

标签: python algorithm sorting search tree


【解决方案1】:

假设您的“搜索条件”始终相同,即“数据集”(年龄、性别、城市、宗教)

将其移动到由您的“数据集”索引的列表(或集合)字典中

result_dict = {}
for record in record_list:
    # you have to know the indexes
    # I'm assuming 0=Age, 1=Gender, 2=City, 3=Hindu
    key_data = []
    for index in [0, 1, 2, 3]:
        key_data.append(str(record[index]))
    key = ','.join(key_data)
    try:
        result_dict[key].append(record)
    except KeyError:
        result_dict[key] = []
        result_dict[key].append(record)

# Find all records that match '23,M,Delhi,Hindu'
print result_dict['23,M,Delhi,Hindu']

但实际上,我可能会将其存储在数据库中,然后在其上运行 SQL 查询。

【讨论】:

  • 你在哪里尊重那里的通配符?
  • 当然不考虑通配符。此 * 仅被视为任何其他值。请以粗体查看我的免责声明。
  • 这种方法如何支持这种要求:matches this record even with one dimension?
【解决方案2】:

您可以将数据存储在一组字典中:

dict1:age->list<entry>
dict2:gender->list<entry>
...

现在,当您收到查询时 - 您所要做的就是创建一个 histogram (map:entry->integer) 根据值对其进行排序并按降序打印。

运行时间为O(d*m + mlogm)(平均),其中d 是列表数(维度),m 是输出条目数。

伪代码:

assume  list of dictionaries, let it be L:
printRelevants(entry):
   histogram <- new dictionary
   for each dimension d:
      l <- L[d].get(entry[d])
      for each element e in l: #remember to check for null l first
         val <- histogram.get(e)
         if val is null:
             histogram.put(e,1)
         else:
             histogram.put(e,val+1) #assuming overriding old entry with the same key
    #done creating the histogram! 
    sort histogram according to value
    print keys of histogram in descending order

【讨论】:

  • @GarethRees 不是m==0。当然,你不能有零输出。
  • @GarethRees O(...) 是一组函数。如果m==0,则集合O(mn+n)O(mn) 是不同的。如果m==0O(mn) 仅包含有界函数。即使一个集合“等于”它的任何元素,如果两边都是集合,你需要更加小心。
  • @JanDvorak:在这里说“如果m==0”没有意义。 Big-O 表示法是关于一个函数在其所有输入趋于无穷大时的行为。
  • @GarethRees 奇怪...我认为输入向量可以向任何方向发散,而不是所有组件都必须发散。
【解决方案3】:

假设您确实使用评论中提到的 SQL 数据库 (sqlite3),SQL 将如下所示:

-- gives you the set of complete records
SELECT
    v0.*
FROM values v0
WHERE -- only full records
    v0.Age IS NOT NULL
AND v0.Gender IS NOT NULL
AND v0.City IS NOT NULL
AND v0.Religion IS NOT NULL
AND v0.Propensity IS NOT NULL


SELECT v1.*, 
    CASE v1.Age WHEN IS NULL THEN 0 ELSE 1 END + 
    CASE v1.Gender WHEN IS NULL THEN 0 ELSE 1 END +
    CASE v1.City WHEN IS NULL THEN 0 ELSE 1 END +
    CASE v1.Religion WHEN IS NULL THEN 0 ELSE 1 END + 
    CASE v1.Propensity WHEN IS NULL THEN 0 ELSE 1 END as dimensions
FROM values v1
WHERE v1.Age = 23
OR    v1.Gender = 'M'
OR    v1.City = 'Delhi'
OR    v1.Religion = 'Hindu'
ORDER BY dimensions desc

【讨论】:

  • 我应该创建索引吗?我想必须在每一列上创建一个索引。
  • 我在尝试这个 - SELECT * FROM rules WHERE (age='23' OR age='') AND (gender='M' OR gender='') AND (city='mumbai' OR city='') AND (religion='3' OR宗教='')
  • 查询需要 0.24 毫秒。如果我在所有字段上创建复合索引,则需要 0.34 毫秒。
  • 上面每隔一个学期就有一个星号 OR age='[asterik]'
  • @AdityaSingh 您使用的 where 子句只会选择每条记录。假设一条记录都是通配符,它​​会显示在您的查询中。
【解决方案4】:

这里有几个很好的答案。我已经编写并测试了一些代码。

首先是需求的简单实现:

import pprint

t = [
    [ 23,   None, 'Delhi',   None,    0.33 ],
    [ None, 'M',  'Mumbai',  None,    0.78 ],
    [ None, None,  None,     'Hindu', 0.23 ],
    [ 34,   'F',  'Chennai', None,    0.33 ],
]

rlen = len(t[0])

# None may require special handling
m = [23, 'M', 'Delhi', 'Hindu', None]

a = [[] for i in range(rlen+1)]

for r in t:
    s = sum([1 for i in range(rlen) if r[i] == m[i]])
    if 0 < s:
        a[s].append(r)

# Print rows from least matching to most matching (easy to reverse)
rtable = [row for n in a for row in n]
pprint.pprint(rtable)

问题是我们扫描每一行并检查每个元素的值。为了避免在最后进行排序,我们为每个可能的匹配计数保留单独的列表,然后我们将列表列表展平以获得最终结果。我希望这相对于表的大小执行大约 O(n),如果我们有大量匹配项(构建大型结果列表将比 O(n) 慢,接近 O(n^2) 作为最坏的情况)。

如果我们索引表,我们可以加快速度。我们可以每列使用一个 dict 并使用集合组合列。

from collections import defaultdict
import pprint

# data table
TABLE = [
    [ 23,   None, 'Delhi',   None,    0.33 ],
    [ None, 'M',  'Mumbai',  None,    0.78 ],
    [ None, None,  None,     'Hindu', 0.23 ],
    [ 34,   'F',  'Chennai', None,    0.33 ],
]

# The index is a list of dicts, cdictlist.
# cdictlist is indexed by column number to get the column dict.
# The column dict's key is the data value of the column
def BuildIndex(table):
    rlen = len(table[0])
    rrange = range(rlen)
    cdictlist = [defaultdict(set) for i in range(rlen+1)]
    for ir in range(len(table)):
        r = table[ir]
        for ic in rrange:
            f = r[ic]
            cdictlist[ic][f].add(ir)
    return cdictlist


def multisearch(table, match, cdictlist):
    # rcounts is row counts, number of times columns have matched for a row
    rccounts = defaultdict(int)

    #rset is the result set, set of row indexes returned for this search
    rset = set()

    for ic in range(len(table[0])):
        cset = cdictlist[ic].get(match[ic], set())
        rset = rset.union(cset)
        for i in cset:
            rccounts[i] += 1

    # sort the list by column match count, original row index
    l = sorted((v,k) for (k,v) in rccounts.iteritems())

    # return list of rows, for each row we return (count, index, raw data)
    lr = [ [l[i][0], l[i][1]] + table[l[i][1]] for i in range(len(l)) ]
    return lr

def main():
    cdictlist = BuildIndex(TABLE)

    # None may require special handling
    match = [23, 'M', 'Delhi', 'Hindu', None]

    lr = multisearch(TABLE, match, cdictlist)
    pprint.pprint(lr)

if __name__ == '__main__':
    main()

性能将取决于返回的记录数而不是表的大小。对于大量匹配,集合并集操作将很快成为问题。如果 any 字段匹配并且示例字段之一是 Gender,则记录匹配,因此我们应该期望至少返回一半的行。

如果您必须匹配所有列,这种方法会好得多。我们可以通过构建返回的记录集NOT(使用集合交集),然后过滤掉这些记录来改进这一点。

【讨论】:

  • 试试你的东西。到目前为止一直在搞乱 SQLite。在 30k 条记录上,大约需要 0.26 毫秒。 170k 记录为 0.40 毫秒,1 百万数据库为 0.50 毫秒,启用内存选项并在所有字段上创建复合索引。在 python 中尝试一些替代方法。
  • @AdityaSingh。谢谢。我预计内存中的 SQLite 很可能会成为赢家。
  • @AdityaSingh,我刚刚意识到生成被排除的记录集可能更快。有时间我会做这个的。
猜你喜欢
  • 2020-09-03
  • 2010-12-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-04-17
  • 2018-04-13
  • 2012-01-12
相关资源
最近更新 更多