python - 用于在内存中维护表格数据的数据结构?

标签 python data-structures

我的场景如下:我有一个在我的程序中广泛使用的数据表(少数字段,不到一百行)。我还需要这些数据是持久的,所以我将它保存为 CSV 并在启动时加载它。我选择不使用数据库,因为每个选项(甚至 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. Row 可能是写入文件的“真实”值,也可能只是表示行号的自动生成值。无论哪种方式,它都存在于内存中。
  2. 名字是独一无二的。

我对数据所做的事情:

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

我知道这种对 SQL 的“呼喊”...

我正在尝试找出数据结构的最佳选择。以下是我看到的几个选择:

行列表列表:

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] )

列列表字典(可以创建常量来替换字符串键):

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
...

而且我确信还有其他方法......但是,当涉及到我的要求(复杂的排序和计数)时,每种方法都有缺点。

推荐的方法是什么?

编辑:

澄清一下,性能对我来说不是主要问题。因为表很小,我相信几乎每个操作都会在毫秒范围内,这不是我的应用程序关心的问题。

最佳答案

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

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

然后,您可以使用 SQLite 的所有功能在内存中创建/删除/查询/更新表,完成后不会留下任何文件。从 Python 2.5 开始,sqlite3 在标准库中,所以它并不是真正“矫枉过正”的 IMO。

这是一个如何创建和填充数据库的示例:

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

测试然后产生:

>>> 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 版本,因为它更好地保留了您的类型(无需 Python 中的额外转换代码)并且可以轻松扩展以适应 future 的需求。但话又说回来,我对 SQL 很满意,所以 YMMV。

关于python - 用于在内存中维护表格数据的数据结构?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1038160/

相关文章:

mysql - Excel VBA : Query a MySQL Database

c++ - 多个函数需要相同的参数(如何优化),

python - 在python中从一个网格插值到另一个网格

python - 移动表单上的按钮

python - 为什么在使用 Anaconda 启动期间运行 virt-install BASH 脚本与使用 Python 的 subprocess.run() 函数会出现不一致?

对于不同的venv环境,Python包没有单独安装

python - Keras 3d 张量的前 k 个分类精度问题

c - 如何在C中的字符串中为time_t分配内存

C Linked List - 链接下一个链表节点

c - 链接列表不保留递归函数调用中的值?