2013-03-14 99 views
6

假设我有以下variables及其对应的values,代表record什么是存储一组四个(或更多)值的最佳数据结构?

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

请注意,value可能是不同typesstringintegerfloat,参考到任意-其他对象,等)。

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

我想在Python找到一个有效的数据结构,这将让我(商店)基于这些variableslog(n)时间复杂度的任何一个检索records

例如:

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 recordcolumnlogarithmicncolumns(姓名,年龄,性别,体重,身高等),retrieving records基于任何(一个) (或理想情况下查找时间)复杂?

+0

能否请您证明-1?这是一个真正的编程问题。 – 2013-03-14 19:27:17

+0

也许这会帮助你 - http://wiki.python.org/moin/TimeComplexity? – kgr 2013-03-14 19:35:48

+0

为什么不使用sql呢?似乎更适合。 Python已经内置了对sqlite的支持。 – 2013-03-14 19:40:38

回答

5

没有内置到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)实例,这提供关于平均类字典ø(1)查找时间,并且还允许多个值与每一个相关联。因此,查找键是要查找的字段值的值,并且与其关联的数据将是Person列表中存储的Person记录的整数索引列表,并且具有该值 - 因此它们都是相对的小。

请注意,该类的代码完全是数据驱动的,因为它不包含任何硬编码的字段名,它们在读入时取自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 = defaultdict(lambda: defaultdict(list)) 

    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 function ' 
      '(%s specified)' % ', '.join([repr(k) for k in kwargs.keys()])) 
     field, value = list(kwargs.items())[0] # Get only keyword arg 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: # Must create field look up table. 
      for index, record in enumerate(self.records): 
       value = getattr(record, field) 
       self.lookup_tables[field][value].append(index) 
     matches = [self.records[index] 
        for index in self.lookup_tables[field].get(value, [])] 
     return matches if matches else None 

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 
3

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

  • 有一本字典为您感兴趣的各列 - AGENAME
  • 拥有的字典(AGENAME)是可能的钥匙给定列(35或“m”)的值。
  • 具有表示一个“集合”的值的列表的列表,例如, VALUES = [ [35, "m"], ...]
  • 将列字典的值(AGENAME)列为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'}] 

但是再次,字典有点h在内存方面很有意思,所以如果你可以使用值列表,它可能会更好。

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

当然的话,最好将基准您的实际执行情况和寻找潜在的改进,但希望这是有意义的,并让你去:)

编辑:

请注意,@moooeeeep说,这只会工作,如果你的值是可散列的,因此可以用作字典键。

4

有没有在Python的数据结构,这将使存储纪录n数列(姓名,年龄,性别,体重,身高等)和检索列基于任何(一个)记录在对数(或理想恒定 - O(1)查找时间)复杂度?

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

2

您可以使用索引(​​单列索引)在关系数据库中实现对数时间复杂度。然后检索数据只是构建适当的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 
+0

你能告诉我下面语法的名字吗? names = {'name','age','weight','height'} – 2017-02-04 00:33:55

+1

@LEDFantom:这是一个[set display](https://docs.python。org/3/reference/expressions.html#displays-for-lists-sets-and-dictionaries)(一个创建'set()'对象的文字)。它可以在Python 2.7和Python 3上使用。 – jfs 2017-02-04 00:53:05

相关问题