用于维护表格数据的内存数据结构?

我的情况如下:我有一个在我的程序中广泛使用的数据表(less数几个字段,不到一百行)。 我也需要这个数据是持久的,所以我把它保存为CSV并在启动时加载它。 我select不使用数据库,因为每一个选项(甚至是SQLite)对我的卑微的要求是一个矫枉过正的(也是 – 我想能够以一种简单的方式离线编辑值,没有什么比记事本简单)。

假设我的数据如下所示(在文件中逗号分隔,没有标题,这只是一个例子):

Row | Name | Year | Priority ------------------------------------ 1 | Cat | 1998 | 1 2 | Fish | 1998 | 2 3 | Dog | 1999 | 1 4 | Aardvark | 2000 | 1 5 | Wallaby | 2000 | 1 6 | Zebra | 2001 | 3 

笔记:

  1. 行可能是写入文件的“真实”值,或者只是表示行号的自动生成的值。 无论哪种方式它存在于内存中。
  2. 名字是独一无二的

我对数据做的事情:

  1. 根据ID(迭代)或名称(直接访问)查找一行。
  2. 根据多个字段以不同的顺序显示表格:我需要按照优先级,年份,年份,优先级等进行sorting。
  3. 我需要根据一组参数来计算实例,例如1997年到2002年之间有多less行,或者1998年有多less行,优先级> 2等。

我知道这个“哭”的SQL …

我试图找出什么是数据结构的最佳select。 以下是我看到的几个select:

行列表的列表:

 a = [] a.append( [1, "Cat", 1998, 1] ) a.append( [2, "Fish", 1998, 2] ) a.append( [3, "Dog", 1999, 1] ) ... 

列表列表(显然是add_row等的API):

 a = [] a.append( [1, 2, 3, 4, 5, 6] ) a.append( ["Cat", "Fish", "Dog", "Aardvark", "Wallaby", "Zebra"] ) a.append( [1998, 1998, 1999, 2000, 2000, 2001] ) a.append( [1, 2, 1, 1, 1, 3] ) 

字典列表(可以创build常量来replacestring键):

 a = {} a['ID'] = [1, 2, 3, 4, 5, 6] a['Name'] = ["Cat", "Fish", "Dog", "Aardvark", "Wallaby", "Zebra"] a['Year'] = [1998, 1998, 1999, 2000, 2000, 2001] a['Priority'] = [1, 2, 1, 1, 1, 3] 

(行,字段)键的字典:

 Create constants to avoid string searching NAME=1 YEAR=2 PRIORITY=3 a={} a[(1, NAME)] = "Cat" a[(1, YEAR)] = 1998 a[(1, PRIORITY)] = 1 a[(2, NAME)] = "Fish" a[(2, YEAR)] = 1998 a[(2, PRIORITY)] = 2 ... 

而且我敢肯定还有其他的方法……但是当涉及到我的要求(复杂的sorting和计数)时,每种方法都有缺点。

什么是推荐的方法?

编辑:

为了澄清,性能对我来说不是一个大问题。 由于表格非常小​​,我相信几乎所有的操作都会在几毫秒的范围内,这不是我的应用程序所关心的问题。

在需要查找,sorting和任意聚合的内存中有一个“表”,确实需要调用SQL。 你说过你尝试过SQLite,但你是否意识到SQLite可以使用内存中的数据库?

 connection = sqlite3.connect(':memory:') 

然后,您可以使用SQLite的所有function在内存中创build/删除/查询/更新表,并在完成时不留下任何文件。 而从Python 2.5开始, sqlite3就在标准库中,所以它不是真的“过度杀伤”IMO。

以下是如何创build和填充数据库的示例:

 import csv import sqlite3 db = sqlite3.connect(':memory:') def init_db(cur): cur.execute('''CREATE TABLE foo ( Row INTEGER, Name TEXT, Year INTEGER, Priority INTEGER)''') def populate_db(cur, csv_fp): rdr = csv.reader(csv_fp) cur.executemany(''' INSERT INTO foo (Row, Name, Year, Priority) VALUES (?,?,?,?)''', rdr) cur = db.cursor() init_db(cur) populate_db(cur, open('my_csv_input_file.csv')) db.commit() 

如果你真的不想使用SQL,你应该使用一个字典列表:

 lod = [ ] # "list of dicts" def populate_lod(lod, csv_fp): rdr = csv.DictReader(csv_fp, ['Row', 'Name', 'Year', 'Priority']) lod.extend(rdr) def query_lod(lod, filter=None, sort_keys=None): if filter is not None: lod = (r for r in lod if filter(r)) if sort_keys is not None: lod = sorted(lod, key=lambda r:[r[k] for k in sort_keys]) else: lod = list(lod) return lod def lookup_lod(lod, **kw): for row in lod: for k,v in kw.iteritems(): if row[k] != str(v): break else: return row return None 

testing然后产生:

 >>> lod = [] >>> populate_lod(lod, csv_fp) >>> >>> pprint(lookup_lod(lod, Row=1)) {'Name': 'Cat', 'Priority': '1', 'Row': '1', 'Year': '1998'} >>> pprint(lookup_lod(lod, Name='Aardvark')) {'Name': 'Aardvark', 'Priority': '1', 'Row': '4', 'Year': '2000'} >>> pprint(query_lod(lod, sort_keys=('Priority', 'Year'))) [{'Name': 'Cat', 'Priority': '1', 'Row': '1', 'Year': '1998'}, {'Name': 'Dog', 'Priority': '1', 'Row': '3', 'Year': '1999'}, {'Name': 'Aardvark', 'Priority': '1', 'Row': '4', 'Year': '2000'}, {'Name': 'Wallaby', 'Priority': '1', 'Row': '5', 'Year': '2000'}, {'Name': 'Fish', 'Priority': '2', 'Row': '2', 'Year': '1998'}, {'Name': 'Zebra', 'Priority': '3', 'Row': '6', 'Year': '2001'}] >>> pprint(query_lod(lod, sort_keys=('Year', 'Priority'))) [{'Name': 'Cat', 'Priority': '1', 'Row': '1', 'Year': '1998'}, {'Name': 'Fish', 'Priority': '2', 'Row': '2', 'Year': '1998'}, {'Name': 'Dog', 'Priority': '1', 'Row': '3', 'Year': '1999'}, {'Name': 'Aardvark', 'Priority': '1', 'Row': '4', 'Year': '2000'}, {'Name': 'Wallaby', 'Priority': '1', 'Row': '5', 'Year': '2000'}, {'Name': 'Zebra', 'Priority': '3', 'Row': '6', 'Year': '2001'}] >>> print len(query_lod(lod, lambda r:1997 <= int(r['Year']) <= 2002)) 6 >>> print len(query_lod(lod, lambda r:int(r['Year'])==1998 and int(r['Priority']) > 2)) 0 

就个人而言,我更喜欢SQLite版本,因为它更好地保留了你的types(没有额外的Python转换代码),并且容易增长以适应未来的需求。 但是再一次,我对SQL很满意,所以YMMV。

一个非常古老的问题,我知道,但…

pandasDataFrame似乎是这里的理想select。

http://pandas.pydata.org/pandas-docs/version/0.13.1/generated/pandas.DataFrame.html

从blurb

二维大小可变,具有标记轴(行和列)的潜在异构表格数据结构。 算术运算在行和列标签上alignment。 可以被认为是Series对象的类似字典的容器。 主要pandas数据结构

http://pandas.pydata.org/

我个人会使用行列表的列表。 因为每行的数据总是以相同的顺序排列,所以只需访问每个列表中的元素,就可以轻松地按任何列进行sorting。 您也可以根据每个列表中的特定列轻松计数,并进行search。 它基本上和二维数组一样接近。

真正唯一的缺点是你必须知道数据的顺序,如果你改变了这个顺序,你必须改变你的search/sorting例程来匹配。

你可以做的另一件事是有一个词典列表。

 rows = [] rows.append({"ID":"1", "name":"Cat", "year":"1998", "priority":"1"}) 

这将避免需要知道参数的顺序,所以您可以查看列表中的每个“年”字段。

有一个Table类,其行是一个字典或更好的行对象的列表

在表中不要直接添加行,但有一个方法,更新几个查找地图,如名称,如果你没有添加行的顺序或ID不是连续的,你也可以有idMap

 class Table(object): def __init__(self): self.rows = []# list of row objects, we assume if order of id self.nameMap = {} # for faster direct lookup for row by name def addRow(self, row): self.rows.append(row) self.nameMap[row['name']] = row def getRow(self, name): return self.nameMap[name] table = Table() table.addRow({'ID':1,'name':'a'}) 

首先,考虑到你有一个复杂的数据检索场景,你确定SQLite是否过度杀伤?

你最终会得到一个特殊的,非正式指定的,错误缠身,缓慢执行一半的SQLite, 这就解释了Greenspun的第十条规则 。

也就是说,select单一数据结构会影响一个或多个search,sorting或计数是非常正确的,因此,如果性能是至关重要的,并且数据是不变的,那么可以考虑为不同的目的使用多个结构。

最重要的是,衡量哪些操作将更为常见,并决定哪种结构最终会花费更less的成本。

我个人最近写了一个lib,叫做BD_XML

因为其存在的最根本的原因是作为在XML文件和SQL数据库之间来回传送数据的一种方式。

它是用西class牙文写成的(如果这在编程语言中很重要),但是非常简单。

 from BD_XML import Tabla 

它定义了一个名为Tabla(Table)的对象,它可以用一个名称来创build,以便识别pep-246兼容数据库接口的预先创build的连接对象。

 Table = Tabla('Animals') 

然后,您需要添加具有agregar_columna (add_column)方法的列,可以使用各种关键字参数:

  • campo (字段):字段的名称

  • tipo (type):存储数据的types,如果你不想导出到数据库后者,可以是类似'varchar'和'double'或python对象名称的东西。

  • defecto (默认值):如果在添加行时没有设置列的默认值

  • 还有其他3个,但只有数据库的时间,并没有实际的function

喜欢:

 Table.agregar_columna(campo='Name', tipo='str') Table.agregar_columna(campo='Year', tipo='date') #declaring it date, time, datetime or timestamp is important for being able to store it as a time object and not only as a number, But you can always put it as a int if you don't care for dates Table.agregar_columna(campo='Priority', tipo='int') 

然后你用+ =操作符(或者+,如果你想创build一个额外的行的副本)添加行

 Table += ('Cat', date(1998,1,1), 1) Table += {'Year':date(1998,1,1), 'Priority':2, Name:'Fish'} #… #The condition for adding is that is a container accessible with either the column name or the position of the column in the table 

然后,您可以生成XML,并使用exportar_XML (export_XML)和escribir_XML (write_XML)将其写入文件中:

 file = os.path.abspath(os.path.join(os.path.dirname(__file__), 'Animals.xml')) Table.exportar_xml() Table.escribir_xml(file) 

然后用importar_XML (import_XML)将文件名称和使用文件而不是string文字的方式导入:

 Table.importar_xml(file, tipo='archivo') #archivo means file 

高级

这是您可以用SQL方式使用Tabla对象的方法。

 #UPDATE <Table> SET Name = CONCAT(Name,' ',Priority), Priority = NULL WHERE id = 2 for row in Table: if row['id'] == 2: row['Name'] += ' ' + row['Priority'] row['Priority'] = None print(Table) #DELETE FROM <Table> WHERE MOD(id,2) = 0 LIMIT 1 n = 0 nmax = 1 for row in Table: if row['id'] % 2 == 0: del Table[row] n += 1 if n >= nmax: break print(Table) 

这个例子假定一个名为'id'的列,但是可以replace宽度row.pos作为例子。

 if row.pos == 2: 

该文件可以从下载:

https://bitbucket.org/WolfangT/librerias