7

假设我有以下内容variables及其对应values的代表 a record

name = 'abc'
age = 23
weight = 60
height = 174

请注意,value可能是不同的typesstringintegerfloat、对任何其他对象的引用等)。

会有很多records(至少> 100,000)。当所有这四个(实际上是它的)放在一起时,每一个都record将是。换句话说,不存在所有4个都相同。uniquevariablesvaluesrecordvalues

我正在尝试找到一种有效的数据结构,在Python该结构中,我可以根据时间复杂度中records的任何一个来(存储和)检索。variableslog(n)

例如:

def retrieve(name=None,age=None,weight=None,height=None) 
    if name is not None and age is None and weight is None and height is None:
        /* get all records with the given name */
    if name is None and age is not None and weight is None and height is None:
        /* get all records with the given age */
    ....
    return records

retrieve应该调用的方式如下:

retrieve(name='abc') 

以上应该返回[{name:'abc', age:23, wight:50, height=175}, {name:'abc', age:28, wight:55, height=170}, etc]

retrieve(age=23) 

以上应该返回[{name:'abc', age:23, wight:50, height=175}, {name:'def', age:23, wight:65, height=180}, etc]

variables而且,将来我可能需要在此记录中再添加一两个。例如,说,sex = 'm'。因此,该retrieve功能必须是可扩展的。

简而言之:是否有一种数据结构Python允许storing a record包含(姓名、年龄、性别、体重、身高等)的n数量并基于任何(一个)in (或理想的查找时间)复杂性? columnsretrieving recordscolumnlogarithmicconstant - O(1)

4

4 回答 4

9

Python 中没有一个单一的数据结构可以满足您的所有需求,但是使用它所必须的数据结构组合来实现您的目标并相当有效地实现这一目标是相当容易的。

例如,假设您的输入是逗号分隔值文件中的以下数据,该文件调用employees.csv的字段名称如第一行所示:

name,age,weight,height
Bob Barker,25,175,6ft 2in
Ted Kingston,28,163,5ft 10in
Mary Manson,27,140,5ft 6in
Sue Sommers,27,132,5ft 8in
Alice Toklas,24,124,5ft 6in

以下是工作代码,它说明了如何读取这些数据并将其存储到记录列表中,并自动创建单独的查找表以查找与每个记录的字段中包含的值相关联的记录。

记录是由其创建的类的实例,该类namedtuple的内存效率非常高,因为每个记录都缺少__dict__类实例通常包含的属性。使用它们可以使用点语法按名称访问每个字段,例如record.fieldname.

查找表是defaultdict(list)实例,它们平均提供类似于字典的O (1) 查找时间,并且还允许多个值与每个值相关联。因此,查找键是要查找的字段值的值,与之关联的数据将是Person存储在employees具有该值的列表中的记录的整数索引列表——因此它们都相对较小。

请注意,该类的代码完全是数据驱动的,因为它不包含任何硬编码的字段名称,而是在读入时从 csv 数据输入文件的第一行获取。当然,当使用实例时,所有retrieve()方法调用必须提供有效的字段名称。

更新

修改为在首次读取数据文件时不为每个字段的每个唯一值创建查找表。现在,retrieve()“懒惰”的方法仅在需要时创建它们(并保存/缓存结果以供将来使用)。还修改为在 Python 2.7+ 中工作,包括 3.x。

from collections import defaultdict, namedtuple
import csv

class DataBase(object):
    def __init__(self, csv_filename, recordname):
        # Read data from csv format file into a list of namedtuples.
        with open(csv_filename, 'r') as inputfile:
            csv_reader = csv.reader(inputfile, delimiter=',')
            self.fields = next(csv_reader)  # Read header row.
            self.Record = namedtuple(recordname, self.fields)
            self.records = [self.Record(*row) for row in csv_reader]
            self.valid_fieldnames = set(self.fields)

        # Create an empty table of lookup tables for each field name that maps
        # each unique field value to a list of record-list indices of the ones
        # that contain it.
        self.lookup_tables = {}

    def retrieve(self, **kwargs):
        """ Fetch a list of records with a field name with the value supplied
            as a keyword arg (or return None if there aren't any).
        """
        if len(kwargs) != 1: raise ValueError(
            'Exactly one fieldname keyword argument required for retrieve function '
            '(%s specified)' % ', '.join([repr(k) for k in kwargs.keys()]))
        field, value = kwargs.popitem()  # Keyword arg's name and value.
        if field not in self.valid_fieldnames:
            raise ValueError('keyword arg "%s" isn\'t a valid field name' % field)
        if field not in self.lookup_tables:  # Need to create a lookup table?
            lookup_table = self.lookup_tables[field] = defaultdict(list)
            for index, record in enumerate(self.records):
                field_value = getattr(record, field)
                lookup_table[field_value].append(index)
        # Return (possibly empty) sequence of matching records.
        return tuple(self.records[index]
                        for index in self.lookup_tables[field].get(value, []))

if __name__ == '__main__':
    empdb = DataBase('employees.csv', 'Person')

    print("retrieve(name='Ted Kingston'): {}".format(empdb.retrieve(name='Ted Kingston')))
    print("retrieve(age='27'): {}".format(empdb.retrieve(age='27')))
    print("retrieve(weight='150'): {}".format(empdb.retrieve(weight='150')))
    try:
        print("retrieve(hight='5ft 6in'):".format(empdb.retrieve(hight='5ft 6in')))
    except ValueError as e:
        print("ValueError('{}') raised as expected".format(e))
    else:
        raise type('NoExceptionError', (Exception,), {})(
            'No exception raised from "retrieve(hight=\'5ft\')" call.')

输出:

retrieve(name='Ted Kingston'): [Person(name='Ted Kingston', age='28', weight='163', height='5ft 10in')]
retrieve(age='27'): [Person(name='Mary Manson', age='27', weight='140', height='5ft 6in'),
                     Person(name='Sue Sommers', age='27', weight='132', height='5ft 8in')]
retrieve(weight='150'): None
retrieve(hight='5ft 6in'): ValueError('keyword arg "hight" is an invalid fieldname')
                           raised as expected
于 2013-03-14T21:39:40.207 回答
4

Python中是否有数据结构允许存储具有n列数(姓名、年龄、性别、体重、身高等)的记录,并基于对数列中的任何(一个)(或理想情况下为常数-O)检索记录(1)查找时间)复杂度?

不,没有。但是您可以尝试在每个值维度一个字典的基础上实现一个。当然,只要您的值是可散列的。如果您为记录实现自定义类,则每个字典都将包含对相同对象的引用。这将为您节省一些内存。

于 2013-03-14T19:54:04.917 回答
4

O(log(n)**k)您可以使用索引(使用单列索引)在关系数据库中实现对数时间复杂度。然后检索数据只需构造适当的 SQL:

names = {'name', 'age', 'weight', 'height'}

def retrieve(c, **params):
    if not (params and names.issuperset(params)):
        raise ValueError(params)
    where = ' and '.join(map('{0}=:{0}'.format, params))
    return c.execute('select * from records where ' + where, params)

例子:

import sqlite3

c = sqlite3.connect(':memory:')
c.row_factory = sqlite3.Row # to provide key access

# create table
c.execute("""create table records
             (name text, age integer, weight real, height real)""")

# insert data
records = (('abc', 23, 60, 174+i) for i in range(2))
c.executemany('insert into records VALUES (?,?,?,?)', records)

# create indexes
for name in names:
    c.execute("create index idx_{0} on records ({0})".format(name))

try:
    retrieve(c, naame='abc')
except ValueError:
    pass
else:
    assert 0

for record in retrieve(c, name='abc', weight=60):
    print(record['height'])

输出:

174.0
175.0
于 2013-03-14T20:40:16.590 回答
3

鉴于http://wiki.python.org/moin/TimeComplexity这个怎么样:

  • 为您感兴趣的每一列都有一本字典 - AGENAME等。
  • 让该字典的键 ( AGE, NAME) 成为给定列的可能值(35 或“m”)。
  • 有一个代表一个“集合”值的列表列表,例如VALUES = [ [35, "m"], ...]
  • 让列字典 ( AGE, NAME) 的值是列表中的索引VALUES列表。
  • 有一个字典,它将列名映射到列表中的索引,VALUES这样你就知道第一列是年龄,第二列是性别(你可以避免这种情况并使用字典,但它们会引入大内存脚注,并且有超过 100K 的对象,这可能是也可能不是一个问题)。

那么retrieve函数可能如下所示:

def retrieve(column_name, column_value):
    if column_name == "age":
        return [VALUES[index] for index in AGE[column_value]]      
    elif ...: # repeat for other "columns"

然后,这就是你得到的

VALUES = [[35, "m"], [20, "f"]]
AGE = {35:[0], 20:[1]}
SEX = {"m":[0], "f":[1]}
KEYS = ["age", "sex"]

retrieve("age", 35)
# [[35, 'm']]

如果需要字典,可以执行以下操作:

[dict(zip(KEYS, values)) for values in retrieve("age", 35)]
# [{'age': 35, 'sex': 'm'}]

但同样,字典在内存方面有点重,所以如果你可以使用值列表可能会更好。

字典和列表检索平均都是 O(1) - 字典的最坏情况是 O(n) - 所以这应该很快。保持这种状态会有点痛苦,但不会那么痛苦。要“写入”,您只需附加到VALUES列表中,然后将索引附加VALUES到每个字典中。

当然,最好的方法是对您的实际实现进行基准测试并寻找潜在的改进,但希望这是有道理的并且会让您继续前进:)

编辑:

请注意,正如@moooeeeep 所说,这仅在您的值是可散列的并且因此可以用作字典键时才有效。

于 2013-03-14T19:49:16.960 回答